/*---------------------------------------------------------------------------*/
/* network.cpp                                                               */
/* copyright (c) innovaphone 2017                                            */
/*                                                                           */
/*---------------------------------------------------------------------------*/
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#ifdef __ANDROID__
#include <fcntl.h>
#include <ctype.h>
#include <sys/endian.h>
#include <asm/ioctls.h>
#include <linux/if.h>
#include <jni.h>
#include "platform/platform.h"
#include "client/android/android_jni/android_jni.h"

JNIEnv *get_jni_env(void);

extern jobject phone_android_context;

#define WLAN0_INTERFACE_NAME    "wlan0"
#define WLAN0_INTERFACE_UPPER   "WLAN0"

#else
#include <ifaddrs.h>
#include <net/if.h>

#ifdef _IOS_

#include <CoreFoundation/CoreFoundation.h>
#include <CFNetwork/CFNetwork.h>
#include "TargetConditionals.h"
#if TARGET_IPHONE_SIMULATOR
#define WLAN0_INTERFACE_NAME    "en1"
#define WLAN0_INTERFACE_UPPER   "EN1"
#else
#define WLAN0_INTERFACE_NAME    "en0"
#define WLAN0_INTERFACE_UPPER   "EN0"
#endif
#else
#define WLAN0_INTERFACE_NAME    "wlan0"
#define WLAN0_INTERFACE_UPPER   "WLAN0"
#endif

#include <platform.h>
#endif
#include "sdk/common/interface/network.h"

#define IPSEC_INTERFACE_NAME    "ipsec"
#define IPSEC_INTERFACE_UPPER   "IPSEC"
#define INETWORK_MAX_HOST_CANDIDATES (10 - 2 - 1) /* CHANNEL_MAX_CANDIDATE - 2 SRFLX - 1 RELAY */
#define is_automatic_private_ip_address(d) (((d) & htonl((dword)0xffff0000)) == htonl((dword)0xa9fe0000)) /* 169.254.x.x */
#define IN6_IS_ADDR_GLOBAL_UNICAST(sin6_addr) (((sin6_addr)->s6_addr[0] & 0xe0) == 0x20)

static bool might_be_vpn_interface(const char *name, bool *ppp0_found, bool *wlan0_found)
{
    unsigned int j;

    j = strlen(name);
    if ((j == 4) && (memcmp(name, "ppp0", 4) == 0)) {
        *ppp0_found = true;
        return true;
    }
    else if ((j >= sizeof(WLAN0_INTERFACE_NAME) - 1)
      &&((memcmp(&(name[j - (sizeof(WLAN0_INTERFACE_NAME) - 1)]), WLAN0_INTERFACE_NAME, sizeof(WLAN0_INTERFACE_NAME) - 1) == 0)
       ||(memcmp(&(name[j - (sizeof(WLAN0_INTERFACE_UPPER) - 1)]), WLAN0_INTERFACE_UPPER, sizeof(WLAN0_INTERFACE_UPPER) - 1) == 0))) {
        *wlan0_found = true;
    }
    return ((strncmp(name, IPSEC_INTERFACE_NAME, sizeof(IPSEC_INTERFACE_NAME) - 1) == 0) || (strncmp(name, IPSEC_INTERFACE_UPPER, sizeof(IPSEC_INTERFACE_UPPER) - 1) == 0));
}

static unsigned int INetworkLastAddrCount = 0;

LocalAddresses * INetwork::GetLocalAddresses()
{
    class LocalAddresses * addrs = new class LocalAddresses();
    unsigned int addrCount = 0;
    bool ppp0_found, wlan0_found, preferred;

#ifdef __ANDROID__
    struct ifconf if_conf;
    struct ifreq if_req_buffer[64];
    struct sockaddr_in6 addr6;
    struct ifreq *ip, *irp;
    char if_name[IFNAMSIZ];
    int sock;
    int res;
    JNIEnv *phone_android_jni_env;
    jboolean bool_result;
    jobject network_interfaces_ref, network_interface_ref, inet_addresses_ref, inet_address_ref;
    jbyteArray address;
    jsize address_length;
    jbyte *address_buf;
    jstring interface_name_string;
    const char *interface_name;
    unsigned interface_name_len;

    ppp0_found = false;
    wlan0_found = false;
    if_conf.ifc_len = sizeof(if_req_buffer);
    if_conf.ifc_req = if_req_buffer;
    sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if(sock == -1) {
        return addrs;
    }
    res = ioctl(sock, SIOCGIFCONF, (void *)(&if_conf));
    if(res < 0) {
        close(sock);
        return addrs;
    }
    ip = if_conf.ifc_req;
    while(ip < (struct ifreq *)(((byte *)(if_conf.ifc_req)) + if_conf.ifc_len)) {
        if((ip->ifr_addr.sa_family == AF_INET) && (((sockaddr_in*)&ip->ifr_addr)->sin_addr.s_addr != htonl(INADDR_LOOPBACK)) && !is_automatic_private_ip_address(((sockaddr_in*)&ip->ifr_addr)->sin_addr.s_addr)) {
            irp = if_conf.ifc_req;
            while ((irp != ip)
             && ((irp->ifr_addr.sa_family != AF_INET)
              || (((sockaddr_in*)&irp->ifr_addr)->sin_addr.s_addr != ((sockaddr_in*)&ip->ifr_addr)->sin_addr.s_addr))) {
                irp++;
            }
            if (irp == ip) {
                char addrBuf[128];
                inet_ntop(AF_INET, &(((sockaddr_in*)(&ip->ifr_addr))->sin_addr), addrBuf, 128);
                class LocalAddr * host = new class LocalAddr(_strdup(addrBuf), false, true, might_be_vpn_interface(ip->ifr_name, &ppp0_found, &wlan0_found));
                addrs->localAddresses.push_back(host);
                addrCount++;
                /*if (INetworkLastAddrCount > INETWORK_MAX_HOST_CANDIDATES)*/ {
                    debug->printf("INetwork::GetLocalAddresses %d %s %s %u %u %u", ip->ifr_addr.sa_family, addrBuf, ip->ifr_name, host->iPv6, host->preferred, host->vpn);
                }
            }
        }
        ip++;
    }
    irp = (struct ifreq *)(((byte *)(if_conf.ifc_req)) + if_conf.ifc_len);
    phone_android_jni_env = get_jni_env();
    network_interfaces_ref = NetworkInterface_getNetworkInterfaces();
    while (Enumeration_hasMoreElements(network_interfaces_ref)) {
        network_interface_ref = Enumeration_nextElement(network_interfaces_ref);
        interface_name_string = (jstring)(NetworkInterface_getName(network_interface_ref));
        interface_name = JNI_GetStringUTFChars(interface_name_string, &bool_result);
        interface_name_len = strlen(interface_name);
        inet_addresses_ref = NetworkInterface_getInetAddresses(network_interface_ref);
        if (inet_addresses_ref) {
            while (Enumeration_hasMoreElements(inet_addresses_ref)) {
                inet_address_ref = Enumeration_nextElement(inet_addresses_ref);
                address = InetAddress_getAddress(inet_address_ref);
                address_length = JNI_GetArrayLength(address);
                if (address_length == sizeof(addr6.sin6_addr.s6_addr)) {
                    memset(&addr6, 0, sizeof(addr6));
                    addr6.sin6_family = AF_INET6;
                    address_buf = JNI_GetByteArrayElements(address, NULL);
                    memcpy(addr6.sin6_addr.s6_addr, address_buf, sizeof(addr6.sin6_addr.s6_addr));
                    if (interface_name_len < sizeof(if_name)) {
                        memcpy(if_name, interface_name, interface_name_len + 1);
                    }
                    else {
                        memcpy(if_name, interface_name, sizeof(if_name) - 1);
                        if_name[sizeof(if_name) - 1] = '\0';
                    }
                    if(!IN6_IS_ADDR_LOOPBACK(&(addr6.sin6_addr)) && IN6_IS_ADDR_GLOBAL_UNICAST(&(addr6.sin6_addr))) {
                        class LocalAddr *inf;
                        char addrBuf[128];

                        inet_ntop(AF_INET6, &(addr6.sin6_addr), addrBuf, 128);
                        ip = if_conf.ifc_req;
                        while ((ip < (struct ifreq *)(((byte *)(if_conf.ifc_req)) + if_conf.ifc_len))
                         && ((ip->ifr_addr.sa_family != AF_INET)
                          || (((sockaddr_in*)&ip->ifr_addr)->sin_addr.s_addr == htonl(INADDR_LOOPBACK))
                          || is_automatic_private_ip_address(((sockaddr_in*)&ip->ifr_addr)->sin_addr.s_addr)
                          || ((strcmp(ip->ifr_name, if_name) != 0)
                           && (((((sockaddr_in*)&ip->ifr_addr)->sin_addr.s_addr & htonl(0xffffff00)) != htonl(0xc0000000)) // match "2a01:599:b28:5e92:cdb1:3f72:6d90:457d rmnet0" with "192.0.0.4 v4-rmnet0"
                            || !strstr(ip->ifr_name, if_name))))) {
                            ip++;
                        }
                        preferred = (ip != (struct ifreq *)(((byte *)(if_conf.ifc_req)) + if_conf.ifc_len));
                        ip = (struct ifreq *)(((byte *)(if_conf.ifc_req)) + if_conf.ifc_len);
                        while ((ip < irp) && (strcmp(ip->ifr_name, if_name) != 0)) {
                            ip++;
                        }
                        preferred &= (ip == irp);
                        inf = NULL;
                        if(!addrs->localAddresses.empty()) {
                            inf = addrs->localAddresses.front();
                            while (inf && (!inf->iPv6 || (strcmp(inf->addr, addrBuf) != 0))) {
                                inf = inf->goNext();
                            }
                        }
                        if (!inf) {
                            class LocalAddr * host = new class LocalAddr(_strdup(addrBuf), true, preferred, might_be_vpn_interface(if_name, &ppp0_found, &wlan0_found));
                            addrs->localAddresses.push_back(host);
                            addrCount++;
                            /*if (INetworkLastAddrCount > INETWORK_MAX_HOST_CANDIDATES)*/ {
                                debug->printf("INetwork::GetLocalAddresses %d %s %s %u %u %u", addr6.sin6_family, addrBuf, if_name, host->iPv6, host->preferred, host->vpn);
                            }
                        }
                        else if (preferred) {
                            inf->preferred = true;
                        }
                        if ((ip == irp) && (irp < (struct ifreq *)(((byte *) if_req_buffer) + sizeof(if_req_buffer)))) {
                            strcpy(irp->ifr_name, if_name);
                            irp++;
                        }
                    }
                    JNI_ReleaseByteArrayElements(address, address_buf, 0);
                }
                JNI_DeleteLocalRef(address);
                JNI_DeleteLocalRef(inet_address_ref);
            }
            JNI_DeleteLocalRef(inet_addresses_ref);
        }
        JNI_ReleaseStringUTFChars(interface_name_string, interface_name);
        JNI_DeleteLocalRef(interface_name_string);
        JNI_DeleteLocalRef(network_interface_ref);
    }
    JNI_DeleteLocalRef(network_interfaces_ref);
#else
    struct ifaddrs *ifap;
#ifdef _IOS_
    addrs->localAddresses.push_back(new class LocalAddr(_strdup("127.0.0.1"), false, true, false));
#endif
    ppp0_found = false;
    wlan0_found = false;
    if(!getifaddrs (&ifap)) {
        struct ifaddrs *ifa;
        for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
            if(ifa->ifa_addr && (ifa->ifa_addr->sa_family == AF_INET) && (((sockaddr_in*)ifa->ifa_addr)->sin_addr.s_addr != htonl(INADDR_LOOPBACK)) && !is_automatic_private_ip_address(((sockaddr_in*)ifa->ifa_addr)->sin_addr.s_addr)) {
                struct ifaddrs *ia;
                ia = ifap;
                while ((ia != ifa)
                 && (!ia->ifa_addr
                  || (ia->ifa_addr->sa_family != AF_INET)
                  || (((sockaddr_in*)ia->ifa_addr)->sin_addr.s_addr != ((sockaddr_in*)ifa->ifa_addr)->sin_addr.s_addr))) {
                    ia = ia->ifa_next;
                }
                if (ia == ifa) {
                    char addrBuf[128];
                    inet_ntop(AF_INET, &(((sockaddr_in*)ifa->ifa_addr)->sin_addr), addrBuf, 128);
                    char netmaskBuf[128];
                    netmaskBuf[0] = '\0';
                    if (ifa->ifa_netmask) {
                        inet_ntop(AF_INET, &(((sockaddr_in*)ifa->ifa_netmask)->sin_addr), netmaskBuf, 128);
                    }
                    char dstaddrBuf[128];
                    dstaddrBuf[0] = '\0';
                    if (ifa->ifa_dstaddr) {
                        inet_ntop(AF_INET, &(((sockaddr_in*)ifa->ifa_dstaddr)->sin_addr), dstaddrBuf, 128);
                    }
                    class LocalAddr * host = new class LocalAddr(_strdup(addrBuf), false, true, might_be_vpn_interface(ifa->ifa_name, &ppp0_found, &wlan0_found));
                    addrs->localAddresses.push_back(host);
                    addrCount++;
                    /*if (INetworkLastAddrCount > INETWORK_MAX_HOST_CANDIDATES)*/ {
                        debug->printf("INetwork::GetLocalAddresses %d %s %s %x %s %s %u %u %u", ifa->ifa_addr->sa_family, addrBuf, ifa->ifa_name, ifa->ifa_flags, netmaskBuf, dstaddrBuf, host->iPv6, host->preferred, host->vpn);
                    }
                }
            }
        }
        for (ifa = ifap; ifa; ifa = ifa->ifa_next) {
            if(ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET6) {
                struct sockaddr_in6 addr6;
                memcpy(&addr6, ifa->ifa_addr, sizeof(struct sockaddr_in6));
#ifdef _IOS_
                /* Patch IP6_ADDRESS_TYPE_SITE_LOCAL since ios clears the second word of the address and returns it as sin6_scope_id */
                if ((addr6.sin6_addr.s6_addr[0] == 0xfe) && ((addr6.sin6_addr.s6_addr[1] & 0xc0) == 0xc0)
                 && (addr6.sin6_addr.s6_addr[2] == 0) && (addr6.sin6_addr.s6_addr[3] == 0)) {
                    addr6.sin6_addr.s6_addr[2] = (byte)(addr6.sin6_scope_id >> 8);
                    addr6.sin6_addr.s6_addr[3] = (byte)(addr6.sin6_scope_id);
                }
#endif
                if(!IN6_IS_ADDR_LOOPBACK(&(addr6.sin6_addr)) && IN6_IS_ADDR_GLOBAL_UNICAST(&(addr6.sin6_addr))) {
                    struct ifaddrs *ia;
                    class LocalAddr *inf;
                    char addrBuf[128];

                    inet_ntop(AF_INET6, &(addr6.sin6_addr), addrBuf, 128);
                    char netmaskBuf[128];
                    netmaskBuf[0] = '\0';
                    if (ifa->ifa_netmask) {
                        inet_ntop(AF_INET6, &(((sockaddr_in*)ifa->ifa_netmask)->sin_addr), netmaskBuf, 128);
                    }
                    char dstaddrBuf[128];
                    dstaddrBuf[0] = '\0';
                    if (ifa->ifa_dstaddr) {
                        inet_ntop(AF_INET6, &(((sockaddr_in*)ifa->ifa_dstaddr)->sin_addr), dstaddrBuf, 128);
                    }
#ifdef _IOS_
                    ia = ifap;
                    while (ia
                     &&  (!ia->ifa_addr
                      || (ia->ifa_addr->sa_family != AF_INET)
                      || (((sockaddr_in*)ia->ifa_addr)->sin_addr.s_addr == htonl(INADDR_LOOPBACK))
                      || is_automatic_private_ip_address(((sockaddr_in*)ia->ifa_addr)->sin_addr.s_addr)
                      || (strcmp(ia->ifa_name, ifa->ifa_name) != 0))) {
                        ia = ia->ifa_next;
                    }
                    preferred = (ia != NULL);
#endif
                    ia = ifap;
                    while ((ia != ifa)
                     && (!ia->ifa_addr
                      || (ia->ifa_addr->sa_family != AF_INET6)
                      || IN6_IS_ADDR_LOOPBACK(&(((sockaddr_in6*)ia->ifa_addr)->sin6_addr))
                      || !IN6_IS_ADDR_GLOBAL_UNICAST(&(((sockaddr_in6*)ia->ifa_addr)->sin6_addr))
                      || (strcmp(ia->ifa_name, ifa->ifa_name) != 0))) {
                        ia = ia->ifa_next;
                    }
#ifdef _IOS_
                    preferred &= (ia == ifa);
#else
                    preferred = (ia == ifa);
#endif
                    inf = NULL;
                    if(!addrs->localAddresses.empty()) {
                        inf = addrs->localAddresses.front();
                        while (inf && (!inf->iPv6 || (strcmp(inf->addr, addrBuf) != 0))) {
                            inf = inf->goNext();
                        }
                    }
                    if (!inf) {
                        class LocalAddr * host = new class LocalAddr(_strdup(addrBuf), true, preferred, might_be_vpn_interface(ifa->ifa_name, &ppp0_found, &wlan0_found));
                        addrs->localAddresses.push_back(host);
                        addrCount++;
                        /*if (INetworkLastAddrCount > INETWORK_MAX_HOST_CANDIDATES)*/ {
                            debug->printf("INetwork::GetLocalAddresses %d %s %s %x %s %s %u %u %u", ifa->ifa_addr->sa_family, addrBuf, ifa->ifa_name, ifa->ifa_flags, netmaskBuf, dstaddrBuf, host->iPv6, host->preferred, host->vpn);
                        }
                    }
                    else if (preferred) {
                        inf->preferred = true;
                    }
                }
            }
        }
    }
    // Free memory
    freeifaddrs(ifap);
#if 0
    if(gethostname(hostname, sizeof(hostname))) {
        return addrs;
    }

    struct addrinfo hints, *res, *p;
    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    if (getaddrinfo(hostname, NULL, &hints, &res) == 0) {
        for (p = res; p != NULL; p = p->ai_next) {
            if (p) {
                SOCKADDR_IN *t_addr = (SOCKADDR_IN *)p->ai_addr;
                if (t_addr) {
                    char addrBuf[128];
                    class LocalAddr * host = new class LocalAddr(_strdup(inet_ntop(AF_INET, &(((sockaddr_in*)t_addr)->sin_addr), addrBuf, 128)), false, true);
                    addrs->localAddresses.push_back(host);
                    addrCount++;
                }                
            }
        }
    }

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET6;
    if (getaddrinfo(hostname, NULL, &hints, &res) == 0) {
        for (p = res; p != NULL; p = p->ai_next) {
            if (p) {
                sockaddr_in6 *t_addr = (sockaddr_in6 *)p->ai_addr;
                if (t_addr) {
                    char addrBuf[128];
                    class LocalAddr * host = new class LocalAddr(_strdup(inet_ntop(AF_INET6, &(((sockaddr_in6*)t_addr)->sin6_addr), addrBuf, 128)), true, true);
                    addrs->localAddresses.push_back(host);
                    addrCount++;
                }                
            }
        }
    }
#endif
#endif
#if (defined(__ANDROID__) || defined(_IOS_))
    if (ppp0_found && !wlan0_found) { /* ppp0_is_mobile_data */
        class LocalAddr * host;
        for(host = addrs->localAddresses.front(); host; host = host->goNext()) {
            host->vpn = false;
        }
    }
#endif
    INetworkLastAddrCount = addrCount;
    return addrs;
}

HTTPProxies * INetwork::GetHTTPProxiesForURL(const char * url)
{
    HTTPProxies * http_proxies = new class HTTPProxies();
    unsigned len = strlen(url);
    char * http_url = (char *)(alloca(len + 3));
    if (((url[0] == 'w') || (url[0] == 'W'))
     && ((url[1] == 's') || (url[1] == 'S'))
     && (((url[2] == ':')
       && (url[3] == '/')
       && (url[4] == '/'))
      || (((url[2] == 's') || (url[2] == 'S'))
       && (url[3] == ':')
       && (url[4] == '/')
       && (url[5] == '/')))) {
        memcpy(http_url, "http", 4);
        memcpy(http_url + 4, url + 2, len - 1);
    }
    else {
        memcpy(http_url, url, len + 1);
    }
//    debug->printf("INetwork::GetHTTPProxiesForURL %s", url);

#if defined(__ANDROID__)
    JNIEnv *phone_android_jni_env;
    jboolean bool_result;
    jobject local_ref;
    jsize proxies_length, proxy_index, accounts_length, accounts_index;
    jstring host_string;
    const char * host, * account_name;
    char * usr, * pwd;
    jint port;
    HTTPProxyType type;

    phone_android_jni_env = get_jni_env();
    jobject account_manager = AccountManager_get(phone_android_context);
    jstring username_key = JNI_NewStringUTF("username");
    jstring account_type = JNI_NewStringUTF("com.innovaphone.http_proxy_credentials");
    jobjectArray accounts = account_manager ? AccountManager_getAccountsByType(account_manager, account_type) : NULL;
    accounts_length = accounts ? JNI_GetArrayLength(accounts) : 0;
    local_ref = ProxySelector_getDefault();
    jstring url_string = JNI_NewStringUTF(http_url);
    jobject uri = URI_create(url_string);
    jobject proxies = NULL;
    if (local_ref) {
        proxies = ProxySelector_select(local_ref, uri);
        JNI_DeleteLocalRef(local_ref);
    }
    if (proxies) {
        proxies_length = List_size(proxies);
        for (proxy_index = 0; proxy_index < proxies_length; proxy_index++) {
            jobject proxy = List_get(proxies, proxy_index);
            jobject socket_address = Proxy_address(proxy);
            if (socket_address) {
                host_string = (jstring)(InetSocketAddress_getHostString(socket_address));
                host = JNI_GetStringUTFChars(host_string, &bool_result);
                port = InetSocketAddress_getPort(socket_address);
            }
            else {
                host_string = NULL;
                host = NULL;
                port = 0;
            }
            usr = NULL;
            pwd = NULL;
            jint proxy_type = Proxy_type(proxy);
            if (proxy_type == Proxy_Type_SOCKS) {
                type = HTTP_PROXY_TYPE_SOCKS;
            }
            else if (proxy_type == Proxy_Type_HTTP) {
                type = HTTP_PROXY_TYPE_HTTP;
            }
            else if (proxy_type == Proxy_Type_DIRECT) {
                type = HTTP_PROXY_TYPE_NONE;
            }
            else {
                type = HTTP_PROXY_TYPE_NONE;
            }
            if (type != HTTP_PROXY_TYPE_NONE) {
                if (host) {
                    accounts_index = 0;
                    while ((accounts_index < accounts_length) && !usr && !pwd) {
                        jobject account = JNI_GetObjectArrayElement(accounts, accounts_index);
                        jstring account_name_string = (jstring)(JNI_GetObjectField(account, Account_name_ID));
                        account_name = JNI_GetStringUTFChars(account_name_string, &bool_result);
                        if (strcmp(account_name, host) == 0) {
                            jstring usr_string = (jstring)(AccountManager_getUserData(account_manager, account, username_key));
                            if (usr_string) {
                                const char * u = JNI_GetStringUTFChars(usr_string, &bool_result);
                                usr = _strdup(u);
                                JNI_ReleaseStringUTFChars(usr_string, u);
                                JNI_DeleteLocalRef(usr_string);
                            }
                            jstring pwd_string = (jstring)(AccountManager_getPassword(account_manager, account));
                            if (pwd_string) {
                                const char * p = JNI_GetStringUTFChars(pwd_string, &bool_result);
                                pwd = _strdup(p);
                                JNI_ReleaseStringUTFChars(pwd_string, p);
                                JNI_DeleteLocalRef(pwd_string);
                            }
                        }
                        JNI_ReleaseStringUTFChars(account_name_string, account_name);
                        JNI_DeleteLocalRef(account_name_string);
                        JNI_DeleteLocalRef(account);
                    }
                }
                class HTTPProxy * http_proxy = new class HTTPProxy(_strdup(host), (word) port, type, usr, pwd);
                http_proxies->httpProxies.push_back(http_proxy);
            }
//            debug->printf("  %s:%u %u %s", host, (word) port, type, usr);
            if (socket_address) {
                JNI_ReleaseStringUTFChars(host_string, host);
                JNI_DeleteLocalRef(host_string);
                JNI_DeleteLocalRef(socket_address);
            }
            JNI_DeleteLocalRef(proxy);
        }
        JNI_DeleteLocalRef(proxies);
    }
    JNI_DeleteLocalRef(uri);
    JNI_DeleteLocalRef(url_string);
    if (accounts) JNI_DeleteLocalRef(accounts);
    JNI_DeleteLocalRef(account_type);
    JNI_DeleteLocalRef(username_key);
    if (account_manager) JNI_DeleteLocalRef(account_manager);
#elif defined(_IOS_)
    char host_buf[4096];
    char usr_buf[4096];
    char pwd_buf[4096];
    char pac_url_buf[4096];
    const char * host, * usr, * pwd, * pac_url;
    CFIndex proxies_length, proxy_index, pac_proxies_length, pac_proxy_index;
    CFErrorRef error_ref;
    HTTPProxyType type;
    int i;
    word port;

    CFDictionaryRef proxy_settings = CFNetworkCopySystemProxySettings();
    CFStringRef url_string = CFStringCreateWithCString(NULL, http_url, kCFStringEncodingUTF8);
    CFURLRef url_ref = CFURLCreateWithString(NULL, url_string, NULL);
    CFArrayRef proxies = CFNetworkCopyProxiesForURL(url_ref, proxy_settings);
    if (proxies) {
        proxies_length = CFArrayGetCount(proxies);
        for (proxy_index = 0; proxy_index < proxies_length; proxy_index++) {
            CFDictionaryRef proxy = (CFDictionaryRef)(CFArrayGetValueAtIndex(proxies, proxy_index));
            const CFStringRef type_string = (const CFStringRef)(CFDictionaryGetValue(proxy, kCFProxyTypeKey));
            if (CFEqual(type_string, kCFProxyTypeSOCKS)) {
                type = HTTP_PROXY_TYPE_SOCKS;
            }
            else if (CFEqual(type_string, kCFProxyTypeHTTPS)) {
                type = HTTP_PROXY_TYPE_HTTPS;
            }
            else if (CFEqual(type_string, kCFProxyTypeHTTP)) {
                type = HTTP_PROXY_TYPE_HTTP;
            }
            else if (CFEqual(type_string, kCFProxyTypeFTP)) {
                type = HTTP_PROXY_TYPE_FTP;
            }
            else if (CFEqual(type_string, kCFProxyTypeNone)) {
                type = HTTP_PROXY_TYPE_NONE;
            }
            else {
                if (CFEqual(type_string, kCFProxyTypeAutoConfigurationURL)) {
                    const CFURLRef pac_url_ref = (const CFURLRef)(CFDictionaryGetValue(proxy, kCFProxyAutoConfigurationURLKey));
                    if (pac_url_ref) {
                        error_ref = NULL;
                        const CFStringRef pac_url_string = CFURLGetString(pac_url_ref);
                        pac_url = (pac_url_string && CFStringGetCString(pac_url_string, pac_url_buf, sizeof(pac_url_buf), kCFStringEncodingUTF8)) ? pac_url_buf : NULL;
//                        debug->printf("  pac script %s", pac_url);
                        CFArrayRef pac_proxies = CFNetworkCopyProxiesForAutoConfigurationScript(pac_url_string, url_ref, &error_ref);
                        if (pac_proxies) {
                            pac_proxies_length = CFArrayGetCount(pac_proxies);
                            for (pac_proxy_index = 0; pac_proxy_index < pac_proxies_length; pac_proxy_index++) {
                                CFDictionaryRef pac_proxy = (CFDictionaryRef)(CFArrayGetValueAtIndex(pac_proxies, pac_proxy_index));
                                const CFStringRef type_string = (const CFStringRef)(CFDictionaryGetValue(pac_proxy, kCFProxyTypeKey));
                                if (CFEqual(type_string, kCFProxyTypeSOCKS)) {
                                    type = HTTP_PROXY_TYPE_SOCKS;
                                }
                                else if (CFEqual(type_string, kCFProxyTypeHTTPS)) {
                                    type = HTTP_PROXY_TYPE_HTTPS;
                                }
                                else if (CFEqual(type_string, kCFProxyTypeHTTP)) {
                                    type = HTTP_PROXY_TYPE_HTTP;
                                }
                                else if (CFEqual(type_string, kCFProxyTypeFTP)) {
                                    type = HTTP_PROXY_TYPE_FTP;
                                }
                                else if (CFEqual(type_string, kCFProxyTypeNone)) {
                                    type = HTTP_PROXY_TYPE_NONE;
                                }
                                else {
                                    type = HTTP_PROXY_TYPE_NONE;
                                }
                                const CFStringRef host_string = (const CFStringRef)(CFDictionaryGetValue(pac_proxy, kCFProxyHostNameKey));
                                host = (host_string && CFStringGetCString(host_string, host_buf, sizeof(host_buf), kCFStringEncodingUTF8)) ? host_buf : NULL;
                                const CFNumberRef port_number = (const CFNumberRef)(CFDictionaryGetValue(pac_proxy, kCFProxyPortNumberKey));
                                port = (port_number && CFNumberGetValue(port_number, kCFNumberIntType, &i)) ? (word) i : 0;
                                const CFStringRef usr_string = (const CFStringRef)(CFDictionaryGetValue(pac_proxy, kCFProxyUsernameKey));
                                usr = (usr_string && CFStringGetCString(usr_string, usr_buf, sizeof(usr_buf), kCFStringEncodingUTF8)) ? usr_buf : NULL;
                                const CFStringRef pwd_string = (const CFStringRef)(CFDictionaryGetValue(pac_proxy, kCFProxyPasswordKey));
                                pwd = (pwd_string && CFStringGetCString(pwd_string, pwd_buf, sizeof(pwd_buf), kCFStringEncodingUTF8)) ? pwd_buf : NULL;
//                                debug->printf("  %s:%u %u %s", host, (word) port, type, usr);
                                if ((type != HTTP_PROXY_TYPE_NONE) && host && (host[0] != '\0')) {
                                    class HTTPProxy * http_proxy = new class HTTPProxy(_strdup(host), (word) port, type, _strdup(usr), _strdup(pwd));
                                    http_proxies->httpProxies.push_back(http_proxy);
                                }
                            }
                            CFRelease(pac_proxies);
                        }
                    }
                }
                type = HTTP_PROXY_TYPE_NONE;
            }
            const CFStringRef host_string = (const CFStringRef)(CFDictionaryGetValue(proxy, kCFProxyHostNameKey));
            host = (host_string && CFStringGetCString(host_string, host_buf, sizeof(host_buf), kCFStringEncodingUTF8)) ? host_buf : NULL;
            const CFNumberRef port_number = (const CFNumberRef)(CFDictionaryGetValue(proxy, kCFProxyPortNumberKey));
            port = (port_number && CFNumberGetValue(port_number, kCFNumberIntType, &i)) ? (word) i : 0;
            const CFStringRef usr_string = (const CFStringRef)(CFDictionaryGetValue(proxy, kCFProxyUsernameKey));
            usr = (usr_string && CFStringGetCString(usr_string, usr_buf, sizeof(usr_buf), kCFStringEncodingUTF8)) ? usr_buf : NULL;
            const CFStringRef pwd_string = (const CFStringRef)(CFDictionaryGetValue(proxy, kCFProxyPasswordKey));
            pwd = (pwd_string && CFStringGetCString(pwd_string, pwd_buf, sizeof(pwd_buf), kCFStringEncodingUTF8)) ? pwd_buf : NULL;
//            debug->printf("  %s:%u %u %s", host, (word) port, type, usr);
            if ((type != HTTP_PROXY_TYPE_NONE) && host && (host[0] != '\0')) {
                class HTTPProxy * http_proxy = new class HTTPProxy(_strdup(host), (word) port, type, _strdup(usr), _strdup(pwd));
                http_proxies->httpProxies.push_back(http_proxy);
            }
        }
        CFRelease(proxies);
    }
    CFRelease(proxy_settings);
    CFRelease(url_ref);
    CFRelease(url_string);
#endif
    return http_proxies;
}
