Repository: MaxSmile/EasyVPN-Free Branch: master Commit: 967fab50c2ed Files: 198 Total size: 136.0 MB Directory structure: gitextract_fzgf4p_h/ ├── .gitignore ├── Android-code/ │ ├── .gitignore │ ├── app/ │ │ ├── .gitignore │ │ ├── build.gradle │ │ ├── fabric.properties │ │ ├── google-services.json │ │ ├── proguard-rules.pro │ │ └── src/ │ │ ├── main/ │ │ │ ├── AndroidManifest.xml │ │ │ ├── aidl/ │ │ │ │ └── com/ │ │ │ │ └── android/ │ │ │ │ └── vending/ │ │ │ │ └── billing/ │ │ │ │ └── IInAppBillingService.aidl │ │ │ ├── assets/ │ │ │ │ ├── countries.json │ │ │ │ ├── full_licenses.html │ │ │ │ ├── nopie_openvpn.arm64-v8a │ │ │ │ ├── nopie_openvpn.armeabi │ │ │ │ ├── nopie_openvpn.armeabi-v7a │ │ │ │ ├── nopie_openvpn.mips │ │ │ │ ├── nopie_openvpn.x86 │ │ │ │ ├── nopie_openvpn.x86_64 │ │ │ │ ├── pie_openvpn.arm64-v8a │ │ │ │ ├── pie_openvpn.armeabi │ │ │ │ ├── pie_openvpn.armeabi-v7a │ │ │ │ ├── pie_openvpn.mips │ │ │ │ ├── pie_openvpn.x86 │ │ │ │ ├── pie_openvpn.x86_64 │ │ │ │ └── world_map.geo.json │ │ │ ├── java/ │ │ │ │ ├── com/ │ │ │ │ │ └── vasilkoff/ │ │ │ │ │ └── easyvpnfree/ │ │ │ │ │ ├── App.java │ │ │ │ │ ├── activity/ │ │ │ │ │ │ ├── AboutActivity.java │ │ │ │ │ │ ├── BaseActivity.java │ │ │ │ │ │ ├── BookmarkServerListActivity.java │ │ │ │ │ │ ├── HomeActivity.java │ │ │ │ │ │ ├── LauncherActivity.java │ │ │ │ │ │ ├── LoaderActivity.java │ │ │ │ │ │ ├── MyPreferencesActivity.java │ │ │ │ │ │ ├── ServerActivity.java │ │ │ │ │ │ ├── ServersInfo.java │ │ │ │ │ │ └── ServersListActivity.java │ │ │ │ │ ├── adapter/ │ │ │ │ │ │ ├── BookmarkServerListAdapter.java │ │ │ │ │ │ └── ServerListAdapter.java │ │ │ │ │ ├── database/ │ │ │ │ │ │ └── DBHelper.java │ │ │ │ │ ├── model/ │ │ │ │ │ │ ├── Country.java │ │ │ │ │ │ └── Server.java │ │ │ │ │ └── util/ │ │ │ │ │ ├── BitmapGenerator.java │ │ │ │ │ ├── ConnectionQuality.java │ │ │ │ │ ├── CountriesNames.java │ │ │ │ │ ├── LoadData.java │ │ │ │ │ ├── NetworkState.java │ │ │ │ │ ├── NumberPickerPreference.java │ │ │ │ │ ├── PropertiesService.java │ │ │ │ │ ├── Stopwatch.java │ │ │ │ │ ├── TotalTraffic.java │ │ │ │ │ ├── iap/ │ │ │ │ │ │ ├── Base64.java │ │ │ │ │ │ ├── Base64DecoderException.java │ │ │ │ │ │ ├── IabException.java │ │ │ │ │ │ ├── IabHelper.java │ │ │ │ │ │ ├── IabResult.java │ │ │ │ │ │ ├── Inventory.java │ │ │ │ │ │ ├── Purchase.java │ │ │ │ │ │ ├── Security.java │ │ │ │ │ │ └── SkuDetails.java │ │ │ │ │ └── map/ │ │ │ │ │ ├── MapCreator.java │ │ │ │ │ └── MyMarker.java │ │ │ │ ├── de/ │ │ │ │ │ └── blinkt/ │ │ │ │ │ └── openvpn/ │ │ │ │ │ ├── VpnProfile.java │ │ │ │ │ └── core/ │ │ │ │ │ ├── CIDRIP.java │ │ │ │ │ ├── ConfigParser.java │ │ │ │ │ ├── Connection.java │ │ │ │ │ ├── DeviceStateReceiver.java │ │ │ │ │ ├── ICSOpenVPNApplication.java │ │ │ │ │ ├── LogFileHandler.java │ │ │ │ │ ├── LogItem.java │ │ │ │ │ ├── LollipopDeviceStateListener.java │ │ │ │ │ ├── NativeUtils.java │ │ │ │ │ ├── NetworkSpace.java │ │ │ │ │ ├── OpenVPNManagement.java │ │ │ │ │ ├── OpenVPNService.java │ │ │ │ │ ├── OpenVPNThread.java │ │ │ │ │ ├── OpenVpnManagementThread.java │ │ │ │ │ ├── PRNGFixes.java │ │ │ │ │ ├── ProfileManager.java │ │ │ │ │ ├── ProxyDetection.java │ │ │ │ │ ├── VPNLaunchHelper.java │ │ │ │ │ ├── VpnStatus.java │ │ │ │ │ └── X509Utils.java │ │ │ │ └── org/ │ │ │ │ └── spongycastle/ │ │ │ │ └── util/ │ │ │ │ ├── encoders/ │ │ │ │ │ ├── Base64.java │ │ │ │ │ ├── Base64Encoder.java │ │ │ │ │ └── Encoder.java │ │ │ │ └── io/ │ │ │ │ └── pem/ │ │ │ │ ├── PemGenerationException.java │ │ │ │ ├── PemHeader.java │ │ │ │ ├── PemObject.java │ │ │ │ ├── PemObjectGenerator.java │ │ │ │ ├── PemReader.java │ │ │ │ └── PemWriter.java │ │ │ └── res/ │ │ │ ├── anim/ │ │ │ │ └── scale.xml │ │ │ ├── drawable/ │ │ │ │ ├── button_bg.xml │ │ │ │ ├── connected_bg.xml │ │ │ │ ├── info_servers_bg.xml │ │ │ │ ├── server_info_bg.xml │ │ │ │ └── side_nav_bar.xml │ │ │ ├── layout/ │ │ │ │ ├── activity_about.xml │ │ │ │ ├── activity_base.xml │ │ │ │ ├── activity_bookmark_server_list.xml │ │ │ │ ├── activity_home.xml │ │ │ │ ├── activity_loader.xml │ │ │ │ ├── activity_preference.xml │ │ │ │ ├── activity_server.xml │ │ │ │ ├── activity_servers_info.xml │ │ │ │ ├── activity_servers_list.xml │ │ │ │ ├── bookmark_server_row.xml │ │ │ │ ├── layout_server_record_row.xml │ │ │ │ ├── pop_up_choose_country.xml │ │ │ │ ├── pop_up_note.xml │ │ │ │ ├── pop_up_rating.xml │ │ │ │ └── pop_up_success_conected.xml │ │ │ ├── menu/ │ │ │ │ └── menu_main.xml │ │ │ ├── values/ │ │ │ │ ├── about.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── props.xml │ │ │ │ ├── reference.xml │ │ │ │ ├── strings.xml │ │ │ │ ├── styles.xml │ │ │ │ ├── untranslatable.xml │ │ │ │ └── vpn_strings.xml │ │ │ ├── values-ar/ │ │ │ │ └── strings.xml │ │ │ ├── values-be/ │ │ │ │ └── strings.xml │ │ │ ├── values-cs/ │ │ │ │ └── strings.xml │ │ │ ├── values-de/ │ │ │ │ └── strings.xml │ │ │ ├── values-el/ │ │ │ │ └── strings.xml │ │ │ ├── values-es/ │ │ │ │ └── strings.xml │ │ │ ├── values-fa/ │ │ │ │ └── strings.xml │ │ │ ├── values-fr/ │ │ │ │ └── strings.xml │ │ │ ├── values-hu/ │ │ │ │ └── strings.xml │ │ │ ├── values-id/ │ │ │ │ └── strings.xml │ │ │ ├── values-it/ │ │ │ │ └── strings.xml │ │ │ ├── values-iw/ │ │ │ │ └── strings.xml │ │ │ ├── values-ja/ │ │ │ │ └── strings.xml │ │ │ ├── values-ka/ │ │ │ │ └── strings.xml │ │ │ ├── values-ko/ │ │ │ │ └── strings.xml │ │ │ ├── values-lt/ │ │ │ │ └── strings.xml │ │ │ ├── values-lv/ │ │ │ │ └── strings.xml │ │ │ ├── values-ro/ │ │ │ │ └── strings.xml │ │ │ ├── values-ru/ │ │ │ │ └── strings.xml │ │ │ ├── values-sk/ │ │ │ │ └── strings.xml │ │ │ ├── values-sl/ │ │ │ │ └── strings.xml │ │ │ ├── values-sr/ │ │ │ │ └── strings.xml │ │ │ ├── values-sv/ │ │ │ │ └── strings.xml │ │ │ ├── values-th/ │ │ │ │ └── strings.xml │ │ │ ├── values-tr/ │ │ │ │ └── strings.xml │ │ │ ├── values-uk/ │ │ │ │ └── strings.xml │ │ │ ├── values-v21/ │ │ │ │ ├── reference.xml │ │ │ │ └── styles.xml │ │ │ ├── values-vi/ │ │ │ │ └── strings.xml │ │ │ ├── values-w820dp/ │ │ │ │ └── dimens.xml │ │ │ ├── values-zh/ │ │ │ │ └── strings.xml │ │ │ └── xml/ │ │ │ └── preferences.xml │ │ └── pro/ │ │ └── google-services.json │ ├── build.gradle │ ├── gradle/ │ │ └── wrapper/ │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradle.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── Binary/ │ └── easyvpnfree-1.5-49-free-release.apk ├── LICENSE ├── README.md └── binary/ ├── app-release-1.0-5.apk ├── easyvpnfree-1.0-6-release.apk ├── easyvpnfree-1.0-7-release.apk ├── easyvpnfree-1.1-9-release.apk ├── easyvpnfree-1.2-11-release.apk ├── easyvpnfree-1.2-12-release.apk ├── easyvpnfree-1.2-13-release.apk ├── easyvpnfree-1.3-15-release.apk ├── easyvpnfree-1.3-16-release.apk ├── easyvpnfree-1.3-18-release.apk ├── easyvpnfree-1.3-19-release.apk ├── easyvpnfree-1.3-21-release.apk ├── easyvpnfree-1.3-22-release.apk ├── easyvpnfree-1.3-23-release.apk ├── easyvpnfree-1.3-25-release.apk ├── easyvpnfree-1.4-26-release.apk ├── easyvpnfree-1.4-27-release.apk ├── easyvpnfree-1.4-29-release.apk ├── easyvpnfree-1.4-30-release.apk ├── easyvpnfree-1.4-31-release.apk ├── easyvpnfree-1.4-33-release.apk ├── easyvpnfree-1.4-34-release.apk ├── easyvpnfree-1.4-36-release.apk ├── easyvpnfree-1.4-37-release.apk ├── easyvpnfree-1.4-38-release.apk ├── easyvpnfree-1.4-39-release.apk ├── easyvpnfree-1.4-40-free-release.apk ├── easyvpnfree-1.4-41-free-release.apk ├── easyvpnfree-1.5-42-free-release.apk ├── easyvpnfree-1.5-43-free-release.apk ├── easyvpnfree-1.5-44-free-release.apk ├── easyvpnfree-1.5-45-free-release.apk ├── easyvpnfree-1.5-50-free-release.apk └── easyvpnfree-1.6-50-free-release.apk ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ *.ap_ # Files for the ART/Dalvik VM *.dex # Java class files *.class # Generated files bin/ gen/ out/ # Gradle files .gradle/ build/ # Local configuration file (sdk path, etc) local.properties # Proguard folder generated by Eclipse proguard/ # Log Files *.log # Android Studio Navigation editor temp files .navigation/ # Android Studio captures folder captures/ # Intellij *.iml .idea/workspace.xml # Keystore files *.jks ================================================ FILE: Android-code/.gitignore ================================================ *.iml .gradle .idea /local.properties /.idea/workspace.xml /.idea/libraries .DS_Store /build /captures /app/src/test /app/src/androidTest /app/src/debug/ /app/src/release/ /app/build ================================================ FILE: Android-code/app/.gitignore ================================================ /build ================================================ FILE: Android-code/app/build.gradle ================================================ buildscript { repositories { maven { url 'https://maven.fabric.io/public' } } dependencies { classpath 'io.fabric.tools:gradle:1.+' } } apply plugin: 'com.android.application' apply plugin: 'io.fabric' repositories { maven { url 'https://maven.fabric.io/public' } } android { compileSdkVersion 24 buildToolsVersion "24.0.2" defaultConfig { applicationId "com.vasilkoff.easyvpnfree" minSdkVersion 17 targetSdkVersion 24 versionCode 50 versionName "1.6" setProperty("archivesBaseName", "easyvpnfree-$versionName-$versionCode") } lintOptions { disable 'MissingTranslation' } productFlavors { free { applicationId "com.vasilkoff.easyvpnfree" resValue "string", "app_name", "Easy VPN Free" resValue "string", "notification_title", "Easy VPN Free - %s" } pro { applicationId "com.vasilkoff.easyvpn" resValue "string", "app_name", "Easy VPN" resValue "string", "notification_title", "Easy VPN - %s" } underground { applicationId "com.vasilkoff.easyvpnunderground" resValue "string", "app_name", "Easy VPN" resValue "string", "notification_title", "Easy VPN - %s" } } buildTypes { release { minifyEnabled false shrinkResources false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' // ndk { // //abiFilters "armeabi" "x86" // abiFilters "armeabi-v7a", "armeabi", "arm64-v8a" // includes ARM SO files only, so no x86 SO file // } } } } dependencies { compile fileTree(dir: 'libs', include: ['*.jar']) compile 'com.android.support:appcompat-v7:24.2.1' compile 'com.android.support:support-v4:24.2.1' compile 'com.android.support:design:24.2.1' compile 'com.android.support:recyclerview-v7:24.2.1' compile 'com.android.support:cardview-v7:24.2.1' compile 'com.daimajia.numberprogressbar:library:1.2@aar' compile 'com.amitshekhar.android:android-networking:0.2.0' compile 'com.google.code.gson:gson:2.7' compile 'org.mapsforge:mapsforge-map-android:0.6.1' compile 'com.caverock:androidsvg:1.2.2-beta-1' testCompile 'junit:junit:4.12' compile('com.crashlytics.sdk.android:crashlytics:2.6.5@aar') { transitive = true } compile 'com.google.android.gms:play-services-analytics:10.0.1' } ================================================ FILE: Android-code/app/fabric.properties ================================================ #Contains API Secret used to validate your application. Commit to internal source control; avoid making secret public. #Wed Dec 14 11:21:31 EET 2016 apiSecret=27f37b51b9f4db8a01ed83314a725d24366fde53dbe7c0ea1df76514132181ff ================================================ FILE: Android-code/app/google-services.json ================================================ { "project_info": { "project_number": "1067063466316", "project_id": "easy-vpn-free" }, "client": [ { "client_info": { "mobilesdk_app_id": "1:1067063466316:android:4ff3282175fe4450", "android_client_info": { "package_name": "com.vasilkoff.easyvpnfree" } }, "oauth_client": [], "api_key": [ { "current_key": "AIzaSyDIDrdo6WD-2RE5dNYGV2XIFcc7qhN9A80" } ], "services": { "analytics_service": { "status": 2, "analytics_property": { "tracking_id": "UA-89622148-1" } }, "appinvite_service": { "status": 1, "other_platform_oauth_client": [] }, "ads_service": { "status": 1 } } } ], "configuration_version": "1" } ================================================ FILE: Android-code/app/proguard-rules.pro ================================================ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified # in /Users/Vasilkoff/Library/Android/sdk/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: # If your project uses WebView with JS, uncomment the following # and specify the fully qualified class name to the JavaScript interface # class: #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} ================================================ FILE: Android-code/app/src/main/AndroidManifest.xml ================================================ ================================================ FILE: Android-code/app/src/main/aidl/com/android/vending/billing/IInAppBillingService.aidl ================================================ /* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.vending.billing; import android.os.Bundle; /** * InAppBillingService is the service that provides in-app billing version 3 and beyond. * This service provides the following features: * 1. Provides a new API to get details of in-app items published for the app including * price, type, title and description. * 2. The purchase flow is synchronous and purchase information is available immediately * after it completes. * 3. Purchase information of in-app purchases is maintained within the Google Play system * till the purchase is consumed. * 4. An API to consume a purchase of an inapp item. All purchases of one-time * in-app items are consumable and thereafter can be purchased again. * 5. An API to get current purchases of the user immediately. This will not contain any * consumed purchases. * * All calls will give a response code with the following possible values * RESULT_OK = 0 - success * RESULT_USER_CANCELED = 1 - user pressed back or canceled a dialog * RESULT_BILLING_UNAVAILABLE = 3 - this billing API version is not supported for the type requested * RESULT_ITEM_UNAVAILABLE = 4 - requested SKU is not available for purchase * RESULT_DEVELOPER_ERROR = 5 - invalid arguments provided to the API * RESULT_ERROR = 6 - Fatal error during the API action * RESULT_ITEM_ALREADY_OWNED = 7 - Failure to purchase since item is already owned * RESULT_ITEM_NOT_OWNED = 8 - Failure to consume since item is not owned */ interface IInAppBillingService { /** * Checks support for the requested billing API version, package and in-app type. * Minimum API version supported by this interface is 3. * @param apiVersion the billing version which the app is using * @param packageName the package name of the calling app * @param type type of the in-app item being purchased "inapp" for one-time purchases * and "subs" for subscription. * @return RESULT_OK(0) on success, corresponding result code on failures */ int isBillingSupported(int apiVersion, String packageName, String type); /** * Provides details of a list of SKUs * Given a list of SKUs of a valid type in the skusBundle, this returns a bundle * with a list JSON strings containing the productId, price, title and description. * This API can be called with a maximum of 20 SKUs. * @param apiVersion billing API version that the Third-party is using * @param packageName the package name of the calling app * @param skusBundle bundle containing a StringArrayList of SKUs with key "ITEM_ID_LIST" * @return Bundle containing the following key-value pairs * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on * failure as listed above. * "DETAILS_LIST" with a StringArrayList containing purchase information * in JSON format similar to: * '{ "productId" : "exampleSku", "type" : "inapp", "price" : "$5.00", * "title : "Example Title", "description" : "This is an example description" }' */ Bundle getSkuDetails(int apiVersion, String packageName, String type, in Bundle skusBundle); /** * Returns a pending intent to launch the purchase flow for an in-app item by providing a SKU, * the type, a unique purchase token and an optional developer payload. * @param apiVersion billing API version that the app is using * @param packageName package name of the calling app * @param sku the SKU of the in-app item as published in the developer console * @param type the type of the in-app item ("inapp" for one-time purchases * and "subs" for subscription). * @param developerPayload optional argument to be sent back with the purchase information * @return Bundle containing the following key-value pairs * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on * failure as listed above. * "BUY_INTENT" - PendingIntent to start the purchase flow * * The Pending intent should be launched with startIntentSenderForResult. When purchase flow * has completed, the onActivityResult() will give a resultCode of OK or CANCELED. * If the purchase is successful, the result data will contain the following key-value pairs * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on * failure as listed above. * "INAPP_PURCHASE_DATA" - String in JSON format similar to * '{"orderId":"12999763169054705758.1371079406387615", * "packageName":"com.example.app", * "productId":"exampleSku", * "purchaseTime":1345678900000, * "purchaseToken" : "122333444455555", * "developerPayload":"example developer payload" }' * "INAPP_DATA_SIGNATURE" - String containing the signature of the purchase data that * was signed with the private key of the developer * TODO: change this to app-specific keys. */ Bundle getBuyIntent(int apiVersion, String packageName, String sku, String type, String developerPayload); /** * Returns the current SKUs owned by the user of the type and package name specified along with * purchase information and a signature of the data to be validated. * This will return all SKUs that have been purchased in V3 and managed items purchased using * V1 and V2 that have not been consumed. * @param apiVersion billing API version that the app is using * @param packageName package name of the calling app * @param type the type of the in-app items being requested * ("inapp" for one-time purchases and "subs" for subscription). * @param continuationToken to be set as null for the first call, if the number of owned * skus are too many, a continuationToken is returned in the response bundle. * This method can be called again with the continuation token to get the next set of * owned skus. * @return Bundle containing the following key-value pairs * "RESPONSE_CODE" with int value, RESULT_OK(0) if success, other response codes on * failure as listed above. * "INAPP_PURCHASE_ITEM_LIST" - StringArrayList containing the list of SKUs * "INAPP_PURCHASE_DATA_LIST" - StringArrayList containing the purchase information * "INAPP_DATA_SIGNATURE_LIST"- StringArrayList containing the signatures * of the purchase information * "INAPP_CONTINUATION_TOKEN" - String containing a continuation token for the * next set of in-app purchases. Only set if the * user has more owned skus than the current list. */ Bundle getPurchases(int apiVersion, String packageName, String type, String continuationToken); /** * Consume the last purchase of the given SKU. This will result in this item being removed * from all subsequent responses to getPurchases() and allow re-purchase of this item. * @param apiVersion billing API version that the app is using * @param packageName package name of the calling app * @param purchaseToken token in the purchase information JSON that identifies the purchase * to be consumed * @return 0 if consumption succeeded. Appropriate error values for failures. */ int consumePurchase(int apiVersion, String packageName, String purchaseToken); } ================================================ FILE: Android-code/app/src/main/assets/countries.json ================================================ [{"CountryName":"Somaliland","CapitalName":"Hargeisa","CapitalLatitude":"9.55","CapitalLongitude":"44.050000","CountryCode":"NULL","ContinentName":"Africa"},{"CountryName":"South Georgia and South Sandwich Islands","CapitalName":"King Edward Point","CapitalLatitude":"-54.283333","CapitalLongitude":"-36.500000","CountryCode":"GS","ContinentName":"Antarctica"},{"CountryName":"French Southern and Antarctic Lands","CapitalName":"Port-aux-Français","CapitalLatitude":"-49.35","CapitalLongitude":"70.216667","CountryCode":"TF","ContinentName":"Antarctica"},{"CountryName":"Palestine","CapitalName":"Jerusalem","CapitalLatitude":"31.7731264","CapitalLongitude":"35.36258698","CountryCode":"PS","ContinentName":"Asia"},{"CountryName":"Aland Islands","CapitalName":"Mariehamn","CapitalLatitude":"60.116667","CapitalLongitude":"19.900000","CountryCode":"AX","ContinentName":"Europe"},{"CountryName":"Nauru","CapitalName":"Yaren","CapitalLatitude":"-0.5477","CapitalLongitude":"166.920867","CountryCode":"NR","ContinentName":"Australia"},{"CountryName":"Saint Martin","CapitalName":"Marigot","CapitalLatitude":"18.0731","CapitalLongitude":"-63.082200","CountryCode":"MF","ContinentName":"North America"},{"CountryName":"Tokelau","CapitalName":"Atafu","CapitalLatitude":"-9.166667","CapitalLongitude":"-171.833333","CountryCode":"TK","ContinentName":"Australia"},{"CountryName":"Western Sahara","CapitalName":"El-Aaiún","CapitalLatitude":"27.153611","CapitalLongitude":"-13.203333","CountryCode":"EH","ContinentName":"Africa"},{"CountryName":"Afghanistan","CapitalName":"Kabul","CapitalLatitude":"34.516666666666666","CapitalLongitude":"69.183333","CountryCode":"AF","ContinentName":"Asia"},{"CountryName":"Albania","CapitalName":"Tirana","CapitalLatitude":"41.31666666666667","CapitalLongitude":"19.816667","CountryCode":"AL","ContinentName":"Europe"},{"CountryName":"Algeria","CapitalName":"Algiers","CapitalLatitude":"36.75","CapitalLongitude":"3.050000","CountryCode":"DZ","ContinentName":"Africa"},{"CountryName":"American Samoa","CapitalName":"Pago Pago","CapitalLatitude":"-14.266666666666667","CapitalLongitude":"-170.700000","CountryCode":"AS","ContinentName":"Australia"},{"CountryName":"Andorra","CapitalName":"Andorra la Vella","CapitalLatitude":"42.5","CapitalLongitude":"1.516667","CountryCode":"AD","ContinentName":"Europe"},{"CountryName":"Angola","CapitalName":"Luanda","CapitalLatitude":"-8.833333333333334","CapitalLongitude":"13.216667","CountryCode":"AO","ContinentName":"Africa"},{"CountryName":"Anguilla","CapitalName":"The Valley","CapitalLatitude":"18.216666666666665","CapitalLongitude":"-63.050000","CountryCode":"AI","ContinentName":"North America"},{"CountryName":"Antigua and Barbuda","CapitalName":"Saint John's","CapitalLatitude":"17.116666666666667","CapitalLongitude":"-61.850000","CountryCode":"AG","ContinentName":"North America"},{"CountryName":"Argentina","CapitalName":"Buenos Aires","CapitalLatitude":"-34.583333333333336","CapitalLongitude":"-58.666667","CountryCode":"AR","ContinentName":"South America"},{"CountryName":"Armenia","CapitalName":"Yerevan","CapitalLatitude":"40.166666666666664","CapitalLongitude":"44.500000","CountryCode":"AM","ContinentName":"Europe"},{"CountryName":"Aruba","CapitalName":"Oranjestad","CapitalLatitude":"12.516666666666667","CapitalLongitude":"-70.033333","CountryCode":"AW","ContinentName":"North America"},{"CountryName":"Australia","CapitalName":"Canberra","CapitalLatitude":"-35.266666666666666","CapitalLongitude":"149.133333","CountryCode":"AU","ContinentName":"Australia"},{"CountryName":"Austria","CapitalName":"Vienna","CapitalLatitude":"48.2","CapitalLongitude":"16.366667","CountryCode":"AT","ContinentName":"Europe"},{"CountryName":"Azerbaijan","CapitalName":"Baku","CapitalLatitude":"40.38333333333333","CapitalLongitude":"49.866667","CountryCode":"AZ","ContinentName":"Europe"},{"CountryName":"Bahamas","CapitalName":"Nassau","CapitalLatitude":"25.083333333333332","CapitalLongitude":"-77.350000","CountryCode":"BS","ContinentName":"North America"},{"CountryName":"Bahrain","CapitalName":"Manama","CapitalLatitude":"26.233333333333334","CapitalLongitude":"50.566667","CountryCode":"BH","ContinentName":"Asia"},{"CountryName":"Bangladesh","CapitalName":"Dhaka","CapitalLatitude":"23.716666666666665","CapitalLongitude":"90.400000","CountryCode":"BD","ContinentName":"Asia"},{"CountryName":"Barbados","CapitalName":"Bridgetown","CapitalLatitude":"13.1","CapitalLongitude":"-59.616667","CountryCode":"BB","ContinentName":"North America"},{"CountryName":"Belarus","CapitalName":"Minsk","CapitalLatitude":"53.9","CapitalLongitude":"27.566667","CountryCode":"BY","ContinentName":"Europe"},{"CountryName":"Belgium","CapitalName":"Brussels","CapitalLatitude":"50.833333333333336","CapitalLongitude":"4.333333","CountryCode":"BE","ContinentName":"Europe"},{"CountryName":"Belize","CapitalName":"Belmopan","CapitalLatitude":"17.25","CapitalLongitude":"-88.766667","CountryCode":"BZ","ContinentName":"Central America"},{"CountryName":"Benin","CapitalName":"Porto-Novo","CapitalLatitude":"6.483333333333333","CapitalLongitude":"2.616667","CountryCode":"BJ","ContinentName":"Africa"},{"CountryName":"Bermuda","CapitalName":"Hamilton","CapitalLatitude":"32.28333333333333","CapitalLongitude":"-64.783333","CountryCode":"BM","ContinentName":"North America"},{"CountryName":"Bhutan","CapitalName":"Thimphu","CapitalLatitude":"27.466666666666665","CapitalLongitude":"89.633333","CountryCode":"BT","ContinentName":"Asia"},{"CountryName":"Bolivia","CapitalName":"La Paz","CapitalLatitude":"-16.5","CapitalLongitude":"-68.150000","CountryCode":"BO","ContinentName":"South America"},{"CountryName":"Bosnia and Herzegovina","CapitalName":"Sarajevo","CapitalLatitude":"43.86666666666667","CapitalLongitude":"18.416667","CountryCode":"BA","ContinentName":"Europe"},{"CountryName":"Botswana","CapitalName":"Gaborone","CapitalLatitude":"-24.633333333333333","CapitalLongitude":"25.900000","CountryCode":"BW","ContinentName":"Africa"},{"CountryName":"Brazil","CapitalName":"Brasilia","CapitalLatitude":"-15.783333333333333","CapitalLongitude":"-47.916667","CountryCode":"BR","ContinentName":"South America"},{"CountryName":"British Virgin Islands","CapitalName":"Road Town","CapitalLatitude":"18.416666666666668","CapitalLongitude":"-64.616667","CountryCode":"VG","ContinentName":"North America"},{"CountryName":"Brunei Darussalam","CapitalName":"Bandar Seri Begawan","CapitalLatitude":"4.883333333333333","CapitalLongitude":"114.933333","CountryCode":"BN","ContinentName":"Asia"},{"CountryName":"Bulgaria","CapitalName":"Sofia","CapitalLatitude":"42.68333333333333","CapitalLongitude":"23.316667","CountryCode":"BG","ContinentName":"Europe"},{"CountryName":"Burkina Faso","CapitalName":"Ouagadougou","CapitalLatitude":"12.366666666666667","CapitalLongitude":"-1.516667","CountryCode":"BF","ContinentName":"Africa"},{"CountryName":"Myanmar","CapitalName":"Rangoon","CapitalLatitude":"16.8","CapitalLongitude":"96.150000","CountryCode":"MM","ContinentName":"Asia"},{"CountryName":"Burundi","CapitalName":"Bujumbura","CapitalLatitude":"-3.3666666666666667","CapitalLongitude":"29.350000","CountryCode":"BI","ContinentName":"Africa"},{"CountryName":"Cambodia","CapitalName":"Phnom Penh","CapitalLatitude":"11.55","CapitalLongitude":"104.916667","CountryCode":"KH","ContinentName":"Asia"},{"CountryName":"Cameroon","CapitalName":"Yaounde","CapitalLatitude":"3.8666666666666667","CapitalLongitude":"11.516667","CountryCode":"CM","ContinentName":"Africa"},{"CountryName":"Canada","CapitalName":"Ottawa","CapitalLatitude":"45.416666666666664","CapitalLongitude":"-75.700000","CountryCode":"CA","ContinentName":"Central America"},{"CountryName":"Cape Verde","CapitalName":"Praia","CapitalLatitude":"14.916666666666666","CapitalLongitude":"-23.516667","CountryCode":"CV","ContinentName":"Africa"},{"CountryName":"Cayman Islands","CapitalName":"George Town","CapitalLatitude":"19.3","CapitalLongitude":"-81.383333","CountryCode":"KY","ContinentName":"North America"},{"CountryName":"Central African Republic","CapitalName":"Bangui","CapitalLatitude":"4.366666666666666","CapitalLongitude":"18.583333","CountryCode":"CF","ContinentName":"Africa"},{"CountryName":"Chad","CapitalName":"N'Djamena","CapitalLatitude":"12.1","CapitalLongitude":"15.033333","CountryCode":"TD","ContinentName":"Africa"},{"CountryName":"Chile","CapitalName":"Santiago","CapitalLatitude":"-33.45","CapitalLongitude":"-70.666667","CountryCode":"CL","ContinentName":"South America"},{"CountryName":"China","CapitalName":"Beijing","CapitalLatitude":"39.916666666666664","CapitalLongitude":"116.383333","CountryCode":"CN","ContinentName":"Asia"},{"CountryName":"Christmas Island","CapitalName":"The Settlement","CapitalLatitude":"-10.416666666666666","CapitalLongitude":"105.716667","CountryCode":"CX","ContinentName":"Australia"},{"CountryName":"Cocos Islands","CapitalName":"West Island","CapitalLatitude":"-12.166666666666666","CapitalLongitude":"96.833333","CountryCode":"CC","ContinentName":"Australia"},{"CountryName":"Colombia","CapitalName":"Bogota","CapitalLatitude":"4.6","CapitalLongitude":"-74.083333","CountryCode":"CO","ContinentName":"South America"},{"CountryName":"Comoros","CapitalName":"Moroni","CapitalLatitude":"-11.7","CapitalLongitude":"43.233333","CountryCode":"KM","ContinentName":"Africa"},{"CountryName":"Democratic Republic of the Congo","CapitalName":"Kinshasa","CapitalLatitude":"-4.316666666666666","CapitalLongitude":"15.300000","CountryCode":"CD","ContinentName":"Africa"},{"CountryName":"Republic of Congo","CapitalName":"Brazzaville","CapitalLatitude":"-4.25","CapitalLongitude":"15.283333","CountryCode":"CG","ContinentName":"Africa"},{"CountryName":"Cook Islands","CapitalName":"Avarua","CapitalLatitude":"-21.2","CapitalLongitude":"-159.766667","CountryCode":"CK","ContinentName":"Australia"},{"CountryName":"Costa Rica","CapitalName":"San Jose","CapitalLatitude":"9.933333333333334","CapitalLongitude":"-84.083333","CountryCode":"CR","ContinentName":"Central America"},{"CountryName":"Cote d'Ivoire","CapitalName":"Yamoussoukro","CapitalLatitude":"6.816666666666666","CapitalLongitude":"-5.266667","CountryCode":"CI","ContinentName":"Africa"},{"CountryName":"Croatia","CapitalName":"Zagreb","CapitalLatitude":"45.8","CapitalLongitude":"16.000000","CountryCode":"HR","ContinentName":"Europe"},{"CountryName":"Cuba","CapitalName":"Havana","CapitalLatitude":"23.116666666666667","CapitalLongitude":"-82.350000","CountryCode":"CU","ContinentName":"North America"},{"CountryName":"Curaçao","CapitalName":"Willemstad","CapitalLatitude":"12.1","CapitalLongitude":"-68.916667","CountryCode":"CW","ContinentName":"North America"},{"CountryName":"Cyprus","CapitalName":"Nicosia","CapitalLatitude":"35.166666666666664","CapitalLongitude":"33.366667","CountryCode":"CY","ContinentName":"Europe"},{"CountryName":"Czech Republic","CapitalName":"Prague","CapitalLatitude":"50.083333333333336","CapitalLongitude":"14.466667","CountryCode":"CZ","ContinentName":"Europe"},{"CountryName":"Denmark","CapitalName":"Copenhagen","CapitalLatitude":"55.666666666666664","CapitalLongitude":"12.583333","CountryCode":"DK","ContinentName":"Europe"},{"CountryName":"Djibouti","CapitalName":"Djibouti","CapitalLatitude":"11.583333333333334","CapitalLongitude":"43.150000","CountryCode":"DJ","ContinentName":"Africa"},{"CountryName":"Dominica","CapitalName":"Roseau","CapitalLatitude":"15.3","CapitalLongitude":"-61.400000","CountryCode":"DM","ContinentName":"North America"},{"CountryName":"Dominican Republic","CapitalName":"Santo Domingo","CapitalLatitude":"18.466666666666665","CapitalLongitude":"-69.900000","CountryCode":"DO","ContinentName":"North America"},{"CountryName":"Ecuador","CapitalName":"Quito","CapitalLatitude":"-0.21666666666666667","CapitalLongitude":"-78.500000","CountryCode":"EC","ContinentName":"South America"},{"CountryName":"Egypt","CapitalName":"Cairo","CapitalLatitude":"30.05","CapitalLongitude":"31.250000","CountryCode":"EG","ContinentName":"Africa"},{"CountryName":"El Salvador","CapitalName":"San Salvador","CapitalLatitude":"13.7","CapitalLongitude":"-89.200000","CountryCode":"SV","ContinentName":"Central America"},{"CountryName":"Equatorial Guinea","CapitalName":"Malabo","CapitalLatitude":"3.75","CapitalLongitude":"8.783333","CountryCode":"GQ","ContinentName":"Africa"},{"CountryName":"Eritrea","CapitalName":"Asmara","CapitalLatitude":"15.333333333333334","CapitalLongitude":"38.933333","CountryCode":"ER","ContinentName":"Africa"},{"CountryName":"Estonia","CapitalName":"Tallinn","CapitalLatitude":"59.43333333333333","CapitalLongitude":"24.716667","CountryCode":"EE","ContinentName":"Europe"},{"CountryName":"Ethiopia","CapitalName":"Addis Ababa","CapitalLatitude":"9.033333333333333","CapitalLongitude":"38.700000","CountryCode":"ET","ContinentName":"Africa"},{"CountryName":"Falkland Islands","CapitalName":"Stanley","CapitalLatitude":"-51.7","CapitalLongitude":"-57.850000","CountryCode":"FK","ContinentName":"South America"},{"CountryName":"Faroe Islands","CapitalName":"Torshavn","CapitalLatitude":"62","CapitalLongitude":"-6.766667","CountryCode":"FO","ContinentName":"Europe"},{"CountryName":"Fiji","CapitalName":"Suva","CapitalLatitude":"-18.133333333333333","CapitalLongitude":"178.416667","CountryCode":"FJ","ContinentName":"Australia"},{"CountryName":"Finland","CapitalName":"Helsinki","CapitalLatitude":"60.166666666666664","CapitalLongitude":"24.933333","CountryCode":"FI","ContinentName":"Europe"},{"CountryName":"France","CapitalName":"Paris","CapitalLatitude":"48.86666666666667","CapitalLongitude":"2.333333","CountryCode":"FR","ContinentName":"Europe"},{"CountryName":"French Polynesia","CapitalName":"Papeete","CapitalLatitude":"-17.533333333333335","CapitalLongitude":"-149.566667","CountryCode":"PF","ContinentName":"Australia"},{"CountryName":"Gabon","CapitalName":"Libreville","CapitalLatitude":"0.38333333333333336","CapitalLongitude":"9.450000","CountryCode":"GA","ContinentName":"Africa"},{"CountryName":"The Gambia","CapitalName":"Banjul","CapitalLatitude":"13.45","CapitalLongitude":"-16.566667","CountryCode":"GM","ContinentName":"Africa"},{"CountryName":"Georgia","CapitalName":"Tbilisi","CapitalLatitude":"41.68333333333333","CapitalLongitude":"44.833333","CountryCode":"GE","ContinentName":"Europe"},{"CountryName":"Germany","CapitalName":"Berlin","CapitalLatitude":"52.516666666666666","CapitalLongitude":"13.400000","CountryCode":"DE","ContinentName":"Europe"},{"CountryName":"Ghana","CapitalName":"Accra","CapitalLatitude":"5.55","CapitalLongitude":"-0.216667","CountryCode":"GH","ContinentName":"Africa"},{"CountryName":"Gibraltar","CapitalName":"Gibraltar","CapitalLatitude":"36.13333333333333","CapitalLongitude":"-5.350000","CountryCode":"GI","ContinentName":"Europe"},{"CountryName":"Greece","CapitalName":"Athens","CapitalLatitude":"37.983333333333334","CapitalLongitude":"23.733333","CountryCode":"GR","ContinentName":"Europe"},{"CountryName":"Greenland","CapitalName":"Nuuk","CapitalLatitude":"64.18333333333334","CapitalLongitude":"-51.750000","CountryCode":"GL","ContinentName":"Central America"},{"CountryName":"Grenada","CapitalName":"Saint George's","CapitalLatitude":"12.05","CapitalLongitude":"-61.750000","CountryCode":"GD","ContinentName":"North America"},{"CountryName":"Guam","CapitalName":"Hagatna","CapitalLatitude":"13.466666666666667","CapitalLongitude":"144.733333","CountryCode":"GU","ContinentName":"Australia"},{"CountryName":"Guatemala","CapitalName":"Guatemala City","CapitalLatitude":"14.616666666666667","CapitalLongitude":"-90.516667","CountryCode":"GT","ContinentName":"Central America"},{"CountryName":"Guernsey","CapitalName":"Saint Peter Port","CapitalLatitude":"49.45","CapitalLongitude":"-2.533333","CountryCode":"GG","ContinentName":"Europe"},{"CountryName":"Guinea","CapitalName":"Conakry","CapitalLatitude":"9.5","CapitalLongitude":"-13.700000","CountryCode":"GN","ContinentName":"Africa"},{"CountryName":"Guinea-Bissau","CapitalName":"Bissau","CapitalLatitude":"11.85","CapitalLongitude":"-15.583333","CountryCode":"GW","ContinentName":"Africa"},{"CountryName":"Guyana","CapitalName":"Georgetown","CapitalLatitude":"6.8","CapitalLongitude":"-58.150000","CountryCode":"GY","ContinentName":"South America"},{"CountryName":"Haiti","CapitalName":"Port-au-Prince","CapitalLatitude":"18.533333333333335","CapitalLongitude":"-72.333333","CountryCode":"HT","ContinentName":"North America"},{"CountryName":"Vatican City","CapitalName":"Vatican City","CapitalLatitude":"41.9","CapitalLongitude":"12.450000","CountryCode":"VA","ContinentName":"Europe"},{"CountryName":"Honduras","CapitalName":"Tegucigalpa","CapitalLatitude":"14.1","CapitalLongitude":"-87.216667","CountryCode":"HN","ContinentName":"Central America"},{"CountryName":"Hungary","CapitalName":"Budapest","CapitalLatitude":"47.5","CapitalLongitude":"19.083333","CountryCode":"HU","ContinentName":"Europe"},{"CountryName":"Iceland","CapitalName":"Reykjavik","CapitalLatitude":"64.15","CapitalLongitude":"-21.950000","CountryCode":"IS","ContinentName":"Europe"},{"CountryName":"India","CapitalName":"New Delhi","CapitalLatitude":"28.6","CapitalLongitude":"77.200000","CountryCode":"IN","ContinentName":"Asia"},{"CountryName":"Indonesia","CapitalName":"Jakarta","CapitalLatitude":"-6.166666666666667","CapitalLongitude":"106.816667","CountryCode":"ID","ContinentName":"Asia"},{"CountryName":"Iran","CapitalName":"Tehran","CapitalLatitude":"35.7","CapitalLongitude":"51.416667","CountryCode":"IR","ContinentName":"Asia"},{"CountryName":"Iraq","CapitalName":"Baghdad","CapitalLatitude":"33.333333333333336","CapitalLongitude":"44.400000","CountryCode":"IQ","ContinentName":"Asia"},{"CountryName":"Ireland","CapitalName":"Dublin","CapitalLatitude":"53.31666666666667","CapitalLongitude":"-6.233333","CountryCode":"IE","ContinentName":"Europe"},{"CountryName":"Isle of Man","CapitalName":"Douglas","CapitalLatitude":"54.15","CapitalLongitude":"-4.483333","CountryCode":"IM","ContinentName":"Europe"},{"CountryName":"Israel","CapitalName":"Jerusalem","CapitalLatitude":"31.76787255","CapitalLongitude":"35.0220108","CountryCode":"IL","ContinentName":"Asia"},{"CountryName":"Italy","CapitalName":"Rome","CapitalLatitude":"41.9","CapitalLongitude":"12.483333","CountryCode":"IT","ContinentName":"Europe"},{"CountryName":"Jamaica","CapitalName":"Kingston","CapitalLatitude":"18","CapitalLongitude":"-76.800000","CountryCode":"JM","ContinentName":"North America"},{"CountryName":"Japan","CapitalName":"Tokyo","CapitalLatitude":"35.68333333333333","CapitalLongitude":"139.750000","CountryCode":"JP","ContinentName":"Asia"},{"CountryName":"Jersey","CapitalName":"Saint Helier","CapitalLatitude":"49.18333333333333","CapitalLongitude":"-2.100000","CountryCode":"JE","ContinentName":"Europe"},{"CountryName":"Jordan","CapitalName":"Amman","CapitalLatitude":"31.95","CapitalLongitude":"35.933333","CountryCode":"JO","ContinentName":"Asia"},{"CountryName":"Kazakhstan","CapitalName":"Astana","CapitalLatitude":"51.166666666666664","CapitalLongitude":"71.416667","CountryCode":"KZ","ContinentName":"Asia"},{"CountryName":"Kenya","CapitalName":"Nairobi","CapitalLatitude":"-1.2833333333333332","CapitalLongitude":"36.816667","CountryCode":"KE","ContinentName":"Africa"},{"CountryName":"Kiribati","CapitalName":"Tarawa","CapitalLatitude":"-0.8833333333333333","CapitalLongitude":"169.533333","CountryCode":"KI","ContinentName":"Australia"},{"CountryName":"North Korea","CapitalName":"Pyongyang","CapitalLatitude":"39.016666666666666","CapitalLongitude":"125.750000","CountryCode":"KP","ContinentName":"Asia"},{"CountryName":"South Korea","CapitalName":"Seoul","CapitalLatitude":"37.55","CapitalLongitude":"126.983333","CountryCode":"KR","ContinentName":"Asia"},{"CountryName":"Kosovo","CapitalName":"Pristina","CapitalLatitude":"42.666666666666664","CapitalLongitude":"21.166667","CountryCode":"KO","ContinentName":"Europe"},{"CountryName":"Kuwait","CapitalName":"Kuwait City","CapitalLatitude":"29.366666666666667","CapitalLongitude":"47.966667","CountryCode":"KW","ContinentName":"Asia"},{"CountryName":"Kyrgyzstan","CapitalName":"Bishkek","CapitalLatitude":"42.86666666666667","CapitalLongitude":"74.600000","CountryCode":"KG","ContinentName":"Asia"},{"CountryName":"Laos","CapitalName":"Vientiane","CapitalLatitude":"17.966666666666665","CapitalLongitude":"102.600000","CountryCode":"LA","ContinentName":"Asia"},{"CountryName":"Latvia","CapitalName":"Riga","CapitalLatitude":"56.95","CapitalLongitude":"24.100000","CountryCode":"LV","ContinentName":"Europe"},{"CountryName":"Lebanon","CapitalName":"Beirut","CapitalLatitude":"33.86666666666667","CapitalLongitude":"35.500000","CountryCode":"LB","ContinentName":"Asia"},{"CountryName":"Lesotho","CapitalName":"Maseru","CapitalLatitude":"-29.316666666666666","CapitalLongitude":"27.483333","CountryCode":"LS","ContinentName":"Africa"},{"CountryName":"Liberia","CapitalName":"Monrovia","CapitalLatitude":"6.3","CapitalLongitude":"-10.800000","CountryCode":"LR","ContinentName":"Africa"},{"CountryName":"Libya","CapitalName":"Tripoli","CapitalLatitude":"32.88333333333333","CapitalLongitude":"13.166667","CountryCode":"LY","ContinentName":"Africa"},{"CountryName":"Liechtenstein","CapitalName":"Vaduz","CapitalLatitude":"47.13333333333333","CapitalLongitude":"9.516667","CountryCode":"LI","ContinentName":"Europe"},{"CountryName":"Lithuania","CapitalName":"Vilnius","CapitalLatitude":"54.68333333333333","CapitalLongitude":"25.316667","CountryCode":"LT","ContinentName":"Europe"},{"CountryName":"Luxembourg","CapitalName":"Luxembourg","CapitalLatitude":"49.6","CapitalLongitude":"6.116667","CountryCode":"LU","ContinentName":"Europe"},{"CountryName":"Macedonia","CapitalName":"Skopje","CapitalLatitude":"42","CapitalLongitude":"21.433333","CountryCode":"MK","ContinentName":"Europe"},{"CountryName":"Madagascar","CapitalName":"Antananarivo","CapitalLatitude":"-18.916666666666668","CapitalLongitude":"47.516667","CountryCode":"MG","ContinentName":"Africa"},{"CountryName":"Malawi","CapitalName":"Lilongwe","CapitalLatitude":"-13.966666666666667","CapitalLongitude":"33.783333","CountryCode":"MW","ContinentName":"Africa"},{"CountryName":"Malaysia","CapitalName":"Kuala Lumpur","CapitalLatitude":"3.1666666666666665","CapitalLongitude":"101.700000","CountryCode":"MY","ContinentName":"Asia"},{"CountryName":"Maldives","CapitalName":"Male","CapitalLatitude":"4.166666666666667","CapitalLongitude":"73.500000","CountryCode":"MV","ContinentName":"Asia"},{"CountryName":"Mali","CapitalName":"Bamako","CapitalLatitude":"12.65","CapitalLongitude":"-8.000000","CountryCode":"ML","ContinentName":"Africa"},{"CountryName":"Malta","CapitalName":"Valletta","CapitalLatitude":"35.88333333333333","CapitalLongitude":"14.500000","CountryCode":"MT","ContinentName":"Europe"},{"CountryName":"Marshall Islands","CapitalName":"Majuro","CapitalLatitude":"7.1","CapitalLongitude":"171.383333","CountryCode":"MH","ContinentName":"Australia"},{"CountryName":"Mauritania","CapitalName":"Nouakchott","CapitalLatitude":"18.066666666666666","CapitalLongitude":"-15.966667","CountryCode":"MR","ContinentName":"Africa"},{"CountryName":"Mauritius","CapitalName":"Port Louis","CapitalLatitude":"-20.15","CapitalLongitude":"57.483333","CountryCode":"MU","ContinentName":"Africa"},{"CountryName":"Mexico","CapitalName":"Mexico City","CapitalLatitude":"19.433333333333334","CapitalLongitude":"-99.133333","CountryCode":"MX","ContinentName":"Central America"},{"CountryName":"Federated States of Micronesia","CapitalName":"Palikir","CapitalLatitude":"6.916666666666667","CapitalLongitude":"158.150000","CountryCode":"FM","ContinentName":"Australia"},{"CountryName":"Moldova","CapitalName":"Chisinau","CapitalLatitude":"47","CapitalLongitude":"28.850000","CountryCode":"MD","ContinentName":"Europe"},{"CountryName":"Monaco","CapitalName":"Monaco","CapitalLatitude":"43.733333333333334","CapitalLongitude":"7.416667","CountryCode":"MC","ContinentName":"Europe"},{"CountryName":"Mongolia","CapitalName":"Ulaanbaatar","CapitalLatitude":"47.916666666666664","CapitalLongitude":"106.916667","CountryCode":"MN","ContinentName":"Asia"},{"CountryName":"Montenegro","CapitalName":"Podgorica","CapitalLatitude":"42.43333333333333","CapitalLongitude":"19.266667","CountryCode":"ME","ContinentName":"Europe"},{"CountryName":"Montserrat","CapitalName":"Plymouth","CapitalLatitude":"16.7","CapitalLongitude":"-62.216667","CountryCode":"MS","ContinentName":"North America"},{"CountryName":"Morocco","CapitalName":"Rabat","CapitalLatitude":"34.016666666666666","CapitalLongitude":"-6.816667","CountryCode":"MA","ContinentName":"Africa"},{"CountryName":"Mozambique","CapitalName":"Maputo","CapitalLatitude":"-25.95","CapitalLongitude":"32.583333","CountryCode":"MZ","ContinentName":"Africa"},{"CountryName":"Namibia","CapitalName":"Windhoek","CapitalLatitude":"-22.566666666666666","CapitalLongitude":"17.083333","CountryCode":"NA","ContinentName":"Africa"},{"CountryName":"Nepal","CapitalName":"Kathmandu","CapitalLatitude":"27.716666666666665","CapitalLongitude":"85.316667","CountryCode":"NP","ContinentName":"Asia"},{"CountryName":"Netherlands","CapitalName":"Amsterdam","CapitalLatitude":"52.35","CapitalLongitude":"4.916667","CountryCode":"NL","ContinentName":"Europe"},{"CountryName":"New Caledonia","CapitalName":"Noumea","CapitalLatitude":"-22.266666666666666","CapitalLongitude":"166.450000","CountryCode":"NC","ContinentName":"Australia"},{"CountryName":"New Zealand","CapitalName":"Wellington","CapitalLatitude":"-41.3","CapitalLongitude":"174.783333","CountryCode":"NZ","ContinentName":"Australia"},{"CountryName":"Nicaragua","CapitalName":"Managua","CapitalLatitude":"12.133333333333333","CapitalLongitude":"-86.250000","CountryCode":"NI","ContinentName":"Central America"},{"CountryName":"Niger","CapitalName":"Niamey","CapitalLatitude":"13.516666666666667","CapitalLongitude":"2.116667","CountryCode":"NE","ContinentName":"Africa"},{"CountryName":"Nigeria","CapitalName":"Abuja","CapitalLatitude":"9.083333333333334","CapitalLongitude":"7.533333","CountryCode":"NG","ContinentName":"Africa"},{"CountryName":"Niue","CapitalName":"Alofi","CapitalLatitude":"-19.016666666666666","CapitalLongitude":"-169.916667","CountryCode":"NU","ContinentName":"Australia"},{"CountryName":"Norfolk Island","CapitalName":"Kingston","CapitalLatitude":"-29.05","CapitalLongitude":"167.966667","CountryCode":"NF","ContinentName":"Australia"},{"CountryName":"Northern Mariana Islands","CapitalName":"Saipan","CapitalLatitude":"15.2","CapitalLongitude":"145.750000","CountryCode":"MP","ContinentName":"Australia"},{"CountryName":"Norway","CapitalName":"Oslo","CapitalLatitude":"59.916666666666664","CapitalLongitude":"10.750000","CountryCode":"NO","ContinentName":"Europe"},{"CountryName":"Oman","CapitalName":"Muscat","CapitalLatitude":"23.616666666666667","CapitalLongitude":"58.583333","CountryCode":"OM","ContinentName":"Asia"},{"CountryName":"Pakistan","CapitalName":"Islamabad","CapitalLatitude":"33.68333333333333","CapitalLongitude":"73.050000","CountryCode":"PK","ContinentName":"Asia"},{"CountryName":"Palau","CapitalName":"Melekeok","CapitalLatitude":"7.483333333333333","CapitalLongitude":"134.633333","CountryCode":"PW","ContinentName":"Australia"},{"CountryName":"Panama","CapitalName":"Panama City","CapitalLatitude":"8.966666666666667","CapitalLongitude":"-79.533333","CountryCode":"PA","ContinentName":"Central America"},{"CountryName":"Papua New Guinea","CapitalName":"Port Moresby","CapitalLatitude":"-9.45","CapitalLongitude":"147.183333","CountryCode":"PG","ContinentName":"Australia"},{"CountryName":"Paraguay","CapitalName":"Asuncion","CapitalLatitude":"-25.266666666666666","CapitalLongitude":"-57.666667","CountryCode":"PY","ContinentName":"South America"},{"CountryName":"Peru","CapitalName":"Lima","CapitalLatitude":"-12.05","CapitalLongitude":"-77.050000","CountryCode":"PE","ContinentName":"South America"},{"CountryName":"Philippines","CapitalName":"Manila","CapitalLatitude":"14.6","CapitalLongitude":"120.966667","CountryCode":"PH","ContinentName":"Asia"},{"CountryName":"Pitcairn Islands","CapitalName":"Adamstown","CapitalLatitude":"-25.066666666666666","CapitalLongitude":"-130.083333","CountryCode":"PN","ContinentName":"Australia"},{"CountryName":"Poland","CapitalName":"Warsaw","CapitalLatitude":"52.25","CapitalLongitude":"21.000000","CountryCode":"PL","ContinentName":"Europe"},{"CountryName":"Portugal","CapitalName":"Lisbon","CapitalLatitude":"38.71666666666667","CapitalLongitude":"-9.133333","CountryCode":"PT","ContinentName":"Europe"},{"CountryName":"Puerto Rico","CapitalName":"San Juan","CapitalLatitude":"18.466666666666665","CapitalLongitude":"-66.116667","CountryCode":"PR","ContinentName":"North America"},{"CountryName":"Qatar","CapitalName":"Doha","CapitalLatitude":"25.283333333333335","CapitalLongitude":"51.533333","CountryCode":"QA","ContinentName":"Asia"},{"CountryName":"Romania","CapitalName":"Bucharest","CapitalLatitude":"44.43333333333333","CapitalLongitude":"26.100000","CountryCode":"RO","ContinentName":"Europe"},{"CountryName":"Russia","CapitalName":"Moscow","CapitalLatitude":"55.75","CapitalLongitude":"37.600000","CountryCode":"RU","ContinentName":"Europe"},{"CountryName":"Rwanda","CapitalName":"Kigali","CapitalLatitude":"-1.95","CapitalLongitude":"30.050000","CountryCode":"RW","ContinentName":"Africa"},{"CountryName":"Saint Barthelemy","CapitalName":"Gustavia","CapitalLatitude":"17.883333333333333","CapitalLongitude":"-62.850000","CountryCode":"BL","ContinentName":"North America"},{"CountryName":"Saint Helena","CapitalName":"Jamestown","CapitalLatitude":"-15.933333333333334","CapitalLongitude":"-5.716667","CountryCode":"SH","ContinentName":"Africa"},{"CountryName":"Saint Kitts and Nevis","CapitalName":"Basseterre","CapitalLatitude":"17.3","CapitalLongitude":"-62.716667","CountryCode":"KN","ContinentName":"North America"},{"CountryName":"Saint Lucia","CapitalName":"Castries","CapitalLatitude":"14","CapitalLongitude":"-61.000000","CountryCode":"LC","ContinentName":"North America"},{"CountryName":"Saint Pierre and Miquelon","CapitalName":"Saint-Pierre","CapitalLatitude":"46.766666666666666","CapitalLongitude":"-56.183333","CountryCode":"PM","ContinentName":"Central America"},{"CountryName":"Saint Vincent and the Grenadines","CapitalName":"Kingstown","CapitalLatitude":"13.133333333333333","CapitalLongitude":"-61.216667","CountryCode":"VC","ContinentName":"Central America"},{"CountryName":"Samoa","CapitalName":"Apia","CapitalLatitude":"-13.816666666666666","CapitalLongitude":"-171.766667","CountryCode":"WS","ContinentName":"Australia"},{"CountryName":"San Marino","CapitalName":"San Marino","CapitalLatitude":"43.93333333333333","CapitalLongitude":"12.416667","CountryCode":"SM","ContinentName":"Europe"},{"CountryName":"Sao Tome and Principe","CapitalName":"Sao Tome","CapitalLatitude":"0.3333333333333333","CapitalLongitude":"6.733333","CountryCode":"ST","ContinentName":"Africa"},{"CountryName":"Saudi Arabia","CapitalName":"Riyadh","CapitalLatitude":"24.65","CapitalLongitude":"46.700000","CountryCode":"SA","ContinentName":"Asia"},{"CountryName":"Senegal","CapitalName":"Dakar","CapitalLatitude":"14.733333333333333","CapitalLongitude":"-17.633333","CountryCode":"SN","ContinentName":"Africa"},{"CountryName":"Serbia","CapitalName":"Belgrade","CapitalLatitude":"44.833333333333336","CapitalLongitude":"20.500000","CountryCode":"RS","ContinentName":"Europe"},{"CountryName":"Seychelles","CapitalName":"Victoria","CapitalLatitude":"-4.616666666666667","CapitalLongitude":"55.450000","CountryCode":"SC","ContinentName":"Africa"},{"CountryName":"Sierra Leone","CapitalName":"Freetown","CapitalLatitude":"8.483333333333333","CapitalLongitude":"-13.233333","CountryCode":"SL","ContinentName":"Africa"},{"CountryName":"Singapore","CapitalName":"Singapore","CapitalLatitude":"1.2833333333333332","CapitalLongitude":"103.850000","CountryCode":"SG","ContinentName":"Asia"},{"CountryName":"Sint Maarten","CapitalName":"Philipsburg","CapitalLatitude":"18.016666666666666","CapitalLongitude":"-63.033333","CountryCode":"SX","ContinentName":"North America"},{"CountryName":"Slovakia","CapitalName":"Bratislava","CapitalLatitude":"48.15","CapitalLongitude":"17.116667","CountryCode":"SK","ContinentName":"Europe"},{"CountryName":"Slovenia","CapitalName":"Ljubljana","CapitalLatitude":"46.05","CapitalLongitude":"14.516667","CountryCode":"SI","ContinentName":"Europe"},{"CountryName":"Solomon Islands","CapitalName":"Honiara","CapitalLatitude":"-9.433333333333334","CapitalLongitude":"159.950000","CountryCode":"SB","ContinentName":"Australia"},{"CountryName":"Somalia","CapitalName":"Mogadishu","CapitalLatitude":"2.066666666666667","CapitalLongitude":"45.333333","CountryCode":"SO","ContinentName":"Africa"},{"CountryName":"South Africa","CapitalName":"Pretoria","CapitalLatitude":"-25.7","CapitalLongitude":"28.216667","CountryCode":"ZA","ContinentName":"Africa"},{"CountryName":"South Sudan","CapitalName":"Juba","CapitalLatitude":"4.85","CapitalLongitude":"31.616667","CountryCode":"SS","ContinentName":"Africa"},{"CountryName":"Spain","CapitalName":"Madrid","CapitalLatitude":"40.4","CapitalLongitude":"-3.683333","CountryCode":"ES","ContinentName":"Europe"},{"CountryName":"Sri Lanka","CapitalName":"Colombo","CapitalLatitude":"6.916666666666667","CapitalLongitude":"79.833333","CountryCode":"LK","ContinentName":"Asia"},{"CountryName":"Sudan","CapitalName":"Khartoum","CapitalLatitude":"15.6","CapitalLongitude":"32.533333","CountryCode":"SD","ContinentName":"Africa"},{"CountryName":"Suriname","CapitalName":"Paramaribo","CapitalLatitude":"5.833333333333333","CapitalLongitude":"-55.166667","CountryCode":"SR","ContinentName":"South America"},{"CountryName":"Svalbard","CapitalName":"Longyearbyen","CapitalLatitude":"78.21666666666667","CapitalLongitude":"15.633333","CountryCode":"SJ","ContinentName":"Europe"},{"CountryName":"Swaziland","CapitalName":"Mbabane","CapitalLatitude":"-26.316666666666666","CapitalLongitude":"31.133333","CountryCode":"SZ","ContinentName":"Africa"},{"CountryName":"Sweden","CapitalName":"Stockholm","CapitalLatitude":"59.333333333333336","CapitalLongitude":"18.050000","CountryCode":"SE","ContinentName":"Europe"},{"CountryName":"Switzerland","CapitalName":"Bern","CapitalLatitude":"46.916666666666664","CapitalLongitude":"7.466667","CountryCode":"CH","ContinentName":"Europe"},{"CountryName":"Syria","CapitalName":"Damascus","CapitalLatitude":"33.5","CapitalLongitude":"36.300000","CountryCode":"SY","ContinentName":"Asia"},{"CountryName":"Taiwan","CapitalName":"Taipei","CapitalLatitude":"25.033333333333335","CapitalLongitude":"121.516667","CountryCode":"TW","ContinentName":"Asia"},{"CountryName":"Tajikistan","CapitalName":"Dushanbe","CapitalLatitude":"38.55","CapitalLongitude":"68.766667","CountryCode":"TJ","ContinentName":"Asia"},{"CountryName":"Tanzania","CapitalName":"Dar es Salaam","CapitalLatitude":"-6.8","CapitalLongitude":"39.283333","CountryCode":"TZ","ContinentName":"Africa"},{"CountryName":"Thailand","CapitalName":"Bangkok","CapitalLatitude":"13.75","CapitalLongitude":"100.516667","CountryCode":"TH","ContinentName":"Asia"},{"CountryName":"Timor-Leste","CapitalName":"Dili","CapitalLatitude":"-8.583333333333334","CapitalLongitude":"125.600000","CountryCode":"TL","ContinentName":"Asia"},{"CountryName":"Togo","CapitalName":"Lome","CapitalLatitude":"6.116666666666666","CapitalLongitude":"1.216667","CountryCode":"TG","ContinentName":"Africa"},{"CountryName":"Tonga","CapitalName":"Nuku'alofa","CapitalLatitude":"-21.133333333333333","CapitalLongitude":"-175.200000","CountryCode":"TO","ContinentName":"Australia"},{"CountryName":"Trinidad and Tobago","CapitalName":"Port of Spain","CapitalLatitude":"10.65","CapitalLongitude":"-61.516667","CountryCode":"TT","ContinentName":"North America"},{"CountryName":"Tunisia","CapitalName":"Tunis","CapitalLatitude":"36.8","CapitalLongitude":"10.183333","CountryCode":"TN","ContinentName":"Africa"},{"CountryName":"Turkey","CapitalName":"Ankara","CapitalLatitude":"39.93333333333333","CapitalLongitude":"32.866667","CountryCode":"TR","ContinentName":"Europe"},{"CountryName":"Turkmenistan","CapitalName":"Ashgabat","CapitalLatitude":"37.95","CapitalLongitude":"58.383333","CountryCode":"TM","ContinentName":"Asia"},{"CountryName":"Turks and Caicos Islands","CapitalName":"Grand Turk","CapitalLatitude":"21.466666666666665","CapitalLongitude":"-71.133333","CountryCode":"TC","ContinentName":"North America"},{"CountryName":"Tuvalu","CapitalName":"Funafuti","CapitalLatitude":"-8.516666666666667","CapitalLongitude":"179.216667","CountryCode":"TV","ContinentName":"Australia"},{"CountryName":"Uganda","CapitalName":"Kampala","CapitalLatitude":"0.31666666666666665","CapitalLongitude":"32.550000","CountryCode":"UG","ContinentName":"Africa"},{"CountryName":"Ukraine","CapitalName":"Kyiv","CapitalLatitude":"50.43333333333333","CapitalLongitude":"30.516667","CountryCode":"UA","ContinentName":"Europe"},{"CountryName":"United Arab Emirates","CapitalName":"Abu Dhabi","CapitalLatitude":"24.466666666666665","CapitalLongitude":"54.366667","CountryCode":"AE","ContinentName":"Asia"},{"CountryName":"United Kingdom","CapitalName":"London","CapitalLatitude":"51.5","CapitalLongitude":"-0.083333","CountryCode":"GB","ContinentName":"Europe"},{"CountryName":"United States","CapitalName":"Washington","CapitalLatitude":"38.883333","CapitalLongitude":"-77.009003","CountryCode":"US","ContinentName":"US"},{"CountryName":"Uruguay","CapitalName":"Montevideo","CapitalLatitude":"-34.85","CapitalLongitude":"-56.166667","CountryCode":"UY","ContinentName":"South America"},{"CountryName":"Uzbekistan","CapitalName":"Tashkent","CapitalLatitude":"41.31666666666667","CapitalLongitude":"69.250000","CountryCode":"UZ","ContinentName":"Asia"},{"CountryName":"Vanuatu","CapitalName":"Port-Vila","CapitalLatitude":"-17.733333333333334","CapitalLongitude":"168.316667","CountryCode":"VU","ContinentName":"Australia"},{"CountryName":"Venezuela","CapitalName":"Caracas","CapitalLatitude":"10.483333333333333","CapitalLongitude":"-66.866667","CountryCode":"VE","ContinentName":"South America"},{"CountryName":"Viet Nam","CapitalName":"Hanoi","CapitalLatitude":"21.033333333333335","CapitalLongitude":"105.850000","CountryCode":"VN","ContinentName":"Asia"},{"CountryName":"US Virgin Islands","CapitalName":"Charlotte Amalie","CapitalLatitude":"18.35","CapitalLongitude":"-64.933333","CountryCode":"VI","ContinentName":"North America"},{"CountryName":"Wallis and Futuna","CapitalName":"Mata-Utu","CapitalLatitude":"-13.95","CapitalLongitude":"-171.933333","CountryCode":"WF","ContinentName":"Australia"},{"CountryName":"Yemen","CapitalName":"Sanaa","CapitalLatitude":"15.35","CapitalLongitude":"44.200000","CountryCode":"YE","ContinentName":"Asia"},{"CountryName":"Zambia","CapitalName":"Lusaka","CapitalLatitude":"-15.416666666666666","CapitalLongitude":"28.283333","CountryCode":"ZM","ContinentName":"Africa"},{"CountryName":"Zimbabwe","CapitalName":"Harare","CapitalLatitude":"-17.816666666666666","CapitalLongitude":"31.033333","CountryCode":"ZW","ContinentName":"Africa"},{"CountryName":"US Minor Outlying Islands","CapitalName":"Washington","CapitalLatitude":"-77.009003","CapitalLongitude":"38.883333","CountryCode":"-77.000000","ContinentName":"UM"},{"CountryName":"Antarctica","CapitalName":"N/A","CapitalLatitude":"0","CapitalLongitude":"0.000000","CountryCode":"AQ","ContinentName":"Antarctica"},{"CountryName":"Northern Cyprus","CapitalName":"North Nicosia","CapitalLatitude":"35.183333","CapitalLongitude":"33.366667","CountryCode":"NULL","ContinentName":"Europe"},{"CountryName":"Hong Kong","CapitalName":"N/A","CapitalLatitude":"22.396428","CapitalLongitude":"114.109497","CountryCode":"HK","ContinentName":"Asia"},{"CountryName":"Heard Island and McDonald Islands","CapitalName":"N/A","CapitalLatitude":"0","CapitalLongitude":"0.000000","CountryCode":"HM","ContinentName":"Antarctica"},{"CountryName":"British Indian Ocean Territory","CapitalName":"Diego Garcia","CapitalLatitude":"-7.3","CapitalLongitude":"72.400000","CountryCode":"IO","ContinentName":"Africa"},{"CountryName":"Macau","CapitalName":"N/A","CapitalLatitude":"0","CapitalLongitude":"0.000000","CountryCode":"MO","ContinentName":"Asia"}] ================================================ FILE: Android-code/app/src/main/assets/full_licenses.html ================================================

OpenVPN for Android

The name ics-openvpn and OpenVPN for Android refer to the same program. OpenVPN for Android is distributed under the GPL license version 2 (see Below) with the following clarification/additional terms of what constitues "deriviate work":

Using/including any part of ics-openvpn, especially using/including any part of the de.blinkt.openvpn class hierarchy, creates derivative work of ics-openvpn. Aadditionally, the normal definitions of derivative work apply.

Special exception for linking ics-openvpn with OpenSSL:

In addition, as a special exception, Arne Schwabe gives permission to link the code of this program with the OpenSSL library (or with modified versions of OpenSSL that use the same license as OpenSSL), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than OpenSSL. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version.

LZO license

LZO is Copyright (C) Markus F.X.J. Oberhumer, and is licensed under the GPL.

Special exception for linking OpenVPN with both OpenSSL and LZO:

Hereby I grant a special exception to the OpenVPN project (http://openvpn.net/) to link the LZO library with the OpenSSL library (http://www.openssl.org). Markus F.X.J. Oberhumer

Google Breakpad

Copyright (c) 2006, Google Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

3. Neither the name of the <ORGANIZATION> nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

Bouncy Castle

Copyright (c) 2000-2013 The Legion Of The Bouncy Castle (http://www.bouncycastle.org)

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

GPL Version 2, June 1991

Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.

Preamble

The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too.

When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.

To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.

For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.

We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.

Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.

Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.

The precise terms and conditions for copying, distribution and modification follow.

TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION

0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you".

Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.

1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.

You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.

2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:

a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.

b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.

c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.

In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.

3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:

a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,

b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,

c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.

If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.

4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.

5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.

6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.

7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.

It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.

This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.

8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.

9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.

10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.

NO WARRANTY

11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.

END OF TERMS AND CONDITIONS

OpenSSL

The OpenSSL toolkit stays under a dual license, i.e. both the conditions of the OpenSSL License and the original SSLeay license apply to the toolkit. See below for the actual license texts. Actually both licenses are BSD-style Open Source licenses. In case of any license issues related to OpenSSL please contact openssl-core@openssl.org.

/* ====================================================================
 * Copyright (c) 1998-2003 The OpenSSL Project.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer. 
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. All advertising materials mentioning features or use of this
 *    software must display the following acknowledgment:
 *    "This product includes software developed by the OpenSSL Project
 *    for use in the OpenSSL Toolkit. (http://www.openssl.org/)"
 *
 * 4. The names "OpenSSL Toolkit" and "OpenSSL Project" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please contact
 *    openssl-core@openssl.org.
 *
 * 5. Products derived from this software may not be called "OpenSSL"
 *    nor may "OpenSSL" appear in their names without prior written
 *    permission of the OpenSSL Project.
 *
 * 6. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the OpenSSL Project
 *    for use in the OpenSSL Toolkit (http://www.openssl.org/)"
 *
 * THIS SOFTWARE IS PROVIDED BY THE OpenSSL PROJECT ``AS IS'' AND ANY
 * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE OpenSSL PROJECT OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 * ====================================================================
 *
 * This product includes cryptographic software written by Eric Young
 * (eay@cryptsoft.com).  This product includes software written by Tim
 * Hudson (tjh@cryptsoft.com).
 *
 */

Original SSLeay License

/* Copyright (C) 1995-1998 Eric Young (eay@cryptsoft.com)
 * All rights reserved.
 *
 * This package is an SSL implementation written
 * by Eric Young (eay@cryptsoft.com).
 * The implementation was written so as to conform with Netscapes SSL.
 * 
 * This library is free for commercial and non-commercial use as long as
 * the following conditions are aheared to.  The following conditions
 * apply to all code found in this distribution, be it the RC4, RSA,
 * lhash, DES, etc., code; not just the SSL code.  The SSL documentation
 * included with this distribution is covered by the same copyright terms
 * except that the holder is Tim Hudson (tjh@cryptsoft.com).
 * 
 * Copyright remains Eric Young's, and as such any Copyright notices in
 * the code are not to be removed.
 * If this package is used in a product, Eric Young should be given attribution
 * as the author of the parts of the library used.
 * This can be in the form of a textual message at program startup or
 * in documentation (online or textual) provided with the package.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *    "This product includes cryptographic software written by
 *     Eric Young (eay@cryptsoft.com)"
 *    The word 'cryptographic' can be left out if the rouines from the library
 *    being used are not cryptographic related :-).
 * 4. If you include any Windows specific code (or a derivative thereof) from 
 *    the apps directory (application code) you must include an acknowledgement:
 *    "This product includes software written by Tim Hudson (tjh@cryptsoft.com)"
 * 
 * THIS SOFTWARE IS PROVIDED BY ERIC YOUNG ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * 
 * The licence and distribution terms for any publically available version or
 * derivative of this code cannot be changed.  i.e. this code cannot simply be
 * copied and put under another distribution licence
 * [including the GNU Public Licence.]
 */
================================================ FILE: Android-code/app/src/main/assets/world_map.geo.json ================================================ {"type":"FeatureCollection","features":[ {"type":"Feature","id":"AFG","properties":{"name":"Afghanistan"},"geometry":{"type":"Polygon","coordinates":[[[61.210817,35.650072],[62.230651,35.270664],[62.984662,35.404041],[63.193538,35.857166],[63.982896,36.007957],[64.546479,36.312073],[64.746105,37.111818],[65.588948,37.305217],[65.745631,37.661164],[66.217385,37.39379],[66.518607,37.362784],[67.075782,37.356144],[67.83,37.144994],[68.135562,37.023115],[68.859446,37.344336],[69.196273,37.151144],[69.518785,37.608997],[70.116578,37.588223],[70.270574,37.735165],[70.376304,38.138396],[70.806821,38.486282],[71.348131,38.258905],[71.239404,37.953265],[71.541918,37.905774],[71.448693,37.065645],[71.844638,36.738171],[72.193041,36.948288],[72.63689,37.047558],[73.260056,37.495257],[73.948696,37.421566],[74.980002,37.41999],[75.158028,37.133031],[74.575893,37.020841],[74.067552,36.836176],[72.920025,36.720007],[71.846292,36.509942],[71.262348,36.074388],[71.498768,35.650563],[71.613076,35.153203],[71.115019,34.733126],[71.156773,34.348911],[70.881803,33.988856],[69.930543,34.02012],[70.323594,33.358533],[69.687147,33.105499],[69.262522,32.501944],[69.317764,31.901412],[68.926677,31.620189],[68.556932,31.71331],[67.792689,31.58293],[67.683394,31.303154],[66.938891,31.304911],[66.381458,30.738899],[66.346473,29.887943],[65.046862,29.472181],[64.350419,29.560031],[64.148002,29.340819],[63.550261,29.468331],[62.549857,29.318572],[60.874248,29.829239],[61.781222,30.73585],[61.699314,31.379506],[60.941945,31.548075],[60.863655,32.18292],[60.536078,32.981269],[60.9637,33.528832],[60.52843,33.676446],[60.803193,34.404102],[61.210817,35.650072]]]}}, {"type":"Feature","id":"AGO","properties":{"name":"Angola"},"geometry":{"type":"MultiPolygon","coordinates":[[[[16.326528,-5.87747],[16.57318,-6.622645],[16.860191,-7.222298],[17.089996,-7.545689],[17.47297,-8.068551],[18.134222,-7.987678],[18.464176,-7.847014],[19.016752,-7.988246],[19.166613,-7.738184],[19.417502,-7.155429],[20.037723,-7.116361],[20.091622,-6.94309],[20.601823,-6.939318],[20.514748,-7.299606],[21.728111,-7.290872],[21.746456,-7.920085],[21.949131,-8.305901],[21.801801,-8.908707],[21.875182,-9.523708],[22.208753,-9.894796],[22.155268,-11.084801],[22.402798,-10.993075],[22.837345,-11.017622],[23.456791,-10.867863],[23.912215,-10.926826],[24.017894,-11.237298],[23.904154,-11.722282],[24.079905,-12.191297],[23.930922,-12.565848],[24.016137,-12.911046],[21.933886,-12.898437],[21.887843,-16.08031],[22.562478,-16.898451],[23.215048,-17.523116],[21.377176,-17.930636],[18.956187,-17.789095],[18.263309,-17.309951],[14.209707,-17.353101],[14.058501,-17.423381],[13.462362,-16.971212],[12.814081,-16.941343],[12.215461,-17.111668],[11.734199,-17.301889],[11.640096,-16.673142],[11.778537,-15.793816],[12.123581,-14.878316],[12.175619,-14.449144],[12.500095,-13.5477],[12.738479,-13.137906],[13.312914,-12.48363],[13.633721,-12.038645],[13.738728,-11.297863],[13.686379,-10.731076],[13.387328,-10.373578],[13.120988,-9.766897],[12.87537,-9.166934],[12.929061,-8.959091],[13.236433,-8.562629],[12.93304,-7.596539],[12.728298,-6.927122],[12.227347,-6.294448],[12.322432,-6.100092],[12.735171,-5.965682],[13.024869,-5.984389],[13.375597,-5.864241],[16.326528,-5.87747]]],[[[12.436688,-5.684304],[12.182337,-5.789931],[11.914963,-5.037987],[12.318608,-4.60623],[12.62076,-4.438023],[12.995517,-4.781103],[12.631612,-4.991271],[12.468004,-5.248362],[12.436688,-5.684304]]]]}}, {"type":"Feature","id":"ALB","properties":{"name":"Albania"},"geometry":{"type":"Polygon","coordinates":[[[20.590247,41.855404],[20.463175,41.515089],[20.605182,41.086226],[21.02004,40.842727],[20.99999,40.580004],[20.674997,40.435],[20.615,40.110007],[20.150016,39.624998],[19.98,39.694993],[19.960002,39.915006],[19.406082,40.250773],[19.319059,40.72723],[19.40355,41.409566],[19.540027,41.719986],[19.371769,41.877548],[19.304486,42.195745],[19.738051,42.688247],[19.801613,42.500093],[20.0707,42.58863],[20.283755,42.32026],[20.52295,42.21787],[20.590247,41.855404]]]}}, {"type":"Feature","id":"ARE","properties":{"name":"United Arab Emirates"},"geometry":{"type":"Polygon","coordinates":[[[51.579519,24.245497],[51.757441,24.294073],[51.794389,24.019826],[52.577081,24.177439],[53.404007,24.151317],[54.008001,24.121758],[54.693024,24.797892],[55.439025,25.439145],[56.070821,26.055464],[56.261042,25.714606],[56.396847,24.924732],[55.886233,24.920831],[55.804119,24.269604],[55.981214,24.130543],[55.528632,23.933604],[55.525841,23.524869],[55.234489,23.110993],[55.208341,22.70833],[55.006803,22.496948],[52.000733,23.001154],[51.617708,24.014219],[51.579519,24.245497]]]}}, {"type":"Feature","id":"ARG","properties":{"name":"Argentina"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-65.5,-55.2],[-66.45,-55.25],[-66.95992,-54.89681],[-67.56244,-54.87001],[-68.63335,-54.8695],[-68.63401,-52.63637],[-68.25,-53.1],[-67.75,-53.85],[-66.45,-54.45],[-65.05,-54.7],[-65.5,-55.2]]],[[[-64.964892,-22.075862],[-64.377021,-22.798091],[-63.986838,-21.993644],[-62.846468,-22.034985],[-62.685057,-22.249029],[-60.846565,-23.880713],[-60.028966,-24.032796],[-58.807128,-24.771459],[-57.777217,-25.16234],[-57.63366,-25.603657],[-58.618174,-27.123719],[-57.60976,-27.395899],[-56.486702,-27.548499],[-55.695846,-27.387837],[-54.788795,-26.621786],[-54.625291,-25.739255],[-54.13005,-25.547639],[-53.628349,-26.124865],[-53.648735,-26.923473],[-54.490725,-27.474757],[-55.162286,-27.881915],[-56.2909,-28.852761],[-57.625133,-30.216295],[-57.874937,-31.016556],[-58.14244,-32.044504],[-58.132648,-33.040567],[-58.349611,-33.263189],[-58.427074,-33.909454],[-58.495442,-34.43149],[-57.22583,-35.288027],[-57.362359,-35.97739],[-56.737487,-36.413126],[-56.788285,-36.901572],[-57.749157,-38.183871],[-59.231857,-38.72022],[-61.237445,-38.928425],[-62.335957,-38.827707],[-62.125763,-39.424105],[-62.330531,-40.172586],[-62.145994,-40.676897],[-62.745803,-41.028761],[-63.770495,-41.166789],[-64.73209,-40.802677],[-65.118035,-41.064315],[-64.978561,-42.058001],[-64.303408,-42.359016],[-63.755948,-42.043687],[-63.458059,-42.563138],[-64.378804,-42.873558],[-65.181804,-43.495381],[-65.328823,-44.501366],[-65.565269,-45.036786],[-66.509966,-45.039628],[-67.293794,-45.551896],[-67.580546,-46.301773],[-66.597066,-47.033925],[-65.641027,-47.236135],[-65.985088,-48.133289],[-67.166179,-48.697337],[-67.816088,-49.869669],[-68.728745,-50.264218],[-69.138539,-50.73251],[-68.815561,-51.771104],[-68.149995,-52.349983],[-68.571545,-52.299444],[-69.498362,-52.142761],[-71.914804,-52.009022],[-72.329404,-51.425956],[-72.309974,-50.67701],[-72.975747,-50.74145],[-73.328051,-50.378785],[-73.415436,-49.318436],[-72.648247,-48.878618],[-72.331161,-48.244238],[-72.447355,-47.738533],[-71.917258,-46.884838],[-71.552009,-45.560733],[-71.659316,-44.973689],[-71.222779,-44.784243],[-71.329801,-44.407522],[-71.793623,-44.207172],[-71.464056,-43.787611],[-71.915424,-43.408565],[-72.148898,-42.254888],[-71.746804,-42.051386],[-71.915734,-40.832339],[-71.680761,-39.808164],[-71.413517,-38.916022],[-70.814664,-38.552995],[-71.118625,-37.576827],[-71.121881,-36.658124],[-70.364769,-36.005089],[-70.388049,-35.169688],[-69.817309,-34.193571],[-69.814777,-33.273886],[-70.074399,-33.09121],[-70.535069,-31.36501],[-69.919008,-30.336339],[-70.01355,-29.367923],[-69.65613,-28.459141],[-69.001235,-27.521214],[-68.295542,-26.89934],[-68.5948,-26.506909],[-68.386001,-26.185016],[-68.417653,-24.518555],[-67.328443,-24.025303],[-66.985234,-22.986349],[-67.106674,-22.735925],[-66.273339,-21.83231],[-64.964892,-22.075862]]]]}}, {"type":"Feature","id":"ARM","properties":{"name":"Armenia"},"geometry":{"type":"Polygon","coordinates":[[[43.582746,41.092143],[44.97248,41.248129],[45.179496,40.985354],[45.560351,40.81229],[45.359175,40.561504],[45.891907,40.218476],[45.610012,39.899994],[46.034534,39.628021],[46.483499,39.464155],[46.50572,38.770605],[46.143623,38.741201],[45.735379,39.319719],[45.739978,39.473999],[45.298145,39.471751],[45.001987,39.740004],[44.79399,39.713003],[44.400009,40.005],[43.656436,40.253564],[43.752658,40.740201],[43.582746,41.092143]]]}}, {"type":"Feature","id":"ATA","properties":{"name":"Antarctica"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-59.572095,-80.040179],[-59.865849,-80.549657],[-60.159656,-81.000327],[-62.255393,-80.863178],[-64.488125,-80.921934],[-65.741666,-80.588827],[-65.741666,-80.549657],[-66.290031,-80.255773],[-64.037688,-80.294944],[-61.883246,-80.39287],[-61.138976,-79.981371],[-60.610119,-79.628679],[-59.572095,-80.040179]]],[[[-159.208184,-79.497059],[-161.127601,-79.634209],[-162.439847,-79.281465],[-163.027408,-78.928774],[-163.066604,-78.869966],[-163.712896,-78.595667],[-163.105801,-78.223338],[-161.245113,-78.380176],[-160.246208,-78.693645],[-159.482405,-79.046338],[-159.208184,-79.497059]]],[[[-45.154758,-78.04707],[-43.920828,-78.478103],[-43.48995,-79.08556],[-43.372438,-79.516645],[-43.333267,-80.026123],[-44.880537,-80.339644],[-46.506174,-80.594357],[-48.386421,-80.829485],[-50.482107,-81.025442],[-52.851988,-80.966685],[-54.164259,-80.633528],[-53.987991,-80.222028],[-51.853134,-79.94773],[-50.991326,-79.614623],[-50.364595,-79.183487],[-49.914131,-78.811209],[-49.306959,-78.458569],[-48.660616,-78.047018],[-48.660616,-78.047019],[-48.151396,-78.04707],[-46.662857,-77.831476],[-45.154758,-78.04707]]],[[[-121.211511,-73.50099],[-119.918851,-73.657725],[-118.724143,-73.481353],[-119.292119,-73.834097],[-120.232217,-74.08881],[-121.62283,-74.010468],[-122.621735,-73.657778],[-122.621735,-73.657777],[-122.406245,-73.324619],[-121.211511,-73.50099]]],[[[-125.559566,-73.481353],[-124.031882,-73.873268],[-124.619469,-73.834097],[-125.912181,-73.736118],[-127.28313,-73.461769],[-127.28313,-73.461768],[-126.558472,-73.246226],[-125.559566,-73.481353]]],[[[-98.98155,-71.933334],[-97.884743,-72.070535],[-96.787937,-71.952971],[-96.20035,-72.521205],[-96.983765,-72.442864],[-98.198083,-72.482035],[-99.432013,-72.442864],[-100.783455,-72.50162],[-101.801868,-72.305663],[-102.330725,-71.894164],[-101.703967,-71.717792],[-100.430919,-71.854993],[-98.98155,-71.933334]]],[[[-68.451346,-70.955823],[-68.333834,-71.406493],[-68.510128,-71.798407],[-68.784297,-72.170736],[-69.959471,-72.307885],[-71.075889,-72.503842],[-72.388134,-72.484257],[-71.8985,-72.092343],[-73.073622,-72.229492],[-74.19004,-72.366693],[-74.953895,-72.072757],[-75.012625,-71.661258],[-73.915819,-71.269345],[-73.915819,-71.269344],[-73.230331,-71.15178],[-72.074717,-71.190951],[-71.780962,-70.681473],[-71.72218,-70.309196],[-71.741791,-69.505782],[-71.173815,-69.035475],[-70.253252,-68.87874],[-69.724447,-69.251017],[-69.489422,-69.623346],[-69.058518,-70.074016],[-68.725541,-70.505153],[-68.451346,-70.955823]]],[[[-58.614143,-64.152467],[-59.045073,-64.36801],[-59.789342,-64.211223],[-60.611928,-64.309202],[-61.297416,-64.54433],[-62.0221,-64.799094],[-62.51176,-65.09303],[-62.648858,-65.484942],[-62.590128,-65.857219],[-62.120079,-66.190326],[-62.805567,-66.425505],[-63.74569,-66.503847],[-64.294106,-66.837004],[-64.881693,-67.150474],[-65.508425,-67.58161],[-65.665082,-67.953887],[-65.312545,-68.365335],[-64.783715,-68.678908],[-63.961103,-68.913984],[-63.1973,-69.227556],[-62.785955,-69.619419],[-62.570516,-69.991747],[-62.276736,-70.383661],[-61.806661,-70.716768],[-61.512906,-71.089045],[-61.375809,-72.010074],[-61.081977,-72.382351],[-61.003661,-72.774265],[-60.690269,-73.166179],[-60.827367,-73.695242],[-61.375809,-74.106742],[-61.96337,-74.439848],[-63.295201,-74.576997],[-63.74569,-74.92974],[-64.352836,-75.262847],[-65.860987,-75.635124],[-67.192818,-75.79191],[-68.446282,-76.007452],[-69.797724,-76.222995],[-70.600724,-76.634494],[-72.206776,-76.673665],[-73.969536,-76.634494],[-75.555977,-76.712887],[-77.24037,-76.712887],[-76.926979,-77.104802],[-75.399294,-77.28107],[-74.282876,-77.55542],[-73.656119,-77.908112],[-74.772536,-78.221633],[-76.4961,-78.123654],[-77.925858,-78.378419],[-77.984666,-78.789918],[-78.023785,-79.181833],[-76.848637,-79.514939],[-76.633224,-79.887216],[-75.360097,-80.259545],[-73.244852,-80.416331],[-71.442946,-80.69063],[-70.013163,-81.004151],[-68.191646,-81.317672],[-65.704279,-81.474458],[-63.25603,-81.748757],[-61.552026,-82.042692],[-59.691416,-82.37585],[-58.712121,-82.846106],[-58.222487,-83.218434],[-57.008117,-82.865691],[-55.362894,-82.571755],[-53.619771,-82.258235],[-51.543644,-82.003521],[-49.76135,-81.729171],[-47.273931,-81.709586],[-44.825708,-81.846735],[-42.808363,-82.081915],[-42.16202,-81.65083],[-40.771433,-81.356894],[-38.244818,-81.337309],[-36.26667,-81.121715],[-34.386397,-80.906172],[-32.310296,-80.769023],[-30.097098,-80.592651],[-28.549802,-80.337938],[-29.254901,-79.985195],[-29.685805,-79.632503],[-29.685805,-79.260226],[-31.624808,-79.299397],[-33.681324,-79.456132],[-35.639912,-79.456132],[-35.914107,-79.083855],[-35.77701,-78.339248],[-35.326546,-78.123654],[-33.896763,-77.888526],[-32.212369,-77.65345],[-30.998051,-77.359515],[-29.783732,-77.065579],[-28.882779,-76.673665],[-27.511752,-76.497345],[-26.160336,-76.360144],[-25.474822,-76.281803],[-23.927552,-76.24258],[-22.458598,-76.105431],[-21.224694,-75.909474],[-20.010375,-75.674346],[-18.913543,-75.439218],[-17.522982,-75.125698],[-16.641589,-74.79254],[-15.701491,-74.498604],[-15.40771,-74.106742],[-16.46532,-73.871614],[-16.112784,-73.460114],[-15.446855,-73.146542],[-14.408805,-72.950585],[-13.311973,-72.715457],[-12.293508,-72.401936],[-11.510067,-72.010074],[-11.020433,-71.539767],[-10.295774,-71.265416],[-9.101015,-71.324224],[-8.611381,-71.65733],[-7.416622,-71.696501],[-7.377451,-71.324224],[-6.868232,-70.93231],[-5.790985,-71.030289],[-5.536375,-71.402617],[-4.341667,-71.461373],[-3.048981,-71.285053],[-1.795492,-71.167438],[-0.659489,-71.226246],[-0.228637,-71.637745],[0.868195,-71.304639],[1.886686,-71.128267],[3.022638,-70.991118],[4.139055,-70.853917],[5.157546,-70.618789],[6.273912,-70.462055],[7.13572,-70.246512],[7.742866,-69.893769],[8.48711,-70.148534],[9.525135,-70.011333],[10.249845,-70.48164],[10.817821,-70.834332],[11.953824,-70.638375],[12.404287,-70.246512],[13.422778,-69.972162],[14.734998,-70.030918],[15.126757,-70.403247],[15.949342,-70.030918],[17.026589,-69.913354],[18.201711,-69.874183],[19.259373,-69.893769],[20.375739,-70.011333],[21.452985,-70.07014],[21.923034,-70.403247],[22.569403,-70.697182],[23.666184,-70.520811],[24.841357,-70.48164],[25.977309,-70.48164],[27.093726,-70.462055],[28.09258,-70.324854],[29.150242,-70.20729],[30.031583,-69.93294],[30.971733,-69.75662],[31.990172,-69.658641],[32.754053,-69.384291],[33.302443,-68.835642],[33.870419,-68.502588],[34.908495,-68.659271],[35.300202,-69.012014],[36.16201,-69.247142],[37.200035,-69.168748],[37.905108,-69.52144],[38.649404,-69.776205],[39.667894,-69.541077],[40.020431,-69.109941],[40.921358,-68.933621],[41.959434,-68.600514],[42.938702,-68.463313],[44.113876,-68.267408],[44.897291,-68.051866],[45.719928,-67.816738],[46.503343,-67.601196],[47.44344,-67.718759],[48.344419,-67.366068],[48.990736,-67.091718],[49.930885,-67.111303],[50.753471,-66.876175],[50.949325,-66.523484],[51.791547,-66.249133],[52.614133,-66.053176],[53.613038,-65.89639],[54.53355,-65.818049],[55.414943,-65.876805],[56.355041,-65.974783],[57.158093,-66.249133],[57.255968,-66.680218],[58.137361,-67.013324],[58.744508,-67.287675],[59.939318,-67.405239],[60.605221,-67.679589],[61.427806,-67.953887],[62.387489,-68.012695],[63.19049,-67.816738],[64.052349,-67.405239],[64.992447,-67.620729],[65.971715,-67.738345],[66.911864,-67.855909],[67.891133,-67.934302],[68.890038,-67.934302],[69.712624,-68.972791],[69.673453,-69.227556],[69.555941,-69.678226],[68.596258,-69.93294],[67.81274,-70.305268],[67.949889,-70.697182],[69.066307,-70.677545],[68.929157,-71.069459],[68.419989,-71.441788],[67.949889,-71.853287],[68.71377,-72.166808],[69.869307,-72.264787],[71.024895,-72.088415],[71.573285,-71.696501],[71.906288,-71.324224],[72.454627,-71.010703],[73.08141,-70.716768],[73.33602,-70.364024],[73.864877,-69.874183],[74.491557,-69.776205],[75.62756,-69.737034],[76.626465,-69.619419],[77.644904,-69.462684],[78.134539,-69.07077],[78.428371,-68.698441],[79.113859,-68.326216],[80.093127,-68.071503],[80.93535,-67.875546],[81.483792,-67.542388],[82.051767,-67.366068],[82.776426,-67.209282],[83.775331,-67.30726],[84.676206,-67.209282],[85.655527,-67.091718],[86.752359,-67.150474],[87.477017,-66.876175],[87.986289,-66.209911],[88.358411,-66.484261],[88.828408,-66.954568],[89.67063,-67.150474],[90.630365,-67.228867],[91.5901,-67.111303],[92.608539,-67.189696],[93.548637,-67.209282],[94.17542,-67.111303],[95.017591,-67.170111],[95.781472,-67.385653],[96.682399,-67.248504],[97.759646,-67.248504],[98.68021,-67.111303],[99.718182,-67.248504],[100.384188,-66.915346],[100.893356,-66.58224],[101.578896,-66.30789],[102.832411,-65.563284],[103.478676,-65.700485],[104.242557,-65.974783],[104.90846,-66.327527],[106.181561,-66.934931],[107.160881,-66.954568],[108.081393,-66.954568],[109.15864,-66.837004],[110.235835,-66.699804],[111.058472,-66.425505],[111.74396,-66.13157],[112.860378,-66.092347],[113.604673,-65.876805],[114.388088,-66.072762],[114.897308,-66.386283],[115.602381,-66.699804],[116.699161,-66.660633],[117.384701,-66.915346],[118.57946,-67.170111],[119.832924,-67.268089],[120.871,-67.189696],[121.654415,-66.876175],[122.320369,-66.562654],[123.221296,-66.484261],[124.122274,-66.621462],[125.160247,-66.719389],[126.100396,-66.562654],[127.001427,-66.562654],[127.882768,-66.660633],[128.80328,-66.758611],[129.704259,-66.58224],[130.781454,-66.425505],[131.799945,-66.386283],[132.935896,-66.386283],[133.85646,-66.288304],[134.757387,-66.209963],[135.031582,-65.72007],[135.070753,-65.308571],[135.697485,-65.582869],[135.873805,-66.033591],[136.206705,-66.44509],[136.618049,-66.778197],[137.460271,-66.954568],[138.596223,-66.895761],[139.908442,-66.876175],[140.809421,-66.817367],[142.121692,-66.817367],[143.061842,-66.797782],[144.374061,-66.837004],[145.490427,-66.915346],[146.195552,-67.228867],[145.999699,-67.601196],[146.646067,-67.895131],[147.723263,-68.130259],[148.839629,-68.385024],[150.132314,-68.561292],[151.483705,-68.71813],[152.502247,-68.874813],[153.638199,-68.894502],[154.284567,-68.561292],[155.165857,-68.835642],[155.92979,-69.149215],[156.811132,-69.384291],[158.025528,-69.482269],[159.181013,-69.599833],[159.670699,-69.991747],[160.80665,-70.226875],[161.570479,-70.579618],[162.686897,-70.736353],[163.842434,-70.716768],[164.919681,-70.775524],[166.11444,-70.755938],[167.309095,-70.834332],[168.425616,-70.971481],[169.463589,-71.20666],[170.501665,-71.402617],[171.20679,-71.696501],[171.089227,-72.088415],[170.560422,-72.441159],[170.109958,-72.891829],[169.75737,-73.24452],[169.287321,-73.65602],[167.975101,-73.812806],[167.387489,-74.165498],[166.094803,-74.38104],[165.644391,-74.772954],[164.958851,-75.145283],[164.234193,-75.458804],[163.822797,-75.870303],[163.568239,-76.24258],[163.47026,-76.693302],[163.489897,-77.065579],[164.057873,-77.457442],[164.273363,-77.82977],[164.743464,-78.182514],[166.604126,-78.319611],[166.995781,-78.750748],[165.193876,-78.907483],[163.666217,-79.123025],[161.766385,-79.162248],[160.924162,-79.730482],[160.747894,-80.200737],[160.316964,-80.573066],[159.788211,-80.945395],[161.120016,-81.278501],[161.629287,-81.690001],[162.490992,-82.062278],[163.705336,-82.395435],[165.095949,-82.708956],[166.604126,-83.022477],[168.895665,-83.335998],[169.404782,-83.825891],[172.283934,-84.041433],[172.477049,-84.117914],[173.224083,-84.41371],[175.985672,-84.158997],[178.277212,-84.472518],[180,-84.71338],[-179.942499,-84.721443],[-179.058677,-84.139412],[-177.256772,-84.452933],[-177.140807,-84.417941],[-176.084673,-84.099259],[-175.947235,-84.110449],[-175.829882,-84.117914],[-174.382503,-84.534323],[-173.116559,-84.117914],[-172.889106,-84.061019],[-169.951223,-83.884647],[-168.999989,-84.117914],[-168.530199,-84.23739],[-167.022099,-84.570497],[-164.182144,-84.82521],[-161.929775,-85.138731],[-158.07138,-85.37391],[-155.192253,-85.09956],[-150.942099,-85.295517],[-148.533073,-85.609038],[-145.888918,-85.315102],[-143.107718,-85.040752],[-142.892279,-84.570497],[-146.829068,-84.531274],[-150.060732,-84.296146],[-150.902928,-83.904232],[-153.586201,-83.68869],[-153.409907,-83.23802],[-153.037759,-82.82652],[-152.665637,-82.454192],[-152.861517,-82.042692],[-154.526299,-81.768394],[-155.29018,-81.41565],[-156.83745,-81.102129],[-154.408787,-81.160937],[-152.097662,-81.004151],[-150.648293,-81.337309],[-148.865998,-81.043373],[-147.22075,-80.671045],[-146.417749,-80.337938],[-146.770286,-79.926439],[-148.062947,-79.652089],[-149.531901,-79.358205],[-151.588416,-79.299397],[-153.390322,-79.162248],[-155.329376,-79.064269],[-155.975668,-78.69194],[-157.268302,-78.378419],[-158.051768,-78.025676],[-158.365134,-76.889207],[-157.875474,-76.987238],[-156.974573,-77.300759],[-155.329376,-77.202728],[-153.742832,-77.065579],[-152.920247,-77.496664],[-151.33378,-77.398737],[-150.00195,-77.183143],[-148.748486,-76.908845],[-147.612483,-76.575738],[-146.104409,-76.47776],[-146.143528,-76.105431],[-146.496091,-75.733154],[-146.20231,-75.380411],[-144.909624,-75.204039],[-144.322037,-75.537197],[-142.794353,-75.34124],[-141.638764,-75.086475],[-140.209007,-75.06689],[-138.85759,-74.968911],[-137.5062,-74.733783],[-136.428901,-74.518241],[-135.214583,-74.302699],[-134.431194,-74.361455],[-133.745654,-74.439848],[-132.257168,-74.302699],[-130.925311,-74.479019],[-129.554284,-74.459433],[-128.242038,-74.322284],[-126.890622,-74.420263],[-125.402082,-74.518241],[-124.011496,-74.479019],[-122.562152,-74.498604],[-121.073613,-74.518241],[-119.70256,-74.479019],[-118.684145,-74.185083],[-117.469801,-74.028348],[-116.216312,-74.243891],[-115.021552,-74.067519],[-113.944331,-73.714828],[-113.297988,-74.028348],[-112.945452,-74.38104],[-112.299083,-74.714198],[-111.261059,-74.420263],[-110.066325,-74.79254],[-108.714909,-74.910103],[-107.559346,-75.184454],[-106.149148,-75.125698],[-104.876074,-74.949326],[-103.367949,-74.988497],[-102.016507,-75.125698],[-100.645531,-75.302018],[-100.1167,-74.870933],[-100.763043,-74.537826],[-101.252703,-74.185083],[-102.545337,-74.106742],[-103.113313,-73.734413],[-103.328752,-73.362084],[-103.681289,-72.61753],[-102.917485,-72.754679],[-101.60524,-72.813436],[-100.312528,-72.754679],[-99.13738,-72.911414],[-98.118889,-73.20535],[-97.688037,-73.558041],[-96.336595,-73.616849],[-95.043961,-73.4797],[-93.672907,-73.283743],[-92.439003,-73.166179],[-91.420564,-73.401307],[-90.088733,-73.322914],[-89.226951,-72.558722],[-88.423951,-73.009393],[-87.268337,-73.185764],[-86.014822,-73.087786],[-85.192236,-73.4797],[-83.879991,-73.518871],[-82.665646,-73.636434],[-81.470913,-73.851977],[-80.687447,-73.4797],[-80.295791,-73.126956],[-79.296886,-73.518871],[-77.925858,-73.420892],[-76.907367,-73.636434],[-76.221879,-73.969541],[-74.890049,-73.871614],[-73.852024,-73.65602],[-72.833533,-73.401307],[-71.619215,-73.264157],[-70.209042,-73.146542],[-68.935916,-73.009393],[-67.956622,-72.79385],[-67.369061,-72.480329],[-67.134036,-72.049244],[-67.251548,-71.637745],[-67.56494,-71.245831],[-67.917477,-70.853917],[-68.230843,-70.462055],[-68.485452,-70.109311],[-68.544209,-69.717397],[-68.446282,-69.325535],[-67.976233,-68.953206],[-67.5845,-68.541707],[-67.427843,-68.149844],[-67.62367,-67.718759],[-67.741183,-67.326845],[-67.251548,-66.876175],[-66.703184,-66.58224],[-66.056815,-66.209963],[-65.371327,-65.89639],[-64.568276,-65.602506],[-64.176542,-65.171423],[-63.628152,-64.897073],[-63.001394,-64.642308],[-62.041686,-64.583552],[-61.414928,-64.270031],[-60.709855,-64.074074],[-59.887269,-63.95651],[-59.162585,-63.701745],[-58.594557,-63.388224],[-57.811143,-63.27066],[-57.223582,-63.525425],[-57.59573,-63.858532],[-58.614143,-64.152467]]]]}}, {"type":"Feature","id":"ATF","properties":{"name":"French Southern and Antarctic Lands"},"geometry":{"type":"Polygon","coordinates":[[[68.935,-48.625],[69.58,-48.94],[70.525,-49.065],[70.56,-49.255],[70.28,-49.71],[68.745,-49.775],[68.72,-49.2425],[68.8675,-48.83],[68.935,-48.625]]]}}, {"type":"Feature","id":"AUS","properties":{"name":"Australia"},"geometry":{"type":"MultiPolygon","coordinates":[[[[145.397978,-40.792549],[146.364121,-41.137695],[146.908584,-41.000546],[147.689259,-40.808258],[148.289068,-40.875438],[148.359865,-42.062445],[148.017301,-42.407024],[147.914052,-43.211522],[147.564564,-42.937689],[146.870343,-43.634597],[146.663327,-43.580854],[146.048378,-43.549745],[145.43193,-42.693776],[145.29509,-42.03361],[144.718071,-41.162552],[144.743755,-40.703975],[145.397978,-40.792549]]],[[[143.561811,-13.763656],[143.922099,-14.548311],[144.563714,-14.171176],[144.894908,-14.594458],[145.374724,-14.984976],[145.271991,-15.428205],[145.48526,-16.285672],[145.637033,-16.784918],[145.888904,-16.906926],[146.160309,-17.761655],[146.063674,-18.280073],[146.387478,-18.958274],[147.471082,-19.480723],[148.177602,-19.955939],[148.848414,-20.39121],[148.717465,-20.633469],[149.28942,-21.260511],[149.678337,-22.342512],[150.077382,-22.122784],[150.482939,-22.556142],[150.727265,-22.402405],[150.899554,-23.462237],[151.609175,-24.076256],[152.07354,-24.457887],[152.855197,-25.267501],[153.136162,-26.071173],[153.161949,-26.641319],[153.092909,-27.2603],[153.569469,-28.110067],[153.512108,-28.995077],[153.339095,-29.458202],[153.069241,-30.35024],[153.089602,-30.923642],[152.891578,-31.640446],[152.450002,-32.550003],[151.709117,-33.041342],[151.343972,-33.816023],[151.010555,-34.31036],[150.714139,-35.17346],[150.32822,-35.671879],[150.075212,-36.420206],[149.946124,-37.109052],[149.997284,-37.425261],[149.423882,-37.772681],[148.304622,-37.809061],[147.381733,-38.219217],[146.922123,-38.606532],[146.317922,-39.035757],[145.489652,-38.593768],[144.876976,-38.417448],[145.032212,-37.896188],[144.485682,-38.085324],[143.609974,-38.809465],[142.745427,-38.538268],[142.17833,-38.380034],[141.606582,-38.308514],[140.638579,-38.019333],[139.992158,-37.402936],[139.806588,-36.643603],[139.574148,-36.138362],[139.082808,-35.732754],[138.120748,-35.612296],[138.449462,-35.127261],[138.207564,-34.384723],[137.71917,-35.076825],[136.829406,-35.260535],[137.352371,-34.707339],[137.503886,-34.130268],[137.890116,-33.640479],[137.810328,-32.900007],[136.996837,-33.752771],[136.372069,-34.094766],[135.989043,-34.890118],[135.208213,-34.47867],[135.239218,-33.947953],[134.613417,-33.222778],[134.085904,-32.848072],[134.273903,-32.617234],[132.990777,-32.011224],[132.288081,-31.982647],[131.326331,-31.495803],[129.535794,-31.590423],[128.240938,-31.948489],[127.102867,-32.282267],[126.148714,-32.215966],[125.088623,-32.728751],[124.221648,-32.959487],[124.028947,-33.483847],[123.659667,-33.890179],[122.811036,-33.914467],[122.183064,-34.003402],[121.299191,-33.821036],[120.580268,-33.930177],[119.893695,-33.976065],[119.298899,-34.509366],[119.007341,-34.464149],[118.505718,-34.746819],[118.024972,-35.064733],[117.295507,-35.025459],[116.625109,-35.025097],[115.564347,-34.386428],[115.026809,-34.196517],[115.048616,-33.623425],[115.545123,-33.487258],[115.714674,-33.259572],[115.679379,-32.900369],[115.801645,-32.205062],[115.689611,-31.612437],[115.160909,-30.601594],[114.997043,-30.030725],[115.040038,-29.461095],[114.641974,-28.810231],[114.616498,-28.516399],[114.173579,-28.118077],[114.048884,-27.334765],[113.477498,-26.543134],[113.338953,-26.116545],[113.778358,-26.549025],[113.440962,-25.621278],[113.936901,-25.911235],[114.232852,-26.298446],[114.216161,-25.786281],[113.721255,-24.998939],[113.625344,-24.683971],[113.393523,-24.384764],[113.502044,-23.80635],[113.706993,-23.560215],[113.843418,-23.059987],[113.736552,-22.475475],[114.149756,-21.755881],[114.225307,-22.517488],[114.647762,-21.82952],[115.460167,-21.495173],[115.947373,-21.068688],[116.711615,-20.701682],[117.166316,-20.623599],[117.441545,-20.746899],[118.229559,-20.374208],[118.836085,-20.263311],[118.987807,-20.044203],[119.252494,-19.952942],[119.805225,-19.976506],[120.85622,-19.683708],[121.399856,-19.239756],[121.655138,-18.705318],[122.241665,-18.197649],[122.286624,-17.798603],[122.312772,-17.254967],[123.012574,-16.4052],[123.433789,-17.268558],[123.859345,-17.069035],[123.503242,-16.596506],[123.817073,-16.111316],[124.258287,-16.327944],[124.379726,-15.56706],[124.926153,-15.0751],[125.167275,-14.680396],[125.670087,-14.51007],[125.685796,-14.230656],[126.125149,-14.347341],[126.142823,-14.095987],[126.582589,-13.952791],[127.065867,-13.817968],[127.804633,-14.276906],[128.35969,-14.86917],[128.985543,-14.875991],[129.621473,-14.969784],[129.4096,-14.42067],[129.888641,-13.618703],[130.339466,-13.357376],[130.183506,-13.10752],[130.617795,-12.536392],[131.223495,-12.183649],[131.735091,-12.302453],[132.575298,-12.114041],[132.557212,-11.603012],[131.824698,-11.273782],[132.357224,-11.128519],[133.019561,-11.376411],[133.550846,-11.786515],[134.393068,-12.042365],[134.678632,-11.941183],[135.298491,-12.248606],[135.882693,-11.962267],[136.258381,-12.049342],[136.492475,-11.857209],[136.95162,-12.351959],[136.685125,-12.887223],[136.305407,-13.29123],[135.961758,-13.324509],[136.077617,-13.724278],[135.783836,-14.223989],[135.428664,-14.715432],[135.500184,-14.997741],[136.295175,-15.550265],[137.06536,-15.870762],[137.580471,-16.215082],[138.303217,-16.807604],[138.585164,-16.806622],[139.108543,-17.062679],[139.260575,-17.371601],[140.215245,-17.710805],[140.875463,-17.369069],[141.07111,-16.832047],[141.274095,-16.38887],[141.398222,-15.840532],[141.702183,-15.044921],[141.56338,-14.561333],[141.63552,-14.270395],[141.519869,-13.698078],[141.65092,-12.944688],[141.842691,-12.741548],[141.68699,-12.407614],[141.928629,-11.877466],[142.118488,-11.328042],[142.143706,-11.042737],[142.51526,-10.668186],[142.79731,-11.157355],[142.866763,-11.784707],[143.115947,-11.90563],[143.158632,-12.325656],[143.522124,-12.834358],[143.597158,-13.400422],[143.561811,-13.763656]]]]}}, {"type":"Feature","id":"AUT","properties":{"name":"Austria"},"geometry":{"type":"Polygon","coordinates":[[[16.979667,48.123497],[16.903754,47.714866],[16.340584,47.712902],[16.534268,47.496171],[16.202298,46.852386],[16.011664,46.683611],[15.137092,46.658703],[14.632472,46.431817],[13.806475,46.509306],[12.376485,46.767559],[12.153088,47.115393],[11.164828,46.941579],[11.048556,46.751359],[10.442701,46.893546],[9.932448,46.920728],[9.47997,47.10281],[9.632932,47.347601],[9.594226,47.525058],[9.896068,47.580197],[10.402084,47.302488],[10.544504,47.566399],[11.426414,47.523766],[12.141357,47.703083],[12.62076,47.672388],[12.932627,47.467646],[13.025851,47.637584],[12.884103,48.289146],[13.243357,48.416115],[13.595946,48.877172],[14.338898,48.555305],[14.901447,48.964402],[15.253416,49.039074],[16.029647,48.733899],[16.499283,48.785808],[16.960288,48.596982],[16.879983,48.470013],[16.979667,48.123497]]]}}, {"type":"Feature","id":"AZE","properties":{"name":"Azerbaijan"},"geometry":{"type":"MultiPolygon","coordinates":[[[[45.001987,39.740004],[45.298145,39.471751],[45.739978,39.473999],[45.735379,39.319719],[46.143623,38.741201],[45.457722,38.874139],[44.952688,39.335765],[44.79399,39.713003],[45.001987,39.740004]]],[[[47.373315,41.219732],[47.815666,41.151416],[47.987283,41.405819],[48.584353,41.80887],[49.110264,41.282287],[49.618915,40.572924],[50.08483,40.526157],[50.392821,40.256561],[49.569202,40.176101],[49.395259,39.399482],[49.223228,39.049219],[48.856532,38.815486],[48.883249,38.320245],[48.634375,38.270378],[48.010744,38.794015],[48.355529,39.288765],[48.060095,39.582235],[47.685079,39.508364],[46.50572,38.770605],[46.483499,39.464155],[46.034534,39.628021],[45.610012,39.899994],[45.891907,40.218476],[45.359175,40.561504],[45.560351,40.81229],[45.179496,40.985354],[44.97248,41.248129],[45.217426,41.411452],[45.962601,41.123873],[46.501637,41.064445],[46.637908,41.181673],[46.145432,41.722802],[46.404951,41.860675],[46.686071,41.827137],[47.373315,41.219732]]]]}}, {"type":"Feature","id":"BDI","properties":{"name":"Burundi"},"geometry":{"type":"Polygon","coordinates":[[[29.339998,-4.499983],[29.276384,-3.293907],[29.024926,-2.839258],[29.632176,-2.917858],[29.938359,-2.348487],[30.469696,-2.413858],[30.527677,-2.807632],[30.743013,-3.034285],[30.752263,-3.35933],[30.50556,-3.568567],[30.116333,-4.090138],[29.753512,-4.452389],[29.339998,-4.499983]]]}}, {"type":"Feature","id":"BEL","properties":{"name":"Belgium"},"geometry":{"type":"Polygon","coordinates":[[[3.314971,51.345781],[4.047071,51.267259],[4.973991,51.475024],[5.606976,51.037298],[6.156658,50.803721],[6.043073,50.128052],[5.782417,50.090328],[5.674052,49.529484],[4.799222,49.985373],[4.286023,49.907497],[3.588184,50.378992],[3.123252,50.780363],[2.658422,50.796848],[2.513573,51.148506],[3.314971,51.345781]]]}}, {"type":"Feature","id":"BEN","properties":{"name":"Benin"},"geometry":{"type":"Polygon","coordinates":[[[2.691702,6.258817],[1.865241,6.142158],[1.618951,6.832038],[1.664478,9.12859],[1.463043,9.334624],[1.425061,9.825395],[1.077795,10.175607],[0.772336,10.470808],[0.899563,10.997339],[1.24347,11.110511],[1.447178,11.547719],[1.935986,11.64115],[2.154474,11.94015],[2.490164,12.233052],[2.848643,12.235636],[3.61118,11.660167],[3.572216,11.327939],[3.797112,10.734746],[3.60007,10.332186],[3.705438,10.06321],[3.220352,9.444153],[2.912308,9.137608],[2.723793,8.506845],[2.749063,7.870734],[2.691702,6.258817]]]}}, {"type":"Feature","id":"BFA","properties":{"name":"Burkina Faso"},"geometry":{"type":"Polygon","coordinates":[[[-2.827496,9.642461],[-3.511899,9.900326],[-3.980449,9.862344],[-4.330247,9.610835],[-4.779884,9.821985],[-4.954653,10.152714],[-5.404342,10.370737],[-5.470565,10.95127],[-5.197843,11.375146],[-5.220942,11.713859],[-4.427166,12.542646],[-4.280405,13.228444],[-4.006391,13.472485],[-3.522803,13.337662],[-3.103707,13.541267],[-2.967694,13.79815],[-2.191825,14.246418],[-2.001035,14.559008],[-1.066363,14.973815],[-0.515854,15.116158],[-0.266257,14.924309],[0.374892,14.928908],[0.295646,14.444235],[0.429928,13.988733],[0.993046,13.33575],[1.024103,12.851826],[2.177108,12.625018],[2.154474,11.94015],[1.935986,11.64115],[1.447178,11.547719],[1.24347,11.110511],[0.899563,10.997339],[0.023803,11.018682],[-0.438702,11.098341],[-0.761576,10.93693],[-1.203358,11.009819],[-2.940409,10.96269],[-2.963896,10.395335],[-2.827496,9.642461]]]}}, {"type":"Feature","id":"BGD","properties":{"name":"Bangladesh"},"geometry":{"type":"Polygon","coordinates":[[[92.672721,22.041239],[92.652257,21.324048],[92.303234,21.475485],[92.368554,20.670883],[92.082886,21.192195],[92.025215,21.70157],[91.834891,22.182936],[91.417087,22.765019],[90.496006,22.805017],[90.586957,22.392794],[90.272971,21.836368],[89.847467,22.039146],[89.70205,21.857116],[89.418863,21.966179],[89.031961,22.055708],[88.876312,22.879146],[88.52977,23.631142],[88.69994,24.233715],[88.084422,24.501657],[88.306373,24.866079],[88.931554,25.238692],[88.209789,25.768066],[88.563049,26.446526],[89.355094,26.014407],[89.832481,25.965082],[89.920693,25.26975],[90.872211,25.132601],[91.799596,25.147432],[92.376202,24.976693],[91.915093,24.130414],[91.46773,24.072639],[91.158963,23.503527],[91.706475,22.985264],[91.869928,23.624346],[92.146035,23.627499],[92.672721,22.041239]]]}}, {"type":"Feature","id":"BGR","properties":{"name":"Bulgaria"},"geometry":{"type":"Polygon","coordinates":[[[22.65715,44.234923],[22.944832,43.823785],[23.332302,43.897011],[24.100679,43.741051],[25.569272,43.688445],[26.065159,43.943494],[27.2424,44.175986],[27.970107,43.812468],[28.558081,43.707462],[28.039095,43.293172],[27.673898,42.577892],[27.99672,42.007359],[27.135739,42.141485],[26.117042,41.826905],[26.106138,41.328899],[25.197201,41.234486],[24.492645,41.583896],[23.692074,41.309081],[22.952377,41.337994],[22.881374,41.999297],[22.380526,42.32026],[22.545012,42.461362],[22.436595,42.580321],[22.604801,42.898519],[22.986019,43.211161],[22.500157,43.642814],[22.410446,44.008063],[22.65715,44.234923]]]}}, {"type":"Feature","id":"BHS","properties":{"name":"The Bahamas"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-77.53466,23.75975],[-77.78,23.71],[-78.03405,24.28615],[-78.40848,24.57564],[-78.19087,25.2103],[-77.89,25.17],[-77.54,24.34],[-77.53466,23.75975]]],[[[-77.82,26.58],[-78.91,26.42],[-78.98,26.79],[-78.51,26.87],[-77.85,26.84],[-77.82,26.58]]],[[[-77,26.59],[-77.17255,25.87918],[-77.35641,26.00735],[-77.34,26.53],[-77.78802,26.92516],[-77.79,27.04],[-77,26.59]]]]}}, {"type":"Feature","id":"BIH","properties":{"name":"Bosnia and Herzegovina"},"geometry":{"type":"Polygon","coordinates":[[[19.005486,44.860234],[19.36803,44.863],[19.11761,44.42307],[19.59976,44.03847],[19.454,43.5681],[19.21852,43.52384],[19.03165,43.43253],[18.70648,43.20011],[18.56,42.65],[17.674922,43.028563],[17.297373,43.446341],[16.916156,43.667722],[16.456443,44.04124],[16.23966,44.351143],[15.750026,44.818712],[15.959367,45.233777],[16.318157,45.004127],[16.534939,45.211608],[17.002146,45.233777],[17.861783,45.06774],[18.553214,45.08159],[19.005486,44.860234]]]}}, {"type":"Feature","id":"BLR","properties":{"name":"Belarus"},"geometry":{"type":"Polygon","coordinates":[[[23.484128,53.912498],[24.450684,53.905702],[25.536354,54.282423],[25.768433,54.846963],[26.588279,55.167176],[26.494331,55.615107],[27.10246,55.783314],[28.176709,56.16913],[29.229513,55.918344],[29.371572,55.670091],[29.896294,55.789463],[30.873909,55.550976],[30.971836,55.081548],[30.757534,54.811771],[31.384472,54.157056],[31.791424,53.974639],[31.731273,53.794029],[32.405599,53.618045],[32.693643,53.351421],[32.304519,53.132726],[31.497644,53.167427],[31.305201,53.073996],[31.540018,52.742052],[31.785998,52.101678],[30.927549,52.042353],[30.619454,51.822806],[30.555117,51.319503],[30.157364,51.416138],[29.254938,51.368234],[28.992835,51.602044],[28.617613,51.427714],[28.241615,51.572227],[27.454066,51.592303],[26.337959,51.832289],[25.327788,51.910656],[24.553106,51.888461],[24.005078,51.617444],[23.527071,51.578454],[23.508002,52.023647],[23.199494,52.486977],[23.799199,52.691099],[23.804935,53.089731],[23.527536,53.470122],[23.484128,53.912498]]]}}, {"type":"Feature","id":"BLZ","properties":{"name":"Belize"},"geometry":{"type":"Polygon","coordinates":[[[-89.14308,17.808319],[-89.150909,17.955468],[-89.029857,18.001511],[-88.848344,17.883198],[-88.490123,18.486831],[-88.300031,18.499982],[-88.296336,18.353273],[-88.106813,18.348674],[-88.123479,18.076675],[-88.285355,17.644143],[-88.197867,17.489475],[-88.302641,17.131694],[-88.239518,17.036066],[-88.355428,16.530774],[-88.551825,16.265467],[-88.732434,16.233635],[-88.930613,15.887273],[-89.229122,15.886938],[-89.150806,17.015577],[-89.14308,17.808319]]]}}, {"type":"Feature","id":"BMU","properties":{"name":"Bermuda"},"geometry":{"type":"Polygon","coordinates":[[[-64.7799734332998,32.3072000581802],[-64.7873319183061,32.3039237143428],[-64.7946942710173,32.3032682700388],[-64.8094297981283,32.3098175728414],[-64.8167896352437,32.3058845718466],[-64.8101968029642,32.3022833180511],[-64.7962291465484,32.2934409732427],[-64.7815086336978,32.2868973114514],[-64.7997025513437,32.2796896417328],[-64.8066707691087,32.2747767569465],[-64.8225587873683,32.2669111289395],[-64.8287548840306,32.2669075473817],[-64.8306732143498,32.2583944840235],[-64.8399924854972,32.254782282336],[-64.8566090462354,32.2547740387514],[-64.8682296789446,32.2616393614322],[-64.8628241459563,32.2724481933959],[-64.8748651338951,32.2757120264753],[-64.8717752856644,32.2819371582026],[-64.8671422127295,32.2930760547989],[-64.8559068764437,32.2960321186471],[-64.8597429072279,32.3015842021933],[-64.8439233486717,32.3140553852543],[-64.8350242329311,32.3242161760006],[-64.8338690593672,32.3294587561557],[-64.8520298651164,32.3110911879954],[-64.8635922932573,32.3048469433363],[-64.8686668994079,32.30910745083],[-64.8721354593415,32.3041908606301],[-64.8779667328485,32.3038632800462],[-64.8780046844321,32.2907757831692],[-64.8849776658292,32.2819261366004],[-64.8783230004629,32.2613001418681],[-64.863194968877,32.2465799485801],[-64.8519819555722,32.2485519134663],[-64.842311980074,32.2492123317296],[-64.8388242605209,32.2475773472534],[-64.8334002575532,32.2462714714698],[-64.8256389530584,32.2472637398594],[-64.8205697556026,32.2531698880328],[-64.8105087275579,32.2561208974156],[-64.7900177727338,32.2659446936992],[-64.7745415970416,32.2718413023427],[-64.7644742436426,32.2855931353214],[-64.7551803442276,32.2908326702531],[-64.7423982971436,32.2996734994024],[-64.7206991797682,32.3137542201258],[-64.7117851247134,32.3176823360806],[-64.6962778813133,32.3275029115532],[-64.6768921127452,32.3324095397555],[-64.6567136927777,32.3451776458469],[-64.6532168823499,32.3494356627941],[-64.6605720384429,32.3589423487763],[-64.65125819471,32.3615600906466],[-64.6462011670816,32.36975169749],[-64.6613227512832,32.3763135008721],[-64.6690666074397,32.388444543924],[-64.6834270548595,32.3854968316788],[-64.6954617672714,32.3763221285869],[-64.70438689565,32.3704254760469],[-64.7117569982798,32.368132600249],[-64.7061764744404,32.3600110593559],[-64.700531552697,32.3590601356818],[-64.6940348033967,32.3640708659835],[-64.6895164826082,32.3633598579866],[-64.6864150099255,32.3547797587266],[-64.6824635995504,32.3540628176846],[-64.6835876652835,32.3626447677968],[-64.6801998697415,32.3631199096979],[-64.6672170444687,32.3597751617473],[-64.6598811264978,32.3497625771755],[-64.6737331235384,32.3390281851635],[-64.6887090648183,32.3342439408053],[-64.706732854446,32.3429010723036],[-64.7149301576112,32.3552188753513],[-64.7185967666669,32.3552239212394],[-64.7214189847314,32.3518830231342],[-64.7270616067222,32.3466461715475],[-64.734962460882,32.3442819830499],[-64.7383521549094,32.3407216514918],[-64.7411729976333,32.3311790864627],[-64.7423019216485,32.323311561213],[-64.7462482354281,32.318538611581],[-64.7566773739613,32.3130509130175],[-64.768738200563,32.3088369816572],[-64.7799734332998,32.3072000581802]]]}}, {"type":"Feature","id":"BOL","properties":{"name":"Bolivia"},"geometry":{"type":"Polygon","coordinates":[[[-62.846468,-22.034985],[-63.986838,-21.993644],[-64.377021,-22.798091],[-64.964892,-22.075862],[-66.273339,-21.83231],[-67.106674,-22.735925],[-67.82818,-22.872919],[-68.219913,-21.494347],[-68.757167,-20.372658],[-68.442225,-19.405068],[-68.966818,-18.981683],[-69.100247,-18.260125],[-69.590424,-17.580012],[-68.959635,-16.500698],[-69.389764,-15.660129],[-69.160347,-15.323974],[-69.339535,-14.953195],[-68.948887,-14.453639],[-68.929224,-13.602684],[-68.88008,-12.899729],[-68.66508,-12.5613],[-69.529678,-10.951734],[-68.786158,-11.03638],[-68.271254,-11.014521],[-68.048192,-10.712059],[-67.173801,-10.306812],[-66.646908,-9.931331],[-65.338435,-9.761988],[-65.444837,-10.511451],[-65.321899,-10.895872],[-65.402281,-11.56627],[-64.316353,-12.461978],[-63.196499,-12.627033],[-62.80306,-13.000653],[-62.127081,-13.198781],[-61.713204,-13.489202],[-61.084121,-13.479384],[-60.503304,-13.775955],[-60.459198,-14.354007],[-60.264326,-14.645979],[-60.251149,-15.077219],[-60.542966,-15.09391],[-60.15839,-16.258284],[-58.24122,-16.299573],[-58.388058,-16.877109],[-58.280804,-17.27171],[-57.734558,-17.552468],[-57.498371,-18.174188],[-57.676009,-18.96184],[-57.949997,-19.400004],[-57.853802,-19.969995],[-58.166392,-20.176701],[-58.183471,-19.868399],[-59.115042,-19.356906],[-60.043565,-19.342747],[-61.786326,-19.633737],[-62.265961,-20.513735],[-62.291179,-21.051635],[-62.685057,-22.249029],[-62.846468,-22.034985]]]}}, {"type":"Feature","id":"BRA","properties":{"name":"Brazil"},"geometry":{"type":"Polygon","coordinates":[[[-57.625133,-30.216295],[-56.2909,-28.852761],[-55.162286,-27.881915],[-54.490725,-27.474757],[-53.648735,-26.923473],[-53.628349,-26.124865],[-54.13005,-25.547639],[-54.625291,-25.739255],[-54.428946,-25.162185],[-54.293476,-24.5708],[-54.29296,-24.021014],[-54.652834,-23.839578],[-55.027902,-24.001274],[-55.400747,-23.956935],[-55.517639,-23.571998],[-55.610683,-22.655619],[-55.797958,-22.35693],[-56.473317,-22.0863],[-56.88151,-22.282154],[-57.937156,-22.090176],[-57.870674,-20.732688],[-58.166392,-20.176701],[-57.853802,-19.969995],[-57.949997,-19.400004],[-57.676009,-18.96184],[-57.498371,-18.174188],[-57.734558,-17.552468],[-58.280804,-17.27171],[-58.388058,-16.877109],[-58.24122,-16.299573],[-60.15839,-16.258284],[-60.542966,-15.09391],[-60.251149,-15.077219],[-60.264326,-14.645979],[-60.459198,-14.354007],[-60.503304,-13.775955],[-61.084121,-13.479384],[-61.713204,-13.489202],[-62.127081,-13.198781],[-62.80306,-13.000653],[-63.196499,-12.627033],[-64.316353,-12.461978],[-65.402281,-11.56627],[-65.321899,-10.895872],[-65.444837,-10.511451],[-65.338435,-9.761988],[-66.646908,-9.931331],[-67.173801,-10.306812],[-68.048192,-10.712059],[-68.271254,-11.014521],[-68.786158,-11.03638],[-69.529678,-10.951734],[-70.093752,-11.123972],[-70.548686,-11.009147],[-70.481894,-9.490118],[-71.302412,-10.079436],[-72.184891,-10.053598],[-72.563033,-9.520194],[-73.226713,-9.462213],[-73.015383,-9.032833],[-73.571059,-8.424447],[-73.987235,-7.52383],[-73.723401,-7.340999],[-73.724487,-6.918595],[-73.120027,-6.629931],[-73.219711,-6.089189],[-72.964507,-5.741251],[-72.891928,-5.274561],[-71.748406,-4.593983],[-70.928843,-4.401591],[-70.794769,-4.251265],[-69.893635,-4.298187],[-69.444102,-1.556287],[-69.420486,-1.122619],[-69.577065,-0.549992],[-70.020656,-0.185156],[-70.015566,0.541414],[-69.452396,0.706159],[-69.252434,0.602651],[-69.218638,0.985677],[-69.804597,1.089081],[-69.816973,1.714805],[-67.868565,1.692455],[-67.53781,2.037163],[-67.259998,1.719999],[-67.065048,1.130112],[-66.876326,1.253361],[-66.325765,0.724452],[-65.548267,0.789254],[-65.354713,1.095282],[-64.611012,1.328731],[-64.199306,1.492855],[-64.083085,1.916369],[-63.368788,2.2009],[-63.422867,2.411068],[-64.269999,2.497006],[-64.408828,3.126786],[-64.368494,3.79721],[-64.816064,4.056445],[-64.628659,4.148481],[-63.888343,4.02053],[-63.093198,3.770571],[-62.804533,4.006965],[-62.08543,4.162124],[-60.966893,4.536468],[-60.601179,4.918098],[-60.733574,5.200277],[-60.213683,5.244486],[-59.980959,5.014061],[-60.111002,4.574967],[-59.767406,4.423503],[-59.53804,3.958803],[-59.815413,3.606499],[-59.974525,2.755233],[-59.718546,2.24963],[-59.646044,1.786894],[-59.030862,1.317698],[-58.540013,1.268088],[-58.429477,1.463942],[-58.11345,1.507195],[-57.660971,1.682585],[-57.335823,1.948538],[-56.782704,1.863711],[-56.539386,1.899523],[-55.995698,1.817667],[-55.9056,2.021996],[-56.073342,2.220795],[-55.973322,2.510364],[-55.569755,2.421506],[-55.097587,2.523748],[-54.524754,2.311849],[-54.088063,2.105557],[-53.778521,2.376703],[-53.554839,2.334897],[-53.418465,2.053389],[-52.939657,2.124858],[-52.556425,2.504705],[-52.249338,3.241094],[-51.657797,4.156232],[-51.317146,4.203491],[-51.069771,3.650398],[-50.508875,1.901564],[-49.974076,1.736483],[-49.947101,1.04619],[-50.699251,0.222984],[-50.388211,-0.078445],[-48.620567,-0.235489],[-48.584497,-1.237805],[-47.824956,-0.581618],[-46.566584,-0.941028],[-44.905703,-1.55174],[-44.417619,-2.13775],[-44.581589,-2.691308],[-43.418791,-2.38311],[-41.472657,-2.912018],[-39.978665,-2.873054],[-38.500383,-3.700652],[-37.223252,-4.820946],[-36.452937,-5.109404],[-35.597796,-5.149504],[-35.235389,-5.464937],[-34.89603,-6.738193],[-34.729993,-7.343221],[-35.128212,-8.996401],[-35.636967,-9.649282],[-37.046519,-11.040721],[-37.683612,-12.171195],[-38.423877,-13.038119],[-38.673887,-13.057652],[-38.953276,-13.79337],[-38.882298,-15.667054],[-39.161092,-17.208407],[-39.267339,-17.867746],[-39.583521,-18.262296],[-39.760823,-19.599113],[-40.774741,-20.904512],[-40.944756,-21.937317],[-41.754164,-22.370676],[-41.988284,-22.97007],[-43.074704,-22.967693],[-44.647812,-23.351959],[-45.352136,-23.796842],[-46.472093,-24.088969],[-47.648972,-24.885199],[-48.495458,-25.877025],[-48.641005,-26.623698],[-48.474736,-27.175912],[-48.66152,-28.186135],[-48.888457,-28.674115],[-49.587329,-29.224469],[-50.696874,-30.984465],[-51.576226,-31.777698],[-52.256081,-32.24537],[-52.7121,-33.196578],[-53.373662,-33.768378],[-53.650544,-33.202004],[-53.209589,-32.727666],[-53.787952,-32.047243],[-54.572452,-31.494511],[-55.60151,-30.853879],[-55.973245,-30.883076],[-56.976026,-30.109686],[-57.625133,-30.216295]]]}}, {"type":"Feature","id":"BRN","properties":{"name":"Brunei"},"geometry":{"type":"Polygon","coordinates":[[[114.204017,4.525874],[114.599961,4.900011],[115.45071,5.44773],[115.4057,4.955228],[115.347461,4.316636],[114.869557,4.348314],[114.659596,4.007637],[114.204017,4.525874]]]}}, {"type":"Feature","id":"BTN","properties":{"name":"Bhutan"},"geometry":{"type":"Polygon","coordinates":[[[91.696657,27.771742],[92.103712,27.452614],[92.033484,26.83831],[91.217513,26.808648],[90.373275,26.875724],[89.744528,26.719403],[88.835643,27.098966],[88.814248,27.299316],[89.47581,28.042759],[90.015829,28.296439],[90.730514,28.064954],[91.258854,28.040614],[91.696657,27.771742]]]}}, {"type":"Feature","id":"BWA","properties":{"name":"Botswana"},"geometry":{"type":"Polygon","coordinates":[[[25.649163,-18.536026],[25.850391,-18.714413],[26.164791,-19.293086],[27.296505,-20.39152],[27.724747,-20.499059],[27.727228,-20.851802],[28.02137,-21.485975],[28.794656,-21.639454],[29.432188,-22.091313],[28.017236,-22.827754],[27.11941,-23.574323],[26.786407,-24.240691],[26.485753,-24.616327],[25.941652,-24.696373],[25.765849,-25.174845],[25.664666,-25.486816],[25.025171,-25.71967],[24.211267,-25.670216],[23.73357,-25.390129],[23.312097,-25.26869],[22.824271,-25.500459],[22.579532,-25.979448],[22.105969,-26.280256],[21.605896,-26.726534],[20.889609,-26.828543],[20.66647,-26.477453],[20.758609,-25.868136],[20.165726,-24.917962],[19.895768,-24.76779],[19.895458,-21.849157],[20.881134,-21.814327],[20.910641,-18.252219],[21.65504,-18.219146],[23.196858,-17.869038],[23.579006,-18.281261],[24.217365,-17.889347],[24.520705,-17.887125],[25.084443,-17.661816],[25.264226,-17.73654],[25.649163,-18.536026]]]}}, {"type":"Feature","id":"CAF","properties":{"name":"Central African Republic"},"geometry":{"type":"Polygon","coordinates":[[[15.27946,7.421925],[16.106232,7.497088],[16.290562,7.754307],[16.456185,7.734774],[16.705988,7.508328],[17.96493,7.890914],[18.389555,8.281304],[18.911022,8.630895],[18.81201,8.982915],[19.094008,9.074847],[20.059685,9.012706],[21.000868,9.475985],[21.723822,10.567056],[22.231129,10.971889],[22.864165,11.142395],[22.977544,10.714463],[23.554304,10.089255],[23.55725,9.681218],[23.394779,9.265068],[23.459013,8.954286],[23.805813,8.666319],[24.567369,8.229188],[25.114932,7.825104],[25.124131,7.500085],[25.796648,6.979316],[26.213418,6.546603],[26.465909,5.946717],[27.213409,5.550953],[27.374226,5.233944],[27.044065,5.127853],[26.402761,5.150875],[25.650455,5.256088],[25.278798,5.170408],[25.128833,4.927245],[24.805029,4.897247],[24.410531,5.108784],[23.297214,4.609693],[22.84148,4.710126],[22.704124,4.633051],[22.405124,4.02916],[21.659123,4.224342],[20.927591,4.322786],[20.290679,4.691678],[19.467784,5.031528],[18.932312,4.709506],[18.542982,4.201785],[18.453065,3.504386],[17.8099,3.560196],[17.133042,3.728197],[16.537058,3.198255],[16.012852,2.26764],[15.907381,2.557389],[15.862732,3.013537],[15.405396,3.335301],[15.03622,3.851367],[14.950953,4.210389],[14.478372,4.732605],[14.558936,5.030598],[14.459407,5.451761],[14.53656,6.226959],[14.776545,6.408498],[15.27946,7.421925]]]}}, {"type":"Feature","id":"CAN","properties":{"name":"Canada"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-63.6645,46.55001],[-62.9393,46.41587],[-62.01208,46.44314],[-62.50391,46.03339],[-62.87433,45.96818],[-64.1428,46.39265],[-64.39261,46.72747],[-64.01486,47.03601],[-63.6645,46.55001]]],[[[-61.806305,49.10506],[-62.29318,49.08717],[-63.58926,49.40069],[-64.51912,49.87304],[-64.17322,49.95718],[-62.85829,49.70641],[-61.835585,49.28855],[-61.806305,49.10506]]],[[[-123.510002,48.510011],[-124.012891,48.370846],[-125.655013,48.825005],[-125.954994,49.179996],[-126.850004,49.53],[-127.029993,49.814996],[-128.059336,49.994959],[-128.444584,50.539138],[-128.358414,50.770648],[-127.308581,50.552574],[-126.695001,50.400903],[-125.755007,50.295018],[-125.415002,49.950001],[-124.920768,49.475275],[-123.922509,49.062484],[-123.510002,48.510011]]],[[[-56.134036,50.68701],[-56.795882,49.812309],[-56.143105,50.150117],[-55.471492,49.935815],[-55.822401,49.587129],[-54.935143,49.313011],[-54.473775,49.556691],[-53.476549,49.249139],[-53.786014,48.516781],[-53.086134,48.687804],[-52.958648,48.157164],[-52.648099,47.535548],[-53.069158,46.655499],[-53.521456,46.618292],[-54.178936,46.807066],[-53.961869,47.625207],[-54.240482,47.752279],[-55.400773,46.884994],[-55.997481,46.91972],[-55.291219,47.389562],[-56.250799,47.632545],[-57.325229,47.572807],[-59.266015,47.603348],[-59.419494,47.899454],[-58.796586,48.251525],[-59.231625,48.523188],[-58.391805,49.125581],[-57.35869,50.718274],[-56.73865,51.287438],[-55.870977,51.632094],[-55.406974,51.588273],[-55.600218,51.317075],[-56.134036,50.68701]]],[[[-132.710008,54.040009],[-131.74999,54.120004],[-132.04948,52.984621],[-131.179043,52.180433],[-131.57783,52.182371],[-132.180428,52.639707],[-132.549992,53.100015],[-133.054611,53.411469],[-133.239664,53.85108],[-133.180004,54.169975],[-132.710008,54.040009]]],[[[-79.26582,62.158675],[-79.65752,61.63308],[-80.09956,61.7181],[-80.36215,62.01649],[-80.315395,62.085565],[-79.92939,62.3856],[-79.52002,62.36371],[-79.26582,62.158675]]],[[[-81.89825,62.7108],[-83.06857,62.15922],[-83.77462,62.18231],[-83.99367,62.4528],[-83.25048,62.91409],[-81.87699,62.90458],[-81.89825,62.7108]]],[[[-85.161308,65.657285],[-84.975764,65.217518],[-84.464012,65.371772],[-83.882626,65.109618],[-82.787577,64.766693],[-81.642014,64.455136],[-81.55344,63.979609],[-80.817361,64.057486],[-80.103451,63.725981],[-80.99102,63.411246],[-82.547178,63.651722],[-83.108798,64.101876],[-84.100417,63.569712],[-85.523405,63.052379],[-85.866769,63.637253],[-87.221983,63.541238],[-86.35276,64.035833],[-86.224886,64.822917],[-85.883848,65.738778],[-85.161308,65.657285]]],[[[-75.86588,67.14886],[-76.98687,67.09873],[-77.2364,67.58809],[-76.81166,68.14856],[-75.89521,68.28721],[-75.1145,68.01036],[-75.10333,67.58202],[-75.21597,67.44425],[-75.86588,67.14886]]],[[[-95.647681,69.10769],[-96.269521,68.75704],[-97.617401,69.06003],[-98.431801,68.9507],[-99.797401,69.40003],[-98.917401,69.71003],[-98.218261,70.14354],[-97.157401,69.86003],[-96.557401,69.68003],[-96.257401,69.49003],[-95.647681,69.10769]]],[[[-90.5471,69.49766],[-90.55151,68.47499],[-89.21515,69.25873],[-88.01966,68.61508],[-88.31749,67.87338],[-87.35017,67.19872],[-86.30607,67.92146],[-85.57664,68.78456],[-85.52197,69.88211],[-84.10081,69.80539],[-82.62258,69.65826],[-81.28043,69.16202],[-81.2202,68.66567],[-81.96436,68.13253],[-81.25928,67.59716],[-81.38653,67.11078],[-83.34456,66.41154],[-84.73542,66.2573],[-85.76943,66.55833],[-86.0676,66.05625],[-87.03143,65.21297],[-87.32324,64.77563],[-88.48296,64.09897],[-89.91444,64.03273],[-90.70398,63.61017],[-90.77004,62.96021],[-91.93342,62.83508],[-93.15698,62.02469],[-94.24153,60.89865],[-94.62931,60.11021],[-94.6846,58.94882],[-93.21502,58.78212],[-92.76462,57.84571],[-92.29703,57.08709],[-90.89769,57.28468],[-89.03953,56.85172],[-88.03978,56.47162],[-87.32421,55.99914],[-86.07121,55.72383],[-85.01181,55.3026],[-83.36055,55.24489],[-82.27285,55.14832],[-82.4362,54.28227],[-82.12502,53.27703],[-81.40075,52.15788],[-79.91289,51.20842],[-79.14301,51.53393],[-78.60191,52.56208],[-79.12421,54.14145],[-79.82958,54.66772],[-78.22874,55.13645],[-77.0956,55.83741],[-76.54137,56.53423],[-76.62319,57.20263],[-77.30226,58.05209],[-78.51688,58.80458],[-77.33676,59.85261],[-77.77272,60.75788],[-78.10687,62.31964],[-77.41067,62.55053],[-75.69621,62.2784],[-74.6682,62.18111],[-73.83988,62.4438],[-72.90853,62.10507],[-71.67708,61.52535],[-71.37369,61.13717],[-69.59042,61.06141],[-69.62033,60.22125],[-69.2879,58.95736],[-68.37455,58.80106],[-67.64976,58.21206],[-66.20178,58.76731],[-65.24517,59.87071],[-64.58352,60.33558],[-63.80475,59.4426],[-62.50236,58.16708],[-61.39655,56.96745],[-61.79866,56.33945],[-60.46853,55.77548],[-59.56962,55.20407],[-57.97508,54.94549],[-57.3332,54.6265],[-56.93689,53.78032],[-56.15811,53.64749],[-55.75632,53.27036],[-55.68338,52.14664],[-56.40916,51.7707],[-57.12691,51.41972],[-58.77482,51.0643],[-60.03309,50.24277],[-61.72366,50.08046],[-63.86251,50.29099],[-65.36331,50.2982],[-66.39905,50.22897],[-67.23631,49.51156],[-68.51114,49.06836],[-69.95362,47.74488],[-71.10458,46.82171],[-70.25522,46.98606],[-68.65,48.3],[-66.55243,49.1331],[-65.05626,49.23278],[-64.17099,48.74248],[-65.11545,48.07085],[-64.79854,46.99297],[-64.47219,46.23849],[-63.17329,45.73902],[-61.52072,45.88377],[-60.51815,47.00793],[-60.4486,46.28264],[-59.80287,45.9204],[-61.03988,45.26525],[-63.25471,44.67014],[-64.24656,44.26553],[-65.36406,43.54523],[-66.1234,43.61867],[-66.16173,44.46512],[-64.42549,45.29204],[-66.02605,45.25931],[-67.13741,45.13753],[-67.79134,45.70281],[-67.79046,47.06636],[-68.23444,47.35486],[-68.905,47.185],[-69.237216,47.447781],[-69.99997,46.69307],[-70.305,45.915],[-70.66,45.46],[-71.08482,45.30524],[-71.405,45.255],[-71.50506,45.0082],[-73.34783,45.00738],[-74.867,45.00048],[-75.31821,44.81645],[-76.375,44.09631],[-76.5,44.018459],[-76.820034,43.628784],[-77.737885,43.629056],[-78.72028,43.625089],[-79.171674,43.466339],[-79.01,43.27],[-78.92,42.965],[-78.939362,42.863611],[-80.247448,42.3662],[-81.277747,42.209026],[-82.439278,41.675105],[-82.690089,41.675105],[-83.02981,41.832796],[-83.142,41.975681],[-83.12,42.08],[-82.9,42.43],[-82.43,42.98],[-82.137642,43.571088],[-82.337763,44.44],[-82.550925,45.347517],[-83.592851,45.816894],[-83.469551,45.994686],[-83.616131,46.116927],[-83.890765,46.116927],[-84.091851,46.275419],[-84.14212,46.512226],[-84.3367,46.40877],[-84.6049,46.4396],[-84.543749,46.538684],[-84.779238,46.637102],[-84.87608,46.900083],[-85.652363,47.220219],[-86.461991,47.553338],[-87.439793,47.94],[-88.378114,48.302918],[-89.272917,48.019808],[-89.6,48.01],[-90.83,48.27],[-91.64,48.14],[-92.61,48.45],[-93.63087,48.60926],[-94.32914,48.67074],[-94.64,48.84],[-94.81758,49.38905],[-95.15609,49.38425],[-95.15907,49],[-97.22872,49.0007],[-100.65,49],[-104.04826,48.99986],[-107.05,49],[-110.05,49],[-113,49],[-116.04818,49],[-117.03121,49],[-120,49],[-122.84,49],[-122.97421,49.002538],[-124.91024,49.98456],[-125.62461,50.41656],[-127.43561,50.83061],[-127.99276,51.71583],[-127.85032,52.32961],[-129.12979,52.75538],[-129.30523,53.56159],[-130.51497,54.28757],[-130.53611,54.80278],[-129.98,55.285],[-130.00778,55.91583],[-131.70781,56.55212],[-132.73042,57.69289],[-133.35556,58.41028],[-134.27111,58.86111],[-134.945,59.27056],[-135.47583,59.78778],[-136.47972,59.46389],[-137.4525,58.905],[-138.34089,59.56211],[-139.039,60],[-140.013,60.27682],[-140.99778,60.30639],[-140.9925,66.00003],[-140.986,69.712],[-139.12052,69.47102],[-137.54636,68.99002],[-136.50358,68.89804],[-135.62576,69.31512],[-134.41464,69.62743],[-132.92925,69.50534],[-131.43136,69.94451],[-129.79471,70.19369],[-129.10773,69.77927],[-128.36156,70.01286],[-128.13817,70.48384],[-127.44712,70.37721],[-125.75632,69.48058],[-124.42483,70.1584],[-124.28968,69.39969],[-123.06108,69.56372],[-122.6835,69.85553],[-121.47226,69.79778],[-119.94288,69.37786],[-117.60268,69.01128],[-116.22643,68.84151],[-115.2469,68.90591],[-113.89794,68.3989],[-115.30489,67.90261],[-113.49727,67.68815],[-110.798,67.80612],[-109.94619,67.98104],[-108.8802,67.38144],[-107.79239,67.88736],[-108.81299,68.31164],[-108.16721,68.65392],[-106.95,68.7],[-106.15,68.8],[-105.34282,68.56122],[-104.33791,68.018],[-103.22115,68.09775],[-101.45433,67.64689],[-99.90195,67.80566],[-98.4432,67.78165],[-98.5586,68.40394],[-97.66948,68.57864],[-96.11991,68.23939],[-96.12588,67.29338],[-95.48943,68.0907],[-94.685,68.06383],[-94.23282,69.06903],[-95.30408,69.68571],[-96.47131,70.08976],[-96.39115,71.19482],[-95.2088,71.92053],[-93.88997,71.76015],[-92.87818,71.31869],[-91.51964,70.19129],[-92.40692,69.69997],[-90.5471,69.49766]]],[[[-114.16717,73.12145],[-114.66634,72.65277],[-112.44102,72.9554],[-111.05039,72.4504],[-109.92035,72.96113],[-109.00654,72.63335],[-108.18835,71.65089],[-107.68599,72.06548],[-108.39639,73.08953],[-107.51645,73.23598],[-106.52259,73.07601],[-105.40246,72.67259],[-104.77484,71.6984],[-104.46476,70.99297],[-102.78537,70.49776],[-100.98078,70.02432],[-101.08929,69.58447],[-102.73116,69.50402],[-102.09329,69.11962],[-102.43024,68.75282],[-104.24,68.91],[-105.96,69.18],[-107.12254,69.11922],[-109,68.78],[-111.534149,68.630059],[-113.3132,68.53554],[-113.85496,69.00744],[-115.22,69.28],[-116.10794,69.16821],[-117.34,69.96],[-116.67473,70.06655],[-115.13112,70.2373],[-113.72141,70.19237],[-112.4161,70.36638],[-114.35,70.6],[-116.48684,70.52045],[-117.9048,70.54056],[-118.43238,70.9092],[-116.11311,71.30918],[-117.65568,71.2952],[-119.40199,71.55859],[-118.56267,72.30785],[-117.86642,72.70594],[-115.18909,73.31459],[-114.16717,73.12145]]],[[[-104.5,73.42],[-105.38,72.76],[-106.94,73.46],[-106.6,73.6],[-105.26,73.64],[-104.5,73.42]]],[[[-76.34,73.102685],[-76.251404,72.826385],[-77.314438,72.855545],[-78.39167,72.876656],[-79.486252,72.742203],[-79.775833,72.802902],[-80.876099,73.333183],[-80.833885,73.693184],[-80.353058,73.75972],[-78.064438,73.651932],[-76.34,73.102685]]],[[[-86.562179,73.157447],[-85.774371,72.534126],[-84.850112,73.340278],[-82.31559,73.750951],[-80.600088,72.716544],[-80.748942,72.061907],[-78.770639,72.352173],[-77.824624,72.749617],[-75.605845,72.243678],[-74.228616,71.767144],[-74.099141,71.33084],[-72.242226,71.556925],[-71.200015,70.920013],[-68.786054,70.525024],[-67.91497,70.121948],[-66.969033,69.186087],[-68.805123,68.720198],[-66.449866,68.067163],[-64.862314,67.847539],[-63.424934,66.928473],[-61.851981,66.862121],[-62.163177,66.160251],[-63.918444,64.998669],[-65.14886,65.426033],[-66.721219,66.388041],[-68.015016,66.262726],[-68.141287,65.689789],[-67.089646,65.108455],[-65.73208,64.648406],[-65.320168,64.382737],[-64.669406,63.392927],[-65.013804,62.674185],[-66.275045,62.945099],[-68.783186,63.74567],[-67.369681,62.883966],[-66.328297,62.280075],[-66.165568,61.930897],[-68.877367,62.330149],[-71.023437,62.910708],[-72.235379,63.397836],[-71.886278,63.679989],[-73.378306,64.193963],[-74.834419,64.679076],[-74.818503,64.389093],[-77.70998,64.229542],[-78.555949,64.572906],[-77.897281,65.309192],[-76.018274,65.326969],[-73.959795,65.454765],[-74.293883,65.811771],[-73.944912,66.310578],[-72.651167,67.284576],[-72.92606,67.726926],[-73.311618,68.069437],[-74.843307,68.554627],[-76.869101,68.894736],[-76.228649,69.147769],[-77.28737,69.76954],[-78.168634,69.826488],[-78.957242,70.16688],[-79.492455,69.871808],[-81.305471,69.743185],[-84.944706,69.966634],[-87.060003,70.260001],[-88.681713,70.410741],[-89.51342,70.762038],[-88.467721,71.218186],[-89.888151,71.222552],[-90.20516,72.235074],[-89.436577,73.129464],[-88.408242,73.537889],[-85.826151,73.803816],[-86.562179,73.157447]]],[[[-100.35642,73.84389],[-99.16387,73.63339],[-97.38,73.76],[-97.12,73.47],[-98.05359,72.99052],[-96.54,72.56],[-96.72,71.66],[-98.35966,71.27285],[-99.32286,71.35639],[-100.01482,71.73827],[-102.5,72.51],[-102.48,72.83],[-100.43836,72.70588],[-101.54,73.36],[-100.35642,73.84389]]],[[[-93.196296,72.771992],[-94.269047,72.024596],[-95.409856,72.061881],[-96.033745,72.940277],[-96.018268,73.43743],[-95.495793,73.862417],[-94.503658,74.134907],[-92.420012,74.100025],[-90.509793,73.856732],[-92.003965,72.966244],[-93.196296,72.771992]]],[[[-120.46,71.383602],[-123.09219,70.90164],[-123.62,71.34],[-125.928949,71.868688],[-125.5,72.292261],[-124.80729,73.02256],[-123.94,73.68],[-124.91775,74.29275],[-121.53788,74.44893],[-120.10978,74.24135],[-117.55564,74.18577],[-116.58442,73.89607],[-115.51081,73.47519],[-116.76794,73.22292],[-119.22,72.52],[-120.46,71.82],[-120.46,71.383602]]],[[[-93.612756,74.979997],[-94.156909,74.592347],[-95.608681,74.666864],[-96.820932,74.927623],[-96.288587,75.377828],[-94.85082,75.647218],[-93.977747,75.29649],[-93.612756,74.979997]]],[[[-98.5,76.72],[-97.735585,76.25656],[-97.704415,75.74344],[-98.16,75],[-99.80874,74.89744],[-100.88366,75.05736],[-100.86292,75.64075],[-102.50209,75.5638],[-102.56552,76.3366],[-101.48973,76.30537],[-99.98349,76.64634],[-98.57699,76.58859],[-98.5,76.72]]],[[[-108.21141,76.20168],[-107.81943,75.84552],[-106.92893,76.01282],[-105.881,75.9694],[-105.70498,75.47951],[-106.31347,75.00527],[-109.7,74.85],[-112.22307,74.41696],[-113.74381,74.39427],[-113.87135,74.72029],[-111.79421,75.1625],[-116.31221,75.04343],[-117.7104,75.2222],[-116.34602,76.19903],[-115.40487,76.47887],[-112.59056,76.14134],[-110.81422,75.54919],[-109.0671,75.47321],[-110.49726,76.42982],[-109.5811,76.79417],[-108.54859,76.67832],[-108.21141,76.20168]]],[[[-94.684086,77.097878],[-93.573921,76.776296],[-91.605023,76.778518],[-90.741846,76.449597],[-90.969661,76.074013],[-89.822238,75.847774],[-89.187083,75.610166],[-87.838276,75.566189],[-86.379192,75.482421],[-84.789625,75.699204],[-82.753445,75.784315],[-81.128531,75.713983],[-80.057511,75.336849],[-79.833933,74.923127],[-80.457771,74.657304],[-81.948843,74.442459],[-83.228894,74.564028],[-86.097452,74.410032],[-88.15035,74.392307],[-89.764722,74.515555],[-92.422441,74.837758],[-92.768285,75.38682],[-92.889906,75.882655],[-93.893824,76.319244],[-95.962457,76.441381],[-97.121379,76.751078],[-96.745123,77.161389],[-94.684086,77.097878]]],[[[-116.198587,77.645287],[-116.335813,76.876962],[-117.106051,76.530032],[-118.040412,76.481172],[-119.899318,76.053213],[-121.499995,75.900019],[-122.854924,76.116543],[-122.854925,76.116543],[-121.157535,76.864508],[-119.103939,77.51222],[-117.570131,77.498319],[-116.198587,77.645287]]],[[[-93.840003,77.519997],[-94.295608,77.491343],[-96.169654,77.555111],[-96.436304,77.834629],[-94.422577,77.820005],[-93.720656,77.634331],[-93.840003,77.519997]]],[[[-110.186938,77.697015],[-112.051191,77.409229],[-113.534279,77.732207],[-112.724587,78.05105],[-111.264443,78.152956],[-109.854452,77.996325],[-110.186938,77.697015]]],[[[-109.663146,78.601973],[-110.881314,78.40692],[-112.542091,78.407902],[-112.525891,78.550555],[-111.50001,78.849994],[-110.963661,78.804441],[-109.663146,78.601973]]],[[[-95.830295,78.056941],[-97.309843,77.850597],[-98.124289,78.082857],[-98.552868,78.458105],[-98.631984,78.87193],[-97.337231,78.831984],[-96.754399,78.765813],[-95.559278,78.418315],[-95.830295,78.056941]]],[[[-100.060192,78.324754],[-99.670939,77.907545],[-101.30394,78.018985],[-102.949809,78.343229],[-105.176133,78.380332],[-104.210429,78.67742],[-105.41958,78.918336],[-105.492289,79.301594],[-103.529282,79.165349],[-100.825158,78.800462],[-100.060192,78.324754]]],[[[-87.02,79.66],[-85.81435,79.3369],[-87.18756,79.0393],[-89.03535,78.28723],[-90.80436,78.21533],[-92.87669,78.34333],[-93.95116,78.75099],[-93.93574,79.11373],[-93.14524,79.3801],[-94.974,79.37248],[-96.07614,79.70502],[-96.70972,80.15777],[-96.01644,80.60233],[-95.32345,80.90729],[-94.29843,80.97727],[-94.73542,81.20646],[-92.40984,81.25739],[-91.13289,80.72345],[-89.45,80.509322],[-87.81,80.32],[-87.02,79.66]]],[[[-68.5,83.106322],[-65.82735,83.02801],[-63.68,82.9],[-61.85,82.6286],[-61.89388,82.36165],[-64.334,81.92775],[-66.75342,81.72527],[-67.65755,81.50141],[-65.48031,81.50657],[-67.84,80.9],[-69.4697,80.61683],[-71.18,79.8],[-73.2428,79.63415],[-73.88,79.430162],[-76.90773,79.32309],[-75.52924,79.19766],[-76.22046,79.01907],[-75.39345,78.52581],[-76.34354,78.18296],[-77.88851,77.89991],[-78.36269,77.50859],[-79.75951,77.20968],[-79.61965,76.98336],[-77.91089,77.022045],[-77.88911,76.777955],[-80.56125,76.17812],[-83.17439,76.45403],[-86.11184,76.29901],[-87.6,76.42],[-89.49068,76.47239],[-89.6161,76.95213],[-87.76739,77.17833],[-88.26,77.9],[-87.65,77.970222],[-84.97634,77.53873],[-86.34,78.18],[-87.96192,78.37181],[-87.15198,78.75867],[-85.37868,78.9969],[-85.09495,79.34543],[-86.50734,79.73624],[-86.93179,80.25145],[-84.19844,80.20836],[-83.408696,80.1],[-81.84823,80.46442],[-84.1,80.58],[-87.59895,80.51627],[-89.36663,80.85569],[-90.2,81.26],[-91.36786,81.5531],[-91.58702,81.89429],[-90.1,82.085],[-88.93227,82.11751],[-86.97024,82.27961],[-85.5,82.652273],[-84.260005,82.6],[-83.18,82.32],[-82.42,82.86],[-81.1,83.02],[-79.30664,83.13056],[-76.25,83.172059],[-75.71878,83.06404],[-72.83153,83.23324],[-70.665765,83.169781],[-68.5,83.106322]]]]}}, {"type":"Feature","id":"CHE","properties":{"name":"Switzerland"},"geometry":{"type":"Polygon","coordinates":[[[9.594226,47.525058],[9.632932,47.347601],[9.47997,47.10281],[9.932448,46.920728],[10.442701,46.893546],[10.363378,46.483571],[9.922837,46.314899],[9.182882,46.440215],[8.966306,46.036932],[8.489952,46.005151],[8.31663,46.163642],[7.755992,45.82449],[7.273851,45.776948],[6.843593,45.991147],[6.5001,46.429673],[6.022609,46.27299],[6.037389,46.725779],[6.768714,47.287708],[6.736571,47.541801],[7.192202,47.449766],[7.466759,47.620582],[8.317301,47.61358],[8.522612,47.830828],[9.594226,47.525058]]]}}, {"type":"Feature","id":"CHL","properties":{"name":"Chile"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-68.63401,-52.63637],[-68.63335,-54.8695],[-67.56244,-54.87001],[-66.95992,-54.89681],[-67.29103,-55.30124],[-68.14863,-55.61183],[-68.639991,-55.580018],[-69.2321,-55.49906],[-69.95809,-55.19843],[-71.00568,-55.05383],[-72.2639,-54.49514],[-73.2852,-53.95752],[-74.66253,-52.83749],[-73.8381,-53.04743],[-72.43418,-53.7154],[-71.10773,-54.07433],[-70.59178,-53.61583],[-70.26748,-52.93123],[-69.34565,-52.5183],[-68.63401,-52.63637]]],[[[-68.219913,-21.494347],[-67.82818,-22.872919],[-67.106674,-22.735925],[-66.985234,-22.986349],[-67.328443,-24.025303],[-68.417653,-24.518555],[-68.386001,-26.185016],[-68.5948,-26.506909],[-68.295542,-26.89934],[-69.001235,-27.521214],[-69.65613,-28.459141],[-70.01355,-29.367923],[-69.919008,-30.336339],[-70.535069,-31.36501],[-70.074399,-33.09121],[-69.814777,-33.273886],[-69.817309,-34.193571],[-70.388049,-35.169688],[-70.364769,-36.005089],[-71.121881,-36.658124],[-71.118625,-37.576827],[-70.814664,-38.552995],[-71.413517,-38.916022],[-71.680761,-39.808164],[-71.915734,-40.832339],[-71.746804,-42.051386],[-72.148898,-42.254888],[-71.915424,-43.408565],[-71.464056,-43.787611],[-71.793623,-44.207172],[-71.329801,-44.407522],[-71.222779,-44.784243],[-71.659316,-44.973689],[-71.552009,-45.560733],[-71.917258,-46.884838],[-72.447355,-47.738533],[-72.331161,-48.244238],[-72.648247,-48.878618],[-73.415436,-49.318436],[-73.328051,-50.378785],[-72.975747,-50.74145],[-72.309974,-50.67701],[-72.329404,-51.425956],[-71.914804,-52.009022],[-69.498362,-52.142761],[-68.571545,-52.299444],[-69.461284,-52.291951],[-69.94278,-52.537931],[-70.845102,-52.899201],[-71.006332,-53.833252],[-71.429795,-53.856455],[-72.557943,-53.53141],[-73.702757,-52.835069],[-73.702757,-52.83507],[-74.946763,-52.262754],[-75.260026,-51.629355],[-74.976632,-51.043396],[-75.479754,-50.378372],[-75.608015,-48.673773],[-75.18277,-47.711919],[-74.126581,-46.939253],[-75.644395,-46.647643],[-74.692154,-45.763976],[-74.351709,-44.103044],[-73.240356,-44.454961],[-72.717804,-42.383356],[-73.3889,-42.117532],[-73.701336,-43.365776],[-74.331943,-43.224958],[-74.017957,-41.794813],[-73.677099,-39.942213],[-73.217593,-39.258689],[-73.505559,-38.282883],[-73.588061,-37.156285],[-73.166717,-37.12378],[-72.553137,-35.50884],[-71.861732,-33.909093],[-71.43845,-32.418899],[-71.668721,-30.920645],[-71.370083,-30.095682],[-71.489894,-28.861442],[-70.905124,-27.64038],[-70.724954,-25.705924],[-70.403966,-23.628997],[-70.091246,-21.393319],[-70.16442,-19.756468],[-70.372572,-18.347975],[-69.858444,-18.092694],[-69.590424,-17.580012],[-69.100247,-18.260125],[-68.966818,-18.981683],[-68.442225,-19.405068],[-68.757167,-20.372658],[-68.219913,-21.494347]]]]}}, {"type":"Feature","id":"CHN","properties":{"name":"China"},"geometry":{"type":"MultiPolygon","coordinates":[[[[110.339188,18.678395],[109.47521,18.197701],[108.655208,18.507682],[108.626217,19.367888],[109.119056,19.821039],[110.211599,20.101254],[110.786551,20.077534],[111.010051,19.69593],[110.570647,19.255879],[110.339188,18.678395]]],[[[127.657407,49.76027],[129.397818,49.4406],[130.582293,48.729687],[130.987282,47.790132],[132.506672,47.78897],[133.373596,48.183442],[135.026311,48.47823],[134.500814,47.57844],[134.112362,47.212467],[133.769644,46.116927],[133.097127,45.144066],[131.883454,45.321162],[131.025212,44.967953],[131.288555,44.11152],[131.144688,42.92999],[130.633866,42.903015],[130.640016,42.395009],[129.994267,42.985387],[129.596669,42.424982],[128.052215,41.994285],[128.208433,41.466772],[127.343783,41.503152],[126.869083,41.816569],[126.182045,41.107336],[125.079942,40.569824],[124.265625,39.928493],[122.86757,39.637788],[122.131388,39.170452],[121.054554,38.897471],[121.585995,39.360854],[121.376757,39.750261],[122.168595,40.422443],[121.640359,40.94639],[120.768629,40.593388],[119.639602,39.898056],[119.023464,39.252333],[118.042749,39.204274],[117.532702,38.737636],[118.059699,38.061476],[118.87815,37.897325],[118.911636,37.448464],[119.702802,37.156389],[120.823457,37.870428],[121.711259,37.481123],[122.357937,37.454484],[122.519995,36.930614],[121.104164,36.651329],[120.637009,36.11144],[119.664562,35.609791],[119.151208,34.909859],[120.227525,34.360332],[120.620369,33.376723],[121.229014,32.460319],[121.908146,31.692174],[121.891919,30.949352],[121.264257,30.676267],[121.503519,30.142915],[122.092114,29.83252],[121.938428,29.018022],[121.684439,28.225513],[121.125661,28.135673],[120.395473,27.053207],[119.585497,25.740781],[118.656871,24.547391],[117.281606,23.624501],[115.890735,22.782873],[114.763827,22.668074],[114.152547,22.22376],[113.80678,22.54834],[113.241078,22.051367],[111.843592,21.550494],[110.785466,21.397144],[110.444039,20.341033],[109.889861,20.282457],[109.627655,21.008227],[109.864488,21.395051],[108.522813,21.715212],[108.05018,21.55238],[107.04342,21.811899],[106.567273,22.218205],[106.725403,22.794268],[105.811247,22.976892],[105.329209,23.352063],[104.476858,22.81915],[103.504515,22.703757],[102.706992,22.708795],[102.170436,22.464753],[101.652018,22.318199],[101.80312,21.174367],[101.270026,21.201652],[101.180005,21.436573],[101.150033,21.849984],[100.416538,21.558839],[99.983489,21.742937],[99.240899,22.118314],[99.531992,22.949039],[98.898749,23.142722],[98.660262,24.063286],[97.60472,23.897405],[97.724609,25.083637],[98.671838,25.918703],[98.712094,26.743536],[98.68269,27.508812],[98.246231,27.747221],[97.911988,28.335945],[97.327114,28.261583],[96.248833,28.411031],[96.586591,28.83098],[96.117679,29.452802],[95.404802,29.031717],[94.56599,29.277438],[93.413348,28.640629],[92.503119,27.896876],[91.696657,27.771742],[91.258854,28.040614],[90.730514,28.064954],[90.015829,28.296439],[89.47581,28.042759],[88.814248,27.299316],[88.730326,28.086865],[88.120441,27.876542],[86.954517,27.974262],[85.82332,28.203576],[85.011638,28.642774],[84.23458,28.839894],[83.898993,29.320226],[83.337115,29.463732],[82.327513,30.115268],[81.525804,30.422717],[81.111256,30.183481],[79.721367,30.882715],[78.738894,31.515906],[78.458446,32.618164],[79.176129,32.48378],[79.208892,32.994395],[78.811086,33.506198],[78.912269,34.321936],[77.837451,35.49401],[76.192848,35.898403],[75.896897,36.666806],[75.158028,37.133031],[74.980002,37.41999],[74.829986,37.990007],[74.864816,38.378846],[74.257514,38.606507],[73.928852,38.505815],[73.675379,39.431237],[73.960013,39.660008],[73.822244,39.893973],[74.776862,40.366425],[75.467828,40.562072],[76.526368,40.427946],[76.904484,41.066486],[78.187197,41.185316],[78.543661,41.582243],[80.11943,42.123941],[80.25999,42.349999],[80.18015,42.920068],[80.866206,43.180362],[79.966106,44.917517],[81.947071,45.317027],[82.458926,45.53965],[83.180484,47.330031],[85.16429,47.000956],[85.720484,47.452969],[85.768233,48.455751],[86.598776,48.549182],[87.35997,49.214981],[87.751264,49.297198],[88.013832,48.599463],[88.854298,48.069082],[90.280826,47.693549],[90.970809,46.888146],[90.585768,45.719716],[90.94554,45.286073],[92.133891,45.115076],[93.480734,44.975472],[94.688929,44.352332],[95.306875,44.241331],[95.762455,43.319449],[96.349396,42.725635],[97.451757,42.74889],[99.515817,42.524691],[100.845866,42.663804],[101.83304,42.514873],[103.312278,41.907468],[104.522282,41.908347],[104.964994,41.59741],[106.129316,42.134328],[107.744773,42.481516],[109.243596,42.519446],[110.412103,42.871234],[111.129682,43.406834],[111.829588,43.743118],[111.667737,44.073176],[111.348377,44.457442],[111.873306,45.102079],[112.436062,45.011646],[113.463907,44.808893],[114.460332,45.339817],[115.985096,45.727235],[116.717868,46.388202],[117.421701,46.672733],[118.874326,46.805412],[119.66327,46.69268],[119.772824,47.048059],[118.866574,47.74706],[118.064143,48.06673],[117.295507,47.697709],[116.308953,47.85341],[115.742837,47.726545],[115.485282,48.135383],[116.191802,49.134598],[116.678801,49.888531],[117.879244,49.510983],[119.288461,50.142883],[119.279366,50.582908],[120.18205,51.643566],[120.738191,51.964115],[120.725789,52.516226],[120.177089,52.753886],[121.003085,53.251401],[122.245748,53.431726],[123.571507,53.458804],[125.068211,53.161045],[125.946349,52.792799],[126.564399,51.784255],[126.939157,51.353894],[127.287456,50.739797],[127.657407,49.76027]]]]}}, {"type":"Feature","id":"CIV","properties":{"name":"Ivory Coast"},"geometry":{"type":"Polygon","coordinates":[[[-2.856125,4.994476],[-3.311084,4.984296],[-4.00882,5.179813],[-4.649917,5.168264],[-5.834496,4.993701],[-6.528769,4.705088],[-7.518941,4.338288],[-7.712159,4.364566],[-7.635368,5.188159],[-7.539715,5.313345],[-7.570153,5.707352],[-7.993693,6.12619],[-8.311348,6.193033],[-8.60288,6.467564],[-8.385452,6.911801],[-8.485446,7.395208],[-8.439298,7.686043],[-8.280703,7.68718],[-8.221792,8.123329],[-8.299049,8.316444],[-8.203499,8.455453],[-7.8321,8.575704],[-8.079114,9.376224],[-8.309616,9.789532],[-8.229337,10.12902],[-8.029944,10.206535],[-7.89959,10.297382],[-7.622759,10.147236],[-6.850507,10.138994],[-6.666461,10.430811],[-6.493965,10.411303],[-6.205223,10.524061],[-6.050452,10.096361],[-5.816926,10.222555],[-5.404342,10.370737],[-4.954653,10.152714],[-4.779884,9.821985],[-4.330247,9.610835],[-3.980449,9.862344],[-3.511899,9.900326],[-2.827496,9.642461],[-2.56219,8.219628],[-2.983585,7.379705],[-3.24437,6.250472],[-2.810701,5.389051],[-2.856125,4.994476]]]}}, {"type":"Feature","id":"CMR","properties":{"name":"Cameroon"},"geometry":{"type":"Polygon","coordinates":[[[13.075822,2.267097],[12.951334,2.321616],[12.35938,2.192812],[11.751665,2.326758],[11.276449,2.261051],[9.649158,2.283866],[9.795196,3.073404],[9.404367,3.734527],[8.948116,3.904129],[8.744924,4.352215],[8.488816,4.495617],[8.500288,4.771983],[8.757533,5.479666],[9.233163,6.444491],[9.522706,6.453482],[10.118277,7.03877],[10.497375,7.055358],[11.058788,6.644427],[11.745774,6.981383],[11.839309,7.397042],[12.063946,7.799808],[12.218872,8.305824],[12.753672,8.717763],[12.955468,9.417772],[13.1676,9.640626],[13.308676,10.160362],[13.57295,10.798566],[14.415379,11.572369],[14.468192,11.904752],[14.577178,12.085361],[14.181336,12.483657],[14.213531,12.802035],[14.495787,12.859396],[14.893386,12.219048],[14.960152,11.555574],[14.923565,10.891325],[15.467873,9.982337],[14.909354,9.992129],[14.627201,9.920919],[14.171466,10.021378],[13.954218,9.549495],[14.544467,8.965861],[14.979996,8.796104],[15.120866,8.38215],[15.436092,7.692812],[15.27946,7.421925],[14.776545,6.408498],[14.53656,6.226959],[14.459407,5.451761],[14.558936,5.030598],[14.478372,4.732605],[14.950953,4.210389],[15.03622,3.851367],[15.405396,3.335301],[15.862732,3.013537],[15.907381,2.557389],[16.012852,2.26764],[15.940919,1.727673],[15.146342,1.964015],[14.337813,2.227875],[13.075822,2.267097]]]}}, {"type":"Feature","id":"COD","properties":{"name":"Democratic Republic of the Congo"},"geometry":{"type":"Polygon","coordinates":[[[30.83386,3.509166],[30.773347,2.339883],[31.174149,2.204465],[30.85267,1.849396],[30.468508,1.583805],[30.086154,1.062313],[29.875779,0.59738],[29.819503,-0.20531],[29.587838,-0.587406],[29.579466,-1.341313],[29.291887,-1.620056],[29.254835,-2.21511],[29.117479,-2.292211],[29.024926,-2.839258],[29.276384,-3.293907],[29.339998,-4.499983],[29.519987,-5.419979],[29.419993,-5.939999],[29.620032,-6.520015],[30.199997,-7.079981],[30.740015,-8.340007],[30.346086,-8.238257],[29.002912,-8.407032],[28.734867,-8.526559],[28.449871,-9.164918],[28.673682,-9.605925],[28.49607,-10.789884],[28.372253,-11.793647],[28.642417,-11.971569],[29.341548,-12.360744],[29.616001,-12.178895],[29.699614,-13.257227],[28.934286,-13.248958],[28.523562,-12.698604],[28.155109,-12.272481],[27.388799,-12.132747],[27.16442,-11.608748],[26.553088,-11.92444],[25.75231,-11.784965],[25.418118,-11.330936],[24.78317,-11.238694],[24.314516,-11.262826],[24.257155,-10.951993],[23.912215,-10.926826],[23.456791,-10.867863],[22.837345,-11.017622],[22.402798,-10.993075],[22.155268,-11.084801],[22.208753,-9.894796],[21.875182,-9.523708],[21.801801,-8.908707],[21.949131,-8.305901],[21.746456,-7.920085],[21.728111,-7.290872],[20.514748,-7.299606],[20.601823,-6.939318],[20.091622,-6.94309],[20.037723,-7.116361],[19.417502,-7.155429],[19.166613,-7.738184],[19.016752,-7.988246],[18.464176,-7.847014],[18.134222,-7.987678],[17.47297,-8.068551],[17.089996,-7.545689],[16.860191,-7.222298],[16.57318,-6.622645],[16.326528,-5.87747],[13.375597,-5.864241],[13.024869,-5.984389],[12.735171,-5.965682],[12.322432,-6.100092],[12.182337,-5.789931],[12.436688,-5.684304],[12.468004,-5.248362],[12.631612,-4.991271],[12.995517,-4.781103],[13.25824,-4.882957],[13.600235,-4.500138],[14.144956,-4.510009],[14.209035,-4.793092],[14.582604,-4.970239],[15.170992,-4.343507],[15.75354,-3.855165],[16.00629,-3.535133],[15.972803,-2.712392],[16.407092,-1.740927],[16.865307,-1.225816],[17.523716,-0.74383],[17.638645,-0.424832],[17.663553,-0.058084],[17.82654,0.288923],[17.774192,0.855659],[17.898835,1.741832],[18.094276,2.365722],[18.393792,2.900443],[18.453065,3.504386],[18.542982,4.201785],[18.932312,4.709506],[19.467784,5.031528],[20.290679,4.691678],[20.927591,4.322786],[21.659123,4.224342],[22.405124,4.02916],[22.704124,4.633051],[22.84148,4.710126],[23.297214,4.609693],[24.410531,5.108784],[24.805029,4.897247],[25.128833,4.927245],[25.278798,5.170408],[25.650455,5.256088],[26.402761,5.150875],[27.044065,5.127853],[27.374226,5.233944],[27.979977,4.408413],[28.428994,4.287155],[28.696678,4.455077],[29.159078,4.389267],[29.715995,4.600805],[29.9535,4.173699],[30.83386,3.509166]]]}}, {"type":"Feature","id":"COG","properties":{"name":"Republic of the Congo"},"geometry":{"type":"Polygon","coordinates":[[[12.995517,-4.781103],[12.62076,-4.438023],[12.318608,-4.60623],[11.914963,-5.037987],[11.093773,-3.978827],[11.855122,-3.426871],[11.478039,-2.765619],[11.820964,-2.514161],[12.495703,-2.391688],[12.575284,-1.948511],[13.109619,-2.42874],[13.992407,-2.470805],[14.29921,-1.998276],[14.425456,-1.333407],[14.316418,-0.552627],[13.843321,0.038758],[14.276266,1.19693],[14.026669,1.395677],[13.282631,1.314184],[13.003114,1.830896],[13.075822,2.267097],[14.337813,2.227875],[15.146342,1.964015],[15.940919,1.727673],[16.012852,2.26764],[16.537058,3.198255],[17.133042,3.728197],[17.8099,3.560196],[18.453065,3.504386],[18.393792,2.900443],[18.094276,2.365722],[17.898835,1.741832],[17.774192,0.855659],[17.82654,0.288923],[17.663553,-0.058084],[17.638645,-0.424832],[17.523716,-0.74383],[16.865307,-1.225816],[16.407092,-1.740927],[15.972803,-2.712392],[16.00629,-3.535133],[15.75354,-3.855165],[15.170992,-4.343507],[14.582604,-4.970239],[14.209035,-4.793092],[14.144956,-4.510009],[13.600235,-4.500138],[13.25824,-4.882957],[12.995517,-4.781103]]]}}, {"type":"Feature","id":"COL","properties":{"name":"Colombia"},"geometry":{"type":"Polygon","coordinates":[[[-75.373223,-0.152032],[-75.801466,0.084801],[-76.292314,0.416047],[-76.57638,0.256936],[-77.424984,0.395687],[-77.668613,0.825893],[-77.855061,0.809925],[-78.855259,1.380924],[-78.990935,1.69137],[-78.617831,1.766404],[-78.662118,2.267355],[-78.42761,2.629556],[-77.931543,2.696606],[-77.510431,3.325017],[-77.12769,3.849636],[-77.496272,4.087606],[-77.307601,4.667984],[-77.533221,5.582812],[-77.318815,5.845354],[-77.476661,6.691116],[-77.881571,7.223771],[-77.753414,7.70984],[-77.431108,7.638061],[-77.242566,7.935278],[-77.474723,8.524286],[-77.353361,8.670505],[-76.836674,8.638749],[-76.086384,9.336821],[-75.6746,9.443248],[-75.664704,9.774003],[-75.480426,10.61899],[-74.906895,11.083045],[-74.276753,11.102036],[-74.197223,11.310473],[-73.414764,11.227015],[-72.627835,11.731972],[-72.238195,11.95555],[-71.75409,12.437303],[-71.399822,12.376041],[-71.137461,12.112982],[-71.331584,11.776284],[-71.973922,11.608672],[-72.227575,11.108702],[-72.614658,10.821975],[-72.905286,10.450344],[-73.027604,9.73677],[-73.304952,9.152],[-72.78873,9.085027],[-72.660495,8.625288],[-72.439862,8.405275],[-72.360901,8.002638],[-72.479679,7.632506],[-72.444487,7.423785],[-72.198352,7.340431],[-71.960176,6.991615],[-70.674234,7.087785],[-70.093313,6.960376],[-69.38948,6.099861],[-68.985319,6.206805],[-68.265052,6.153268],[-67.695087,6.267318],[-67.34144,6.095468],[-67.521532,5.55687],[-67.744697,5.221129],[-67.823012,4.503937],[-67.621836,3.839482],[-67.337564,3.542342],[-67.303173,3.318454],[-67.809938,2.820655],[-67.447092,2.600281],[-67.181294,2.250638],[-66.876326,1.253361],[-67.065048,1.130112],[-67.259998,1.719999],[-67.53781,2.037163],[-67.868565,1.692455],[-69.816973,1.714805],[-69.804597,1.089081],[-69.218638,0.985677],[-69.252434,0.602651],[-69.452396,0.706159],[-70.015566,0.541414],[-70.020656,-0.185156],[-69.577065,-0.549992],[-69.420486,-1.122619],[-69.444102,-1.556287],[-69.893635,-4.298187],[-70.394044,-3.766591],[-70.692682,-3.742872],[-70.047709,-2.725156],[-70.813476,-2.256865],[-71.413646,-2.342802],[-71.774761,-2.16979],[-72.325787,-2.434218],[-73.070392,-2.308954],[-73.659504,-1.260491],[-74.122395,-1.002833],[-74.441601,-0.53082],[-75.106625,-0.057205],[-75.373223,-0.152032]]]}}, {"type":"Feature","id":"CRI","properties":{"name":"Costa Rica"},"geometry":{"type":"Polygon","coordinates":[[[-82.965783,8.225028],[-83.508437,8.446927],[-83.711474,8.656836],[-83.596313,8.830443],[-83.632642,9.051386],[-83.909886,9.290803],[-84.303402,9.487354],[-84.647644,9.615537],[-84.713351,9.908052],[-84.97566,10.086723],[-84.911375,9.795992],[-85.110923,9.55704],[-85.339488,9.834542],[-85.660787,9.933347],[-85.797445,10.134886],[-85.791709,10.439337],[-85.659314,10.754331],[-85.941725,10.895278],[-85.71254,11.088445],[-85.561852,11.217119],[-84.903003,10.952303],[-84.673069,11.082657],[-84.355931,10.999226],[-84.190179,10.79345],[-83.895054,10.726839],[-83.655612,10.938764],[-83.40232,10.395438],[-83.015677,9.992982],[-82.546196,9.566135],[-82.932891,9.476812],[-82.927155,9.07433],[-82.719183,8.925709],[-82.868657,8.807266],[-82.829771,8.626295],[-82.913176,8.423517],[-82.965783,8.225028]]]}}, {"type":"Feature","id":"CUB","properties":{"name":"Cuba"},"geometry":{"type":"Polygon","coordinates":[[[-82.268151,23.188611],[-81.404457,23.117271],[-80.618769,23.10598],[-79.679524,22.765303],[-79.281486,22.399202],[-78.347434,22.512166],[-77.993296,22.277194],[-77.146422,21.657851],[-76.523825,21.20682],[-76.19462,21.220565],[-75.598222,21.016624],[-75.67106,20.735091],[-74.933896,20.693905],[-74.178025,20.284628],[-74.296648,20.050379],[-74.961595,19.923435],[-75.63468,19.873774],[-76.323656,19.952891],[-77.755481,19.855481],[-77.085108,20.413354],[-77.492655,20.673105],[-78.137292,20.739949],[-78.482827,21.028613],[-78.719867,21.598114],[-79.285,21.559175],[-80.217475,21.827324],[-80.517535,22.037079],[-81.820943,22.192057],[-82.169992,22.387109],[-81.795002,22.636965],[-82.775898,22.68815],[-83.494459,22.168518],[-83.9088,22.154565],[-84.052151,21.910575],[-84.54703,21.801228],[-84.974911,21.896028],[-84.447062,22.20495],[-84.230357,22.565755],[-83.77824,22.788118],[-83.267548,22.983042],[-82.510436,23.078747],[-82.268151,23.188611]]]}}, {"type":"Feature","id":"-99","properties":{"name":"Northern Cyprus"},"geometry":{"type":"Polygon","coordinates":[[[32.73178,35.140026],[32.802474,35.145504],[32.946961,35.386703],[33.667227,35.373216],[34.576474,35.671596],[33.900804,35.245756],[33.973617,35.058506],[33.86644,35.093595],[33.675392,35.017863],[33.525685,35.038688],[33.475817,35.000345],[33.455922,35.101424],[33.383833,35.162712],[33.190977,35.173125],[32.919572,35.087833],[32.73178,35.140026]]]}}, {"type":"Feature","id":"CYP","properties":{"name":"Cyprus"},"geometry":{"type":"Polygon","coordinates":[[[33.973617,35.058506],[34.004881,34.978098],[32.979827,34.571869],[32.490296,34.701655],[32.256667,35.103232],[32.73178,35.140026],[32.919572,35.087833],[33.190977,35.173125],[33.383833,35.162712],[33.455922,35.101424],[33.475817,35.000345],[33.525685,35.038688],[33.675392,35.017863],[33.86644,35.093595],[33.973617,35.058506]]]}}, {"type":"Feature","id":"CZE","properties":{"name":"Czech Republic"},"geometry":{"type":"Polygon","coordinates":[[[16.960288,48.596982],[16.499283,48.785808],[16.029647,48.733899],[15.253416,49.039074],[14.901447,48.964402],[14.338898,48.555305],[13.595946,48.877172],[13.031329,49.307068],[12.521024,49.547415],[12.415191,49.969121],[12.240111,50.266338],[12.966837,50.484076],[13.338132,50.733234],[14.056228,50.926918],[14.307013,51.117268],[14.570718,51.002339],[15.016996,51.106674],[15.490972,50.78473],[16.238627,50.697733],[16.176253,50.422607],[16.719476,50.215747],[16.868769,50.473974],[17.554567,50.362146],[17.649445,50.049038],[18.392914,49.988629],[18.853144,49.49623],[18.554971,49.495015],[18.399994,49.315001],[18.170498,49.271515],[18.104973,49.043983],[17.913512,48.996493],[17.886485,48.903475],[17.545007,48.800019],[17.101985,48.816969],[16.960288,48.596982]]]}}, {"type":"Feature","id":"DEU","properties":{"name":"Germany"},"geometry":{"type":"Polygon","coordinates":[[[9.921906,54.983104],[9.93958,54.596642],[10.950112,54.363607],[10.939467,54.008693],[11.956252,54.196486],[12.51844,54.470371],[13.647467,54.075511],[14.119686,53.757029],[14.353315,53.248171],[14.074521,52.981263],[14.4376,52.62485],[14.685026,52.089947],[14.607098,51.745188],[15.016996,51.106674],[14.570718,51.002339],[14.307013,51.117268],[14.056228,50.926918],[13.338132,50.733234],[12.966837,50.484076],[12.240111,50.266338],[12.415191,49.969121],[12.521024,49.547415],[13.031329,49.307068],[13.595946,48.877172],[13.243357,48.416115],[12.884103,48.289146],[13.025851,47.637584],[12.932627,47.467646],[12.62076,47.672388],[12.141357,47.703083],[11.426414,47.523766],[10.544504,47.566399],[10.402084,47.302488],[9.896068,47.580197],[9.594226,47.525058],[8.522612,47.830828],[8.317301,47.61358],[7.466759,47.620582],[7.593676,48.333019],[8.099279,49.017784],[6.65823,49.201958],[6.18632,49.463803],[6.242751,49.902226],[6.043073,50.128052],[6.156658,50.803721],[5.988658,51.851616],[6.589397,51.852029],[6.84287,52.22844],[7.092053,53.144043],[6.90514,53.482162],[7.100425,53.693932],[7.936239,53.748296],[8.121706,53.527792],[8.800734,54.020786],[8.572118,54.395646],[8.526229,54.962744],[9.282049,54.830865],[9.921906,54.983104]]]}}, {"type":"Feature","id":"DJI","properties":{"name":"Djibouti"},"geometry":{"type":"Polygon","coordinates":[[[43.081226,12.699639],[43.317852,12.390148],[43.286381,11.974928],[42.715874,11.735641],[43.145305,11.46204],[42.776852,10.926879],[42.55493,11.10511],[42.31414,11.0342],[41.75557,11.05091],[41.73959,11.35511],[41.66176,11.6312],[42,12.1],[42.35156,12.54223],[42.779642,12.455416],[43.081226,12.699639]]]}}, {"type":"Feature","id":"DNK","properties":{"name":"Denmark"},"geometry":{"type":"MultiPolygon","coordinates":[[[[12.690006,55.609991],[12.089991,54.800015],[11.043543,55.364864],[10.903914,55.779955],[12.370904,56.111407],[12.690006,55.609991]]],[[[10.912182,56.458621],[10.667804,56.081383],[10.369993,56.190007],[9.649985,55.469999],[9.921906,54.983104],[9.282049,54.830865],[8.526229,54.962744],[8.120311,55.517723],[8.089977,56.540012],[8.256582,56.809969],[8.543438,57.110003],[9.424469,57.172066],[9.775559,57.447941],[10.580006,57.730017],[10.546106,57.215733],[10.25,56.890016],[10.369993,56.609982],[10.912182,56.458621]]]]}}, {"type":"Feature","id":"DOM","properties":{"name":"Dominican Republic"},"geometry":{"type":"Polygon","coordinates":[[[-71.712361,19.714456],[-71.587304,19.884911],[-70.806706,19.880286],[-70.214365,19.622885],[-69.950815,19.648],[-69.76925,19.293267],[-69.222126,19.313214],[-69.254346,19.015196],[-68.809412,18.979074],[-68.317943,18.612198],[-68.689316,18.205142],[-69.164946,18.422648],[-69.623988,18.380713],[-69.952934,18.428307],[-70.133233,18.245915],[-70.517137,18.184291],[-70.669298,18.426886],[-70.99995,18.283329],[-71.40021,17.598564],[-71.657662,17.757573],[-71.708305,18.044997],[-71.687738,18.31666],[-71.945112,18.6169],[-71.701303,18.785417],[-71.624873,19.169838],[-71.712361,19.714456]]]}}, {"type":"Feature","id":"DZA","properties":{"name":"Algeria"},"geometry":{"type":"Polygon","coordinates":[[[11.999506,23.471668],[8.572893,21.565661],[5.677566,19.601207],[4.267419,19.155265],[3.158133,19.057364],[3.146661,19.693579],[2.683588,19.85623],[2.060991,20.142233],[1.823228,20.610809],[-1.550055,22.792666],[-4.923337,24.974574],[-8.6844,27.395744],[-8.665124,27.589479],[-8.66559,27.656426],[-8.674116,28.841289],[-7.059228,29.579228],[-6.060632,29.7317],[-5.242129,30.000443],[-4.859646,30.501188],[-3.690441,30.896952],[-3.647498,31.637294],[-3.06898,31.724498],[-2.616605,32.094346],[-1.307899,32.262889],[-1.124551,32.651522],[-1.388049,32.864015],[-1.733455,33.919713],[-1.792986,34.527919],[-2.169914,35.168396],[-1.208603,35.714849],[-0.127454,35.888662],[0.503877,36.301273],[1.466919,36.605647],[3.161699,36.783905],[4.815758,36.865037],[5.32012,36.716519],[6.26182,37.110655],[7.330385,37.118381],[7.737078,36.885708],[8.420964,36.946427],[8.217824,36.433177],[8.376368,35.479876],[8.140981,34.655146],[7.524482,34.097376],[7.612642,33.344115],[8.430473,32.748337],[8.439103,32.506285],[9.055603,32.102692],[9.48214,30.307556],[9.805634,29.424638],[9.859998,28.95999],[9.683885,28.144174],[9.756128,27.688259],[9.629056,27.140953],[9.716286,26.512206],[9.319411,26.094325],[9.910693,25.365455],[9.948261,24.936954],[10.303847,24.379313],[10.771364,24.562532],[11.560669,24.097909],[11.999506,23.471668]]]}}, {"type":"Feature","id":"ECU","properties":{"name":"Ecuador"},"geometry":{"type":"Polygon","coordinates":[[[-80.302561,-3.404856],[-79.770293,-2.657512],[-79.986559,-2.220794],[-80.368784,-2.685159],[-80.967765,-2.246943],[-80.764806,-1.965048],[-80.933659,-1.057455],[-80.58337,-0.906663],[-80.399325,-0.283703],[-80.020898,0.36034],[-80.09061,0.768429],[-79.542762,0.982938],[-78.855259,1.380924],[-77.855061,0.809925],[-77.668613,0.825893],[-77.424984,0.395687],[-76.57638,0.256936],[-76.292314,0.416047],[-75.801466,0.084801],[-75.373223,-0.152032],[-75.233723,-0.911417],[-75.544996,-1.56161],[-76.635394,-2.608678],[-77.837905,-3.003021],[-78.450684,-3.873097],[-78.639897,-4.547784],[-79.205289,-4.959129],[-79.624979,-4.454198],[-80.028908,-4.346091],[-80.442242,-4.425724],[-80.469295,-4.059287],[-80.184015,-3.821162],[-80.302561,-3.404856]]]}}, {"type":"Feature","id":"EGY","properties":{"name":"Egypt"},"geometry":{"type":"Polygon","coordinates":[[[34.9226,29.50133],[34.64174,29.09942],[34.42655,28.34399],[34.15451,27.8233],[33.92136,27.6487],[33.58811,27.97136],[33.13676,28.41765],[32.42323,29.85108],[32.32046,29.76043],[32.73482,28.70523],[33.34876,27.69989],[34.10455,26.14227],[34.47387,25.59856],[34.79507,25.03375],[35.69241,23.92671],[35.49372,23.75237],[35.52598,23.10244],[36.69069,22.20485],[36.86623,22],[32.9,22],[29.02,22],[25,22],[25,25.6825],[25,29.238655],[24.70007,30.04419],[24.95762,30.6616],[24.80287,31.08929],[25.16482,31.56915],[26.49533,31.58568],[27.45762,31.32126],[28.45048,31.02577],[28.91353,30.87005],[29.68342,31.18686],[30.09503,31.4734],[30.97693,31.55586],[31.68796,31.4296],[31.96041,30.9336],[32.19247,31.26034],[32.99392,31.02407],[33.7734,30.96746],[34.26544,31.21936],[34.9226,29.50133]]]}}, {"type":"Feature","id":"ERI","properties":{"name":"Eritrea"},"geometry":{"type":"Polygon","coordinates":[[[42.35156,12.54223],[42.00975,12.86582],[41.59856,13.45209],[41.155194,13.77332],[40.8966,14.11864],[40.026219,14.519579],[39.34061,14.53155],[39.0994,14.74064],[38.51295,14.50547],[37.90607,14.95943],[37.59377,14.2131],[36.42951,14.42211],[36.323189,14.822481],[36.75386,16.291874],[36.85253,16.95655],[37.16747,17.26314],[37.904,17.42754],[38.41009,17.998307],[38.990623,16.840626],[39.26611,15.922723],[39.814294,15.435647],[41.179275,14.49108],[41.734952,13.921037],[42.276831,13.343992],[42.589576,13.000421],[43.081226,12.699639],[42.779642,12.455416],[42.35156,12.54223]]]}}, {"type":"Feature","id":"ESP","properties":{"name":"Spain"},"geometry":{"type":"Polygon","coordinates":[[[-9.034818,41.880571],[-8.984433,42.592775],[-9.392884,43.026625],[-7.97819,43.748338],[-6.754492,43.567909],[-5.411886,43.57424],[-4.347843,43.403449],[-3.517532,43.455901],[-1.901351,43.422802],[-1.502771,43.034014],[0.338047,42.579546],[0.701591,42.795734],[1.826793,42.343385],[2.985999,42.473015],[3.039484,41.89212],[2.091842,41.226089],[0.810525,41.014732],[0.721331,40.678318],[0.106692,40.123934],[-0.278711,39.309978],[0.111291,38.738514],[-0.467124,38.292366],[-0.683389,37.642354],[-1.438382,37.443064],[-2.146453,36.674144],[-3.415781,36.6589],[-4.368901,36.677839],[-4.995219,36.324708],[-5.37716,35.94685],[-5.866432,36.029817],[-6.236694,36.367677],[-6.520191,36.942913],[-7.453726,37.097788],[-7.537105,37.428904],[-7.166508,37.803894],[-7.029281,38.075764],[-7.374092,38.373059],[-7.098037,39.030073],[-7.498632,39.629571],[-7.066592,39.711892],[-7.026413,40.184524],[-6.86402,40.330872],[-6.851127,41.111083],[-6.389088,41.381815],[-6.668606,41.883387],[-7.251309,41.918346],[-7.422513,41.792075],[-8.013175,41.790886],[-8.263857,42.280469],[-8.671946,42.134689],[-9.034818,41.880571]]]}}, {"type":"Feature","id":"EST","properties":{"name":"Estonia"},"geometry":{"type":"Polygon","coordinates":[[[24.312863,57.793424],[24.428928,58.383413],[24.061198,58.257375],[23.42656,58.612753],[23.339795,59.18724],[24.604214,59.465854],[25.864189,59.61109],[26.949136,59.445803],[27.981114,59.475388],[28.131699,59.300825],[27.420166,58.724581],[27.716686,57.791899],[27.288185,57.474528],[26.463532,57.476389],[25.60281,57.847529],[25.164594,57.970157],[24.312863,57.793424]]]}}, {"type":"Feature","id":"ETH","properties":{"name":"Ethiopia"},"geometry":{"type":"Polygon","coordinates":[[[37.90607,14.95943],[38.51295,14.50547],[39.0994,14.74064],[39.34061,14.53155],[40.02625,14.51959],[40.8966,14.11864],[41.1552,13.77333],[41.59856,13.45209],[42.00975,12.86582],[42.35156,12.54223],[42,12.1],[41.66176,11.6312],[41.73959,11.35511],[41.75557,11.05091],[42.31414,11.0342],[42.55493,11.10511],[42.776852,10.926879],[42.55876,10.57258],[42.92812,10.02194],[43.29699,9.54048],[43.67875,9.18358],[46.94834,7.99688],[47.78942,8.003],[44.9636,5.00162],[43.66087,4.95755],[42.76967,4.25259],[42.12861,4.23413],[41.855083,3.918912],[41.1718,3.91909],[40.76848,4.25702],[39.85494,3.83879],[39.559384,3.42206],[38.89251,3.50074],[38.67114,3.61607],[38.43697,3.58851],[38.120915,3.598605],[36.855093,4.447864],[36.159079,4.447864],[35.817448,4.776966],[35.817448,5.338232],[35.298007,5.506],[34.70702,6.59422],[34.25032,6.82607],[34.0751,7.22595],[33.56829,7.71334],[32.95418,7.78497],[33.2948,8.35458],[33.8255,8.37916],[33.97498,8.68456],[33.96162,9.58358],[34.25745,10.63009],[34.73115,10.91017],[34.83163,11.31896],[35.26049,12.08286],[35.86363,12.57828],[36.27022,13.56333],[36.42951,14.42211],[37.59377,14.2131],[37.90607,14.95943]]]}}, {"type":"Feature","id":"FIN","properties":{"name":"Finland"},"geometry":{"type":"Polygon","coordinates":[[[28.59193,69.064777],[28.445944,68.364613],[29.977426,67.698297],[29.054589,66.944286],[30.21765,65.80598],[29.54443,64.948672],[30.444685,64.204453],[30.035872,63.552814],[31.516092,62.867687],[31.139991,62.357693],[30.211107,61.780028],[28.069998,60.503517],[26.255173,60.423961],[24.496624,60.057316],[22.869695,59.846373],[22.290764,60.391921],[21.322244,60.72017],[21.544866,61.705329],[21.059211,62.607393],[21.536029,63.189735],[22.442744,63.81781],[24.730512,64.902344],[25.398068,65.111427],[25.294043,65.534346],[23.903379,66.006927],[23.56588,66.396051],[23.539473,67.936009],[21.978535,68.616846],[20.645593,69.106247],[21.244936,69.370443],[22.356238,68.841741],[23.66205,68.891247],[24.735679,68.649557],[25.689213,69.092114],[26.179622,69.825299],[27.732292,70.164193],[29.015573,69.766491],[28.59193,69.064777]]]}}, {"type":"Feature","id":"FJI","properties":{"name":"Fiji"},"geometry":{"type":"MultiPolygon","coordinates":[[[[178.3736,-17.33992],[178.71806,-17.62846],[178.55271,-18.15059],[177.93266,-18.28799],[177.38146,-18.16432],[177.28504,-17.72465],[177.67087,-17.38114],[178.12557,-17.50481],[178.3736,-17.33992]]],[[[179.364143,-16.801354],[178.725059,-17.012042],[178.596839,-16.63915],[179.096609,-16.433984],[179.413509,-16.379054],[180,-16.067133],[180,-16.555217],[179.364143,-16.801354]]],[[[-179.917369,-16.501783],[-180,-16.555217],[-180,-16.067133],[-179.79332,-16.020882],[-179.917369,-16.501783]]]]}}, {"type":"Feature","id":"FLK","properties":{"name":"Falkland Islands"},"geometry":{"type":"Polygon","coordinates":[[[-61.2,-51.85],[-60,-51.25],[-59.15,-51.5],[-58.55,-51.1],[-57.75,-51.55],[-58.05,-51.9],[-59.4,-52.2],[-59.85,-51.85],[-60.7,-52.3],[-61.2,-51.85]]]}}, {"type":"Feature","id":"FRA","properties":{"name":"France"},"geometry":{"type":"MultiPolygon","coordinates":[[[[9.560016,42.152492],[9.229752,41.380007],[8.775723,41.583612],[8.544213,42.256517],[8.746009,42.628122],[9.390001,43.009985],[9.560016,42.152492]]],[[[3.588184,50.378992],[4.286023,49.907497],[4.799222,49.985373],[5.674052,49.529484],[5.897759,49.442667],[6.18632,49.463803],[6.65823,49.201958],[8.099279,49.017784],[7.593676,48.333019],[7.466759,47.620582],[7.192202,47.449766],[6.736571,47.541801],[6.768714,47.287708],[6.037389,46.725779],[6.022609,46.27299],[6.5001,46.429673],[6.843593,45.991147],[6.802355,45.70858],[7.096652,45.333099],[6.749955,45.028518],[7.007562,44.254767],[7.549596,44.127901],[7.435185,43.693845],[6.529245,43.128892],[4.556963,43.399651],[3.100411,43.075201],[2.985999,42.473015],[1.826793,42.343385],[0.701591,42.795734],[0.338047,42.579546],[-1.502771,43.034014],[-1.901351,43.422802],[-1.384225,44.02261],[-1.193798,46.014918],[-2.225724,47.064363],[-2.963276,47.570327],[-4.491555,47.954954],[-4.59235,48.68416],[-3.295814,48.901692],[-1.616511,48.644421],[-1.933494,49.776342],[-0.989469,49.347376],[1.338761,50.127173],[1.639001,50.946606],[2.513573,51.148506],[2.658422,50.796848],[3.123252,50.780363],[3.588184,50.378992]]]]}}, {"type":"Feature","id":"GAB","properties":{"name":"Gabon"},"geometry":{"type":"Polygon","coordinates":[[[11.093773,-3.978827],[10.066135,-2.969483],[9.405245,-2.144313],[8.797996,-1.111301],[8.830087,-0.779074],[9.04842,-0.459351],[9.291351,0.268666],[9.492889,1.01012],[9.830284,1.067894],[11.285079,1.057662],[11.276449,2.261051],[11.751665,2.326758],[12.35938,2.192812],[12.951334,2.321616],[13.075822,2.267097],[13.003114,1.830896],[13.282631,1.314184],[14.026669,1.395677],[14.276266,1.19693],[13.843321,0.038758],[14.316418,-0.552627],[14.425456,-1.333407],[14.29921,-1.998276],[13.992407,-2.470805],[13.109619,-2.42874],[12.575284,-1.948511],[12.495703,-2.391688],[11.820964,-2.514161],[11.478039,-2.765619],[11.855122,-3.426871],[11.093773,-3.978827]]]}}, {"type":"Feature","id":"GBR","properties":{"name":"United Kingdom"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-5.661949,54.554603],[-6.197885,53.867565],[-6.95373,54.073702],[-7.572168,54.059956],[-7.366031,54.595841],[-7.572168,55.131622],[-6.733847,55.17286],[-5.661949,54.554603]]],[[[-3.005005,58.635],[-4.073828,57.553025],[-3.055002,57.690019],[-1.959281,57.6848],[-2.219988,56.870017],[-3.119003,55.973793],[-2.085009,55.909998],[-2.005676,55.804903],[-1.114991,54.624986],[-0.430485,54.464376],[0.184981,53.325014],[0.469977,52.929999],[1.681531,52.73952],[1.559988,52.099998],[1.050562,51.806761],[1.449865,51.289428],[0.550334,50.765739],[-0.787517,50.774989],[-2.489998,50.500019],[-2.956274,50.69688],[-3.617448,50.228356],[-4.542508,50.341837],[-5.245023,49.96],[-5.776567,50.159678],[-4.30999,51.210001],[-3.414851,51.426009],[-3.422719,51.426848],[-4.984367,51.593466],[-5.267296,51.9914],[-4.222347,52.301356],[-4.770013,52.840005],[-4.579999,53.495004],[-3.093831,53.404547],[-3.09208,53.404441],[-2.945009,53.985],[-3.614701,54.600937],[-3.630005,54.615013],[-4.844169,54.790971],[-5.082527,55.061601],[-4.719112,55.508473],[-5.047981,55.783986],[-5.586398,55.311146],[-5.644999,56.275015],[-6.149981,56.78501],[-5.786825,57.818848],[-5.009999,58.630013],[-4.211495,58.550845],[-3.005005,58.635]]]]}}, {"type":"Feature","id":"GEO","properties":{"name":"Georgia"},"geometry":{"type":"Polygon","coordinates":[[[41.554084,41.535656],[41.703171,41.962943],[41.45347,42.645123],[40.875469,43.013628],[40.321394,43.128634],[39.955009,43.434998],[40.076965,43.553104],[40.922185,43.382159],[42.394395,43.220308],[43.756017,42.740828],[43.9312,42.554974],[44.537623,42.711993],[45.470279,42.502781],[45.77641,42.092444],[46.404951,41.860675],[46.145432,41.722802],[46.637908,41.181673],[46.501637,41.064445],[45.962601,41.123873],[45.217426,41.411452],[44.97248,41.248129],[43.582746,41.092143],[42.619549,41.583173],[41.554084,41.535656]]]}}, {"type":"Feature","id":"GHA","properties":{"name":"Ghana"},"geometry":{"type":"Polygon","coordinates":[[[1.060122,5.928837],[-0.507638,5.343473],[-1.063625,5.000548],[-1.964707,4.710462],[-2.856125,4.994476],[-2.810701,5.389051],[-3.24437,6.250472],[-2.983585,7.379705],[-2.56219,8.219628],[-2.827496,9.642461],[-2.963896,10.395335],[-2.940409,10.96269],[-1.203358,11.009819],[-0.761576,10.93693],[-0.438702,11.098341],[0.023803,11.018682],[-0.049785,10.706918],[0.36758,10.191213],[0.365901,9.465004],[0.461192,8.677223],[0.712029,8.312465],[0.490957,7.411744],[0.570384,6.914359],[0.836931,6.279979],[1.060122,5.928837]]]}}, {"type":"Feature","id":"GIN","properties":{"name":"Guinea"},"geometry":{"type":"Polygon","coordinates":[[[-8.439298,7.686043],[-8.722124,7.711674],[-8.926065,7.309037],[-9.208786,7.313921],[-9.403348,7.526905],[-9.33728,7.928534],[-9.755342,8.541055],[-10.016567,8.428504],[-10.230094,8.406206],[-10.505477,8.348896],[-10.494315,8.715541],[-10.65477,8.977178],[-10.622395,9.26791],[-10.839152,9.688246],[-11.117481,10.045873],[-11.917277,10.046984],[-12.150338,9.858572],[-12.425929,9.835834],[-12.596719,9.620188],[-12.711958,9.342712],[-13.24655,8.903049],[-13.685154,9.494744],[-14.074045,9.886167],[-14.330076,10.01572],[-14.579699,10.214467],[-14.693232,10.656301],[-14.839554,10.876572],[-15.130311,11.040412],[-14.685687,11.527824],[-14.382192,11.509272],[-14.121406,11.677117],[-13.9008,11.678719],[-13.743161,11.811269],[-13.828272,12.142644],[-13.718744,12.247186],[-13.700476,12.586183],[-13.217818,12.575874],[-12.499051,12.33209],[-12.278599,12.35444],[-12.203565,12.465648],[-11.658301,12.386583],[-11.513943,12.442988],[-11.456169,12.076834],[-11.297574,12.077971],[-11.036556,12.211245],[-10.87083,12.177887],[-10.593224,11.923975],[-10.165214,11.844084],[-9.890993,12.060479],[-9.567912,12.194243],[-9.327616,12.334286],[-9.127474,12.30806],[-8.905265,12.088358],[-8.786099,11.812561],[-8.376305,11.393646],[-8.581305,11.136246],[-8.620321,10.810891],[-8.407311,10.909257],[-8.282357,10.792597],[-8.335377,10.494812],[-8.029944,10.206535],[-8.229337,10.12902],[-8.309616,9.789532],[-8.079114,9.376224],[-7.8321,8.575704],[-8.203499,8.455453],[-8.299049,8.316444],[-8.221792,8.123329],[-8.280703,7.68718],[-8.439298,7.686043]]]}}, {"type":"Feature","id":"GMB","properties":{"name":"Gambia"},"geometry":{"type":"Polygon","coordinates":[[[-16.841525,13.151394],[-16.713729,13.594959],[-15.624596,13.623587],[-15.39877,13.860369],[-15.081735,13.876492],[-14.687031,13.630357],[-14.376714,13.62568],[-14.046992,13.794068],[-13.844963,13.505042],[-14.277702,13.280585],[-14.712197,13.298207],[-15.141163,13.509512],[-15.511813,13.27857],[-15.691001,13.270353],[-15.931296,13.130284],[-16.841525,13.151394]]]}}, {"type":"Feature","id":"GNB","properties":{"name":"Guinea Bissau"},"geometry":{"type":"Polygon","coordinates":[[[-15.130311,11.040412],[-15.66418,11.458474],[-16.085214,11.524594],[-16.314787,11.806515],[-16.308947,11.958702],[-16.613838,12.170911],[-16.677452,12.384852],[-16.147717,12.547762],[-15.816574,12.515567],[-15.548477,12.62817],[-13.700476,12.586183],[-13.718744,12.247186],[-13.828272,12.142644],[-13.743161,11.811269],[-13.9008,11.678719],[-14.121406,11.677117],[-14.382192,11.509272],[-14.685687,11.527824],[-15.130311,11.040412]]]}}, {"type":"Feature","id":"GNQ","properties":{"name":"Equatorial Guinea"},"geometry":{"type":"Polygon","coordinates":[[[9.492889,1.01012],[9.305613,1.160911],[9.649158,2.283866],[11.276449,2.261051],[11.285079,1.057662],[9.830284,1.067894],[9.492889,1.01012]]]}}, {"type":"Feature","id":"GRC","properties":{"name":"Greece"},"geometry":{"type":"MultiPolygon","coordinates":[[[[23.69998,35.705004],[24.246665,35.368022],[25.025015,35.424996],[25.769208,35.354018],[25.745023,35.179998],[26.290003,35.29999],[26.164998,35.004995],[24.724982,34.919988],[24.735007,35.084991],[23.514978,35.279992],[23.69998,35.705004]]],[[[26.604196,41.562115],[26.294602,40.936261],[26.056942,40.824123],[25.447677,40.852545],[24.925848,40.947062],[23.714811,40.687129],[24.407999,40.124993],[23.899968,39.962006],[23.342999,39.960998],[22.813988,40.476005],[22.626299,40.256561],[22.849748,39.659311],[23.350027,39.190011],[22.973099,38.970903],[23.530016,38.510001],[24.025025,38.219993],[24.040011,37.655015],[23.115003,37.920011],[23.409972,37.409991],[22.774972,37.30501],[23.154225,36.422506],[22.490028,36.41],[21.670026,36.844986],[21.295011,37.644989],[21.120034,38.310323],[20.730032,38.769985],[20.217712,39.340235],[20.150016,39.624998],[20.615,40.110007],[20.674997,40.435],[20.99999,40.580004],[21.02004,40.842727],[21.674161,40.931275],[22.055378,41.149866],[22.597308,41.130487],[22.76177,41.3048],[22.952377,41.337994],[23.692074,41.309081],[24.492645,41.583896],[25.197201,41.234486],[26.106138,41.328899],[26.117042,41.826905],[26.604196,41.562115]]]]}}, {"type":"Feature","id":"GRL","properties":{"name":"Greenland"},"geometry":{"type":"Polygon","coordinates":[[[-46.76379,82.62796],[-43.40644,83.22516],[-39.89753,83.18018],[-38.62214,83.54905],[-35.08787,83.64513],[-27.10046,83.51966],[-20.84539,82.72669],[-22.69182,82.34165],[-26.51753,82.29765],[-31.9,82.2],[-31.39646,82.02154],[-27.85666,82.13178],[-24.84448,81.78697],[-22.90328,82.09317],[-22.07175,81.73449],[-23.16961,81.15271],[-20.62363,81.52462],[-15.76818,81.91245],[-12.77018,81.71885],[-12.20855,81.29154],[-16.28533,80.58004],[-16.85,80.35],[-20.04624,80.17708],[-17.73035,80.12912],[-18.9,79.4],[-19.70499,78.75128],[-19.67353,77.63859],[-18.47285,76.98565],[-20.03503,76.94434],[-21.67944,76.62795],[-19.83407,76.09808],[-19.59896,75.24838],[-20.66818,75.15585],[-19.37281,74.29561],[-21.59422,74.22382],[-20.43454,73.81713],[-20.76234,73.46436],[-22.17221,73.30955],[-23.56593,73.30663],[-22.31311,72.62928],[-22.29954,72.18409],[-24.27834,72.59788],[-24.79296,72.3302],[-23.44296,72.08016],[-22.13281,71.46898],[-21.75356,70.66369],[-23.53603,70.471],[-24.30702,70.85649],[-25.54341,71.43094],[-25.20135,70.75226],[-26.36276,70.22646],[-23.72742,70.18401],[-22.34902,70.12946],[-25.02927,69.2588],[-27.74737,68.47046],[-30.67371,68.12503],[-31.77665,68.12078],[-32.81105,67.73547],[-34.20196,66.67974],[-36.35284,65.9789],[-37.04378,65.93768],[-38.37505,65.69213],[-39.81222,65.45848],[-40.66899,64.83997],[-40.68281,64.13902],[-41.1887,63.48246],[-42.81938,62.68233],[-42.41666,61.90093],[-42.86619,61.07404],[-43.3784,60.09772],[-44.7875,60.03676],[-46.26364,60.85328],[-48.26294,60.85843],[-49.23308,61.40681],[-49.90039,62.38336],[-51.63325,63.62691],[-52.14014,64.27842],[-52.27659,65.1767],[-53.66166,66.09957],[-53.30161,66.8365],[-53.96911,67.18899],[-52.9804,68.35759],[-51.47536,68.72958],[-51.08041,69.14781],[-50.87122,69.9291],[-52.013585,69.574925],[-52.55792,69.42616],[-53.45629,69.283625],[-54.68336,69.61003],[-54.75001,70.28932],[-54.35884,70.821315],[-53.431315,70.835755],[-51.39014,70.56978],[-53.10937,71.20485],[-54.00422,71.54719],[-55,71.406537],[-55.83468,71.65444],[-54.71819,72.58625],[-55.32634,72.95861],[-56.12003,73.64977],[-57.32363,74.71026],[-58.59679,75.09861],[-58.58516,75.51727],[-61.26861,76.10238],[-63.39165,76.1752],[-66.06427,76.13486],[-68.50438,76.06141],[-69.66485,76.37975],[-71.40257,77.00857],[-68.77671,77.32312],[-66.76397,77.37595],[-71.04293,77.63595],[-73.297,78.04419],[-73.15938,78.43271],[-69.37345,78.91388],[-65.7107,79.39436],[-65.3239,79.75814],[-68.02298,80.11721],[-67.15129,80.51582],[-63.68925,81.21396],[-62.23444,81.3211],[-62.65116,81.77042],[-60.28249,82.03363],[-57.20744,82.19074],[-54.13442,82.19962],[-53.04328,81.88833],[-50.39061,82.43883],[-48.00386,82.06481],[-46.59984,81.985945],[-44.523,81.6607],[-46.9007,82.19979],[-46.76379,82.62796]]]}}, {"type":"Feature","id":"GTM","properties":{"name":"Guatemala"},"geometry":{"type":"Polygon","coordinates":[[[-90.095555,13.735338],[-90.608624,13.909771],[-91.23241,13.927832],[-91.689747,14.126218],[-92.22775,14.538829],[-92.20323,14.830103],[-92.087216,15.064585],[-92.229249,15.251447],[-91.74796,16.066565],[-90.464473,16.069562],[-90.438867,16.41011],[-90.600847,16.470778],[-90.711822,16.687483],[-91.08167,16.918477],[-91.453921,17.252177],[-91.002269,17.254658],[-91.00152,17.817595],[-90.067934,17.819326],[-89.14308,17.808319],[-89.150806,17.015577],[-89.229122,15.886938],[-88.930613,15.887273],[-88.604586,15.70638],[-88.518364,15.855389],[-88.225023,15.727722],[-88.68068,15.346247],[-89.154811,15.066419],[-89.22522,14.874286],[-89.145535,14.678019],[-89.353326,14.424133],[-89.587343,14.362586],[-89.534219,14.244816],[-89.721934,14.134228],[-90.064678,13.88197],[-90.095555,13.735338]]]}}, {"type":"Feature","id":"GUF","properties":{"name":"French Guiana"},"geometry":{"type":"Polygon","coordinates":[[[-52.556425,2.504705],[-52.939657,2.124858],[-53.418465,2.053389],[-53.554839,2.334897],[-53.778521,2.376703],[-54.088063,2.105557],[-54.524754,2.311849],[-54.27123,2.738748],[-54.184284,3.194172],[-54.011504,3.62257],[-54.399542,4.212611],[-54.478633,4.896756],[-53.958045,5.756548],[-53.618453,5.646529],[-52.882141,5.409851],[-51.823343,4.565768],[-51.657797,4.156232],[-52.249338,3.241094],[-52.556425,2.504705]]]}}, {"type":"Feature","id":"GUY","properties":{"name":"Guyana"},"geometry":{"type":"Polygon","coordinates":[[[-59.758285,8.367035],[-59.101684,7.999202],[-58.482962,7.347691],[-58.454876,6.832787],[-58.078103,6.809094],[-57.542219,6.321268],[-57.147436,5.97315],[-57.307246,5.073567],[-57.914289,4.812626],[-57.86021,4.576801],[-58.044694,4.060864],[-57.601569,3.334655],[-57.281433,3.333492],[-57.150098,2.768927],[-56.539386,1.899523],[-56.782704,1.863711],[-57.335823,1.948538],[-57.660971,1.682585],[-58.11345,1.507195],[-58.429477,1.463942],[-58.540013,1.268088],[-59.030862,1.317698],[-59.646044,1.786894],[-59.718546,2.24963],[-59.974525,2.755233],[-59.815413,3.606499],[-59.53804,3.958803],[-59.767406,4.423503],[-60.111002,4.574967],[-59.980959,5.014061],[-60.213683,5.244486],[-60.733574,5.200277],[-61.410303,5.959068],[-61.139415,6.234297],[-61.159336,6.696077],[-60.543999,6.856584],[-60.295668,7.043911],[-60.637973,7.415],[-60.550588,7.779603],[-59.758285,8.367035]]]}}, {"type":"Feature","id":"HND","properties":{"name":"Honduras"},"geometry":{"type":"Polygon","coordinates":[[[-87.316654,12.984686],[-87.489409,13.297535],[-87.793111,13.38448],[-87.723503,13.78505],[-87.859515,13.893312],[-88.065343,13.964626],[-88.503998,13.845486],[-88.541231,13.980155],[-88.843073,14.140507],[-89.058512,14.340029],[-89.353326,14.424133],[-89.145535,14.678019],[-89.22522,14.874286],[-89.154811,15.066419],[-88.68068,15.346247],[-88.225023,15.727722],[-88.121153,15.688655],[-87.901813,15.864458],[-87.61568,15.878799],[-87.522921,15.797279],[-87.367762,15.84694],[-86.903191,15.756713],[-86.440946,15.782835],[-86.119234,15.893449],[-86.001954,16.005406],[-85.683317,15.953652],[-85.444004,15.885749],[-85.182444,15.909158],[-84.983722,15.995923],[-84.52698,15.857224],[-84.368256,15.835158],[-84.063055,15.648244],[-83.773977,15.424072],[-83.410381,15.270903],[-83.147219,14.995829],[-83.489989,15.016267],[-83.628585,14.880074],[-83.975721,14.749436],[-84.228342,14.748764],[-84.449336,14.621614],[-84.649582,14.666805],[-84.820037,14.819587],[-84.924501,14.790493],[-85.052787,14.551541],[-85.148751,14.560197],[-85.165365,14.35437],[-85.514413,14.079012],[-85.698665,13.960078],[-85.801295,13.836055],[-86.096264,14.038187],[-86.312142,13.771356],[-86.520708,13.778487],[-86.755087,13.754845],[-86.733822,13.263093],[-86.880557,13.254204],[-87.005769,13.025794],[-87.316654,12.984686]]]}}, {"type":"Feature","id":"HRV","properties":{"name":"Croatia"},"geometry":{"type":"Polygon","coordinates":[[[18.829838,45.908878],[19.072769,45.521511],[19.390476,45.236516],[19.005486,44.860234],[18.553214,45.08159],[17.861783,45.06774],[17.002146,45.233777],[16.534939,45.211608],[16.318157,45.004127],[15.959367,45.233777],[15.750026,44.818712],[16.23966,44.351143],[16.456443,44.04124],[16.916156,43.667722],[17.297373,43.446341],[17.674922,43.028563],[18.56,42.65],[18.450016,42.479991],[17.50997,42.849995],[16.930006,43.209998],[16.015385,43.507215],[15.174454,44.243191],[15.37625,44.317915],[14.920309,44.738484],[14.901602,45.07606],[14.258748,45.233777],[13.952255,44.802124],[13.656976,45.136935],[13.679403,45.484149],[13.71506,45.500324],[14.411968,45.466166],[14.595109,45.634941],[14.935244,45.471695],[15.327675,45.452316],[15.323954,45.731783],[15.67153,45.834154],[15.768733,46.238108],[16.564808,46.503751],[16.882515,46.380632],[17.630066,45.951769],[18.456062,45.759481],[18.829838,45.908878]]]}}, {"type":"Feature","id":"HTI","properties":{"name":"Haiti"},"geometry":{"type":"Polygon","coordinates":[[[-73.189791,19.915684],[-72.579673,19.871501],[-71.712361,19.714456],[-71.624873,19.169838],[-71.701303,18.785417],[-71.945112,18.6169],[-71.687738,18.31666],[-71.708305,18.044997],[-72.372476,18.214961],[-72.844411,18.145611],[-73.454555,18.217906],[-73.922433,18.030993],[-74.458034,18.34255],[-74.369925,18.664908],[-73.449542,18.526053],[-72.694937,18.445799],[-72.334882,18.668422],[-72.79165,19.101625],[-72.784105,19.483591],[-73.415022,19.639551],[-73.189791,19.915684]]]}}, {"type":"Feature","id":"HUN","properties":{"name":"Hungary"},"geometry":{"type":"Polygon","coordinates":[[[16.202298,46.852386],[16.534268,47.496171],[16.340584,47.712902],[16.903754,47.714866],[16.979667,48.123497],[17.488473,47.867466],[17.857133,47.758429],[18.696513,47.880954],[18.777025,48.081768],[19.174365,48.111379],[19.661364,48.266615],[19.769471,48.202691],[20.239054,48.327567],[20.473562,48.56285],[20.801294,48.623854],[21.872236,48.319971],[22.085608,48.422264],[22.64082,48.15024],[22.710531,47.882194],[22.099768,47.672439],[21.626515,46.994238],[21.021952,46.316088],[20.220192,46.127469],[19.596045,46.17173],[18.829838,45.908878],[18.456062,45.759481],[17.630066,45.951769],[16.882515,46.380632],[16.564808,46.503751],[16.370505,46.841327],[16.202298,46.852386]]]}}, {"type":"Feature","id":"IDN","properties":{"name":"Indonesia"},"geometry":{"type":"MultiPolygon","coordinates":[[[[120.715609,-10.239581],[120.295014,-10.25865],[118.967808,-9.557969],[119.90031,-9.36134],[120.425756,-9.665921],[120.775502,-9.969675],[120.715609,-10.239581]]],[[[124.43595,-10.140001],[123.579982,-10.359987],[123.459989,-10.239995],[123.550009,-9.900016],[123.980009,-9.290027],[124.968682,-8.89279],[125.07002,-9.089987],[125.08852,-9.393173],[124.43595,-10.140001]]],[[[117.900018,-8.095681],[118.260616,-8.362383],[118.87846,-8.280683],[119.126507,-8.705825],[117.970402,-8.906639],[117.277731,-9.040895],[116.740141,-9.032937],[117.083737,-8.457158],[117.632024,-8.449303],[117.900018,-8.095681]]],[[[122.903537,-8.094234],[122.756983,-8.649808],[121.254491,-8.933666],[119.924391,-8.810418],[119.920929,-8.444859],[120.715092,-8.236965],[121.341669,-8.53674],[122.007365,-8.46062],[122.903537,-8.094234]]],[[[108.623479,-6.777674],[110.539227,-6.877358],[110.759576,-6.465186],[112.614811,-6.946036],[112.978768,-7.594213],[114.478935,-7.776528],[115.705527,-8.370807],[114.564511,-8.751817],[113.464734,-8.348947],[112.559672,-8.376181],[111.522061,-8.302129],[110.58615,-8.122605],[109.427667,-7.740664],[108.693655,-7.6416],[108.277763,-7.766657],[106.454102,-7.3549],[106.280624,-6.9249],[105.365486,-6.851416],[106.051646,-5.895919],[107.265009,-5.954985],[108.072091,-6.345762],[108.486846,-6.421985],[108.623479,-6.777674]]],[[[134.724624,-6.214401],[134.210134,-6.895238],[134.112776,-6.142467],[134.290336,-5.783058],[134.499625,-5.445042],[134.727002,-5.737582],[134.724624,-6.214401]]],[[[127.249215,-3.459065],[126.874923,-3.790983],[126.183802,-3.607376],[125.989034,-3.177273],[127.000651,-3.129318],[127.249215,-3.459065]]],[[[130.471344,-3.093764],[130.834836,-3.858472],[129.990547,-3.446301],[129.155249,-3.362637],[128.590684,-3.428679],[127.898891,-3.393436],[128.135879,-2.84365],[129.370998,-2.802154],[130.471344,-3.093764]]],[[[134.143368,-1.151867],[134.422627,-2.769185],[135.457603,-3.367753],[136.293314,-2.307042],[137.440738,-1.703513],[138.329727,-1.702686],[139.184921,-2.051296],[139.926684,-2.409052],[141.00021,-2.600151],[141.017057,-5.859022],[141.033852,-9.117893],[140.143415,-8.297168],[139.127767,-8.096043],[138.881477,-8.380935],[137.614474,-8.411683],[138.039099,-7.597882],[138.668621,-7.320225],[138.407914,-6.232849],[137.92784,-5.393366],[135.98925,-4.546544],[135.164598,-4.462931],[133.66288,-3.538853],[133.367705,-4.024819],[132.983956,-4.112979],[132.756941,-3.746283],[132.753789,-3.311787],[131.989804,-2.820551],[133.066845,-2.460418],[133.780031,-2.479848],[133.696212,-2.214542],[132.232373,-2.212526],[131.836222,-1.617162],[130.94284,-1.432522],[130.519558,-0.93772],[131.867538,-0.695461],[132.380116,-0.369538],[133.985548,-0.78021],[134.143368,-1.151867]]],[[[125.240501,1.419836],[124.437035,0.427881],[123.685505,0.235593],[122.723083,0.431137],[121.056725,0.381217],[120.183083,0.237247],[120.04087,-0.519658],[120.935905,-1.408906],[121.475821,-0.955962],[123.340565,-0.615673],[123.258399,-1.076213],[122.822715,-0.930951],[122.38853,-1.516858],[121.508274,-1.904483],[122.454572,-3.186058],[122.271896,-3.5295],[123.170963,-4.683693],[123.162333,-5.340604],[122.628515,-5.634591],[122.236394,-5.282933],[122.719569,-4.464172],[121.738234,-4.851331],[121.489463,-4.574553],[121.619171,-4.188478],[120.898182,-3.602105],[120.972389,-2.627643],[120.305453,-2.931604],[120.390047,-4.097579],[120.430717,-5.528241],[119.796543,-5.6734],[119.366906,-5.379878],[119.653606,-4.459417],[119.498835,-3.494412],[119.078344,-3.487022],[118.767769,-2.801999],[119.180974,-2.147104],[119.323394,-1.353147],[119.825999,0.154254],[120.035702,0.566477],[120.885779,1.309223],[121.666817,1.013944],[122.927567,0.875192],[124.077522,0.917102],[125.065989,1.643259],[125.240501,1.419836]]],[[[128.688249,1.132386],[128.635952,0.258486],[128.12017,0.356413],[127.968034,-0.252077],[128.379999,-0.780004],[128.100016,-0.899996],[127.696475,-0.266598],[127.39949,1.011722],[127.600512,1.810691],[127.932378,2.174596],[128.004156,1.628531],[128.594559,1.540811],[128.688249,1.132386]]],[[[117.875627,1.827641],[118.996747,0.902219],[117.811858,0.784242],[117.478339,0.102475],[117.521644,-0.803723],[116.560048,-1.487661],[116.533797,-2.483517],[116.148084,-4.012726],[116.000858,-3.657037],[114.864803,-4.106984],[114.468652,-3.495704],[113.755672,-3.43917],[113.256994,-3.118776],[112.068126,-3.478392],[111.703291,-2.994442],[111.04824,-3.049426],[110.223846,-2.934032],[110.070936,-1.592874],[109.571948,-1.314907],[109.091874,-0.459507],[108.952658,0.415375],[109.069136,1.341934],[109.66326,2.006467],[109.830227,1.338136],[110.514061,0.773131],[111.159138,0.976478],[111.797548,0.904441],[112.380252,1.410121],[112.859809,1.49779],[113.80585,1.217549],[114.621355,1.430688],[115.134037,2.821482],[115.519078,3.169238],[115.865517,4.306559],[117.015214,4.306094],[117.882035,4.137551],[117.313232,3.234428],[118.04833,2.28769],[117.875627,1.827641]]],[[[105.817655,-5.852356],[104.710384,-5.873285],[103.868213,-5.037315],[102.584261,-4.220259],[102.156173,-3.614146],[101.399113,-2.799777],[100.902503,-2.050262],[100.141981,-0.650348],[99.26374,0.183142],[98.970011,1.042882],[98.601351,1.823507],[97.699598,2.453184],[97.176942,3.308791],[96.424017,3.86886],[95.380876,4.970782],[95.293026,5.479821],[95.936863,5.439513],[97.484882,5.246321],[98.369169,4.26837],[99.142559,3.59035],[99.693998,3.174329],[100.641434,2.099381],[101.658012,2.083697],[102.498271,1.3987],[103.07684,0.561361],[103.838396,0.104542],[103.437645,-0.711946],[104.010789,-1.059212],[104.369991,-1.084843],[104.53949,-1.782372],[104.887893,-2.340425],[105.622111,-2.428844],[106.108593,-3.061777],[105.857446,-4.305525],[105.817655,-5.852356]]]]}}, {"type":"Feature","id":"IND","properties":{"name":"India"},"geometry":{"type":"Polygon","coordinates":[[[77.837451,35.49401],[78.912269,34.321936],[78.811086,33.506198],[79.208892,32.994395],[79.176129,32.48378],[78.458446,32.618164],[78.738894,31.515906],[79.721367,30.882715],[81.111256,30.183481],[80.476721,29.729865],[80.088425,28.79447],[81.057203,28.416095],[81.999987,27.925479],[83.304249,27.364506],[84.675018,27.234901],[85.251779,26.726198],[86.024393,26.630985],[87.227472,26.397898],[88.060238,26.414615],[88.174804,26.810405],[88.043133,27.445819],[88.120441,27.876542],[88.730326,28.086865],[88.814248,27.299316],[88.835643,27.098966],[89.744528,26.719403],[90.373275,26.875724],[91.217513,26.808648],[92.033484,26.83831],[92.103712,27.452614],[91.696657,27.771742],[92.503119,27.896876],[93.413348,28.640629],[94.56599,29.277438],[95.404802,29.031717],[96.117679,29.452802],[96.586591,28.83098],[96.248833,28.411031],[97.327114,28.261583],[97.402561,27.882536],[97.051989,27.699059],[97.133999,27.083774],[96.419366,27.264589],[95.124768,26.573572],[95.155153,26.001307],[94.603249,25.162495],[94.552658,24.675238],[94.106742,23.850741],[93.325188,24.078556],[93.286327,23.043658],[93.060294,22.703111],[93.166128,22.27846],[92.672721,22.041239],[92.146035,23.627499],[91.869928,23.624346],[91.706475,22.985264],[91.158963,23.503527],[91.46773,24.072639],[91.915093,24.130414],[92.376202,24.976693],[91.799596,25.147432],[90.872211,25.132601],[89.920693,25.26975],[89.832481,25.965082],[89.355094,26.014407],[88.563049,26.446526],[88.209789,25.768066],[88.931554,25.238692],[88.306373,24.866079],[88.084422,24.501657],[88.69994,24.233715],[88.52977,23.631142],[88.876312,22.879146],[89.031961,22.055708],[88.888766,21.690588],[88.208497,21.703172],[86.975704,21.495562],[87.033169,20.743308],[86.499351,20.151638],[85.060266,19.478579],[83.941006,18.30201],[83.189217,17.671221],[82.192792,17.016636],[82.191242,16.556664],[81.692719,16.310219],[80.791999,15.951972],[80.324896,15.899185],[80.025069,15.136415],[80.233274,13.835771],[80.286294,13.006261],[79.862547,12.056215],[79.857999,10.357275],[79.340512,10.308854],[78.885345,9.546136],[79.18972,9.216544],[78.277941,8.933047],[77.941165,8.252959],[77.539898,7.965535],[76.592979,8.899276],[76.130061,10.29963],[75.746467,11.308251],[75.396101,11.781245],[74.864816,12.741936],[74.616717,13.992583],[74.443859,14.617222],[73.534199,15.990652],[73.119909,17.92857],[72.820909,19.208234],[72.824475,20.419503],[72.630533,21.356009],[71.175273,20.757441],[70.470459,20.877331],[69.16413,22.089298],[69.644928,22.450775],[69.349597,22.84318],[68.176645,23.691965],[68.842599,24.359134],[71.04324,24.356524],[70.844699,25.215102],[70.282873,25.722229],[70.168927,26.491872],[69.514393,26.940966],[70.616496,27.989196],[71.777666,27.91318],[72.823752,28.961592],[73.450638,29.976413],[74.42138,30.979815],[74.405929,31.692639],[75.258642,32.271105],[74.451559,32.7649],[74.104294,33.441473],[73.749948,34.317699],[74.240203,34.748887],[75.757061,34.504923],[76.871722,34.653544],[77.837451,35.49401]]]}}, {"type":"Feature","id":"IRL","properties":{"name":"Ireland"},"geometry":{"type":"Polygon","coordinates":[[[-6.197885,53.867565],[-6.032985,53.153164],[-6.788857,52.260118],[-8.561617,51.669301],[-9.977086,51.820455],[-9.166283,52.864629],[-9.688525,53.881363],[-8.327987,54.664519],[-7.572168,55.131622],[-7.366031,54.595841],[-7.572168,54.059956],[-6.95373,54.073702],[-6.197885,53.867565]]]}}, {"type":"Feature","id":"IRN","properties":{"name":"Iran"},"geometry":{"type":"Polygon","coordinates":[[[53.921598,37.198918],[54.800304,37.392421],[55.511578,37.964117],[56.180375,37.935127],[56.619366,38.121394],[57.330434,38.029229],[58.436154,37.522309],[59.234762,37.412988],[60.377638,36.527383],[61.123071,36.491597],[61.210817,35.650072],[60.803193,34.404102],[60.52843,33.676446],[60.9637,33.528832],[60.536078,32.981269],[60.863655,32.18292],[60.941945,31.548075],[61.699314,31.379506],[61.781222,30.73585],[60.874248,29.829239],[61.369309,29.303276],[61.771868,28.699334],[62.72783,28.259645],[62.755426,27.378923],[63.233898,27.217047],[63.316632,26.756532],[61.874187,26.239975],[61.497363,25.078237],[59.616134,25.380157],[58.525761,25.609962],[57.397251,25.739902],[56.970766,26.966106],[56.492139,27.143305],[55.72371,26.964633],[54.71509,26.480658],[53.493097,26.812369],[52.483598,27.580849],[51.520763,27.86569],[50.852948,28.814521],[50.115009,30.147773],[49.57685,29.985715],[48.941333,30.31709],[48.567971,29.926778],[48.014568,30.452457],[48.004698,30.985137],[47.685286,30.984853],[47.849204,31.709176],[47.334661,32.469155],[46.109362,33.017287],[45.416691,33.967798],[45.64846,34.748138],[46.151788,35.093259],[46.07634,35.677383],[45.420618,35.977546],[44.77267,37.17045],[44.225756,37.971584],[44.421403,38.281281],[44.109225,39.428136],[44.79399,39.713003],[44.952688,39.335765],[45.457722,38.874139],[46.143623,38.741201],[46.50572,38.770605],[47.685079,39.508364],[48.060095,39.582235],[48.355529,39.288765],[48.010744,38.794015],[48.634375,38.270378],[48.883249,38.320245],[49.199612,37.582874],[50.147771,37.374567],[50.842354,36.872814],[52.264025,36.700422],[53.82579,36.965031],[53.921598,37.198918]]]}}, {"type":"Feature","id":"IRQ","properties":{"name":"Iraq"},"geometry":{"type":"Polygon","coordinates":[[[45.420618,35.977546],[46.07634,35.677383],[46.151788,35.093259],[45.64846,34.748138],[45.416691,33.967798],[46.109362,33.017287],[47.334661,32.469155],[47.849204,31.709176],[47.685286,30.984853],[48.004698,30.985137],[48.014568,30.452457],[48.567971,29.926778],[47.974519,29.975819],[47.302622,30.05907],[46.568713,29.099025],[44.709499,29.178891],[41.889981,31.190009],[40.399994,31.889992],[39.195468,32.161009],[38.792341,33.378686],[41.006159,34.419372],[41.383965,35.628317],[41.289707,36.358815],[41.837064,36.605854],[42.349591,37.229873],[42.779126,37.385264],[43.942259,37.256228],[44.293452,37.001514],[44.772699,37.170445],[45.420618,35.977546]]]}}, {"type":"Feature","id":"ISL","properties":{"name":"Iceland"},"geometry":{"type":"Polygon","coordinates":[[[-14.508695,66.455892],[-14.739637,65.808748],[-13.609732,65.126671],[-14.909834,64.364082],[-17.794438,63.678749],[-18.656246,63.496383],[-19.972755,63.643635],[-22.762972,63.960179],[-21.778484,64.402116],[-23.955044,64.89113],[-22.184403,65.084968],[-22.227423,65.378594],[-24.326184,65.611189],[-23.650515,66.262519],[-22.134922,66.410469],[-20.576284,65.732112],[-19.056842,66.276601],[-17.798624,65.993853],[-16.167819,66.526792],[-14.508695,66.455892]]]}}, {"type":"Feature","id":"ISR","properties":{"name":"Israel"},"geometry":{"type":"Polygon","coordinates":[[[35.719918,32.709192],[35.545665,32.393992],[35.18393,32.532511],[34.974641,31.866582],[35.225892,31.754341],[34.970507,31.616778],[34.927408,31.353435],[35.397561,31.489086],[35.420918,31.100066],[34.922603,29.501326],[34.265433,31.219361],[34.556372,31.548824],[34.488107,31.605539],[34.752587,32.072926],[34.955417,32.827376],[35.098457,33.080539],[35.126053,33.0909],[35.460709,33.08904],[35.552797,33.264275],[35.821101,33.277426],[35.836397,32.868123],[35.700798,32.716014],[35.719918,32.709192]]]}}, {"type":"Feature","id":"ITA","properties":{"name":"Italy"},"geometry":{"type":"MultiPolygon","coordinates":[[[[15.520376,38.231155],[15.160243,37.444046],[15.309898,37.134219],[15.099988,36.619987],[14.335229,36.996631],[13.826733,37.104531],[12.431004,37.61295],[12.570944,38.126381],[13.741156,38.034966],[14.761249,38.143874],[15.520376,38.231155]]],[[[9.210012,41.209991],[9.809975,40.500009],[9.669519,39.177376],[9.214818,39.240473],[8.806936,38.906618],[8.428302,39.171847],[8.388253,40.378311],[8.159998,40.950007],[8.709991,40.899984],[9.210012,41.209991]]],[[[12.376485,46.767559],[13.806475,46.509306],[13.69811,46.016778],[13.93763,45.591016],[13.141606,45.736692],[12.328581,45.381778],[12.383875,44.885374],[12.261453,44.600482],[12.589237,44.091366],[13.526906,43.587727],[14.029821,42.761008],[15.14257,41.95514],[15.926191,41.961315],[16.169897,41.740295],[15.889346,41.541082],[16.785002,41.179606],[17.519169,40.877143],[18.376687,40.355625],[18.480247,40.168866],[18.293385,39.810774],[17.73838,40.277671],[16.869596,40.442235],[16.448743,39.795401],[17.17149,39.4247],[17.052841,38.902871],[16.635088,38.843572],[16.100961,37.985899],[15.684087,37.908849],[15.687963,38.214593],[15.891981,38.750942],[16.109332,38.964547],[15.718814,39.544072],[15.413613,40.048357],[14.998496,40.172949],[14.703268,40.60455],[14.060672,40.786348],[13.627985,41.188287],[12.888082,41.25309],[12.106683,41.704535],[11.191906,42.355425],[10.511948,42.931463],[10.200029,43.920007],[9.702488,44.036279],[8.888946,44.366336],[8.428561,44.231228],[7.850767,43.767148],[7.435185,43.693845],[7.549596,44.127901],[7.007562,44.254767],[6.749955,45.028518],[7.096652,45.333099],[6.802355,45.70858],[6.843593,45.991147],[7.273851,45.776948],[7.755992,45.82449],[8.31663,46.163642],[8.489952,46.005151],[8.966306,46.036932],[9.182882,46.440215],[9.922837,46.314899],[10.363378,46.483571],[10.442701,46.893546],[11.048556,46.751359],[11.164828,46.941579],[12.153088,47.115393],[12.376485,46.767559]]]]}}, {"type":"Feature","id":"JAM","properties":{"name":"Jamaica"},"geometry":{"type":"Polygon","coordinates":[[[-77.569601,18.490525],[-76.896619,18.400867],[-76.365359,18.160701],[-76.199659,17.886867],[-76.902561,17.868238],[-77.206341,17.701116],[-77.766023,17.861597],[-78.337719,18.225968],[-78.217727,18.454533],[-77.797365,18.524218],[-77.569601,18.490525]]]}}, {"type":"Feature","id":"JOR","properties":{"name":"Jordan"},"geometry":{"type":"Polygon","coordinates":[[[35.545665,32.393992],[35.719918,32.709192],[36.834062,32.312938],[38.792341,33.378686],[39.195468,32.161009],[39.004886,32.010217],[37.002166,31.508413],[37.998849,30.5085],[37.66812,30.338665],[37.503582,30.003776],[36.740528,29.865283],[36.501214,29.505254],[36.068941,29.197495],[34.956037,29.356555],[34.922603,29.501326],[35.420918,31.100066],[35.397561,31.489086],[35.545252,31.782505],[35.545665,32.393992]]]}}, {"type":"Feature","id":"JPN","properties":{"name":"Japan"},"geometry":{"type":"MultiPolygon","coordinates":[[[[134.638428,34.149234],[134.766379,33.806335],[134.203416,33.201178],[133.79295,33.521985],[133.280268,33.28957],[133.014858,32.704567],[132.363115,32.989382],[132.371176,33.463642],[132.924373,34.060299],[133.492968,33.944621],[133.904106,34.364931],[134.638428,34.149234]]],[[[140.976388,37.142074],[140.59977,36.343983],[140.774074,35.842877],[140.253279,35.138114],[138.975528,34.6676],[137.217599,34.606286],[135.792983,33.464805],[135.120983,33.849071],[135.079435,34.596545],[133.340316,34.375938],[132.156771,33.904933],[130.986145,33.885761],[132.000036,33.149992],[131.33279,31.450355],[130.686318,31.029579],[130.20242,31.418238],[130.447676,32.319475],[129.814692,32.61031],[129.408463,33.296056],[130.353935,33.604151],[130.878451,34.232743],[131.884229,34.749714],[132.617673,35.433393],[134.608301,35.731618],[135.677538,35.527134],[136.723831,37.304984],[137.390612,36.827391],[138.857602,37.827485],[139.426405,38.215962],[140.05479,39.438807],[139.883379,40.563312],[140.305783,41.195005],[141.368973,41.37856],[141.914263,39.991616],[141.884601,39.180865],[140.959489,38.174001],[140.976388,37.142074]]],[[[143.910162,44.1741],[144.613427,43.960883],[145.320825,44.384733],[145.543137,43.262088],[144.059662,42.988358],[143.18385,41.995215],[141.611491,42.678791],[141.067286,41.584594],[139.955106,41.569556],[139.817544,42.563759],[140.312087,43.333273],[141.380549,43.388825],[141.671952,44.772125],[141.967645,45.551483],[143.14287,44.510358],[143.910162,44.1741]]]]}}, {"type":"Feature","id":"KAZ","properties":{"name":"Kazakhstan"},"geometry":{"type":"Polygon","coordinates":[[[70.962315,42.266154],[70.388965,42.081308],[69.070027,41.384244],[68.632483,40.668681],[68.259896,40.662325],[67.985856,41.135991],[66.714047,41.168444],[66.510649,41.987644],[66.023392,41.994646],[66.098012,42.99766],[64.900824,43.728081],[63.185787,43.650075],[62.0133,43.504477],[61.05832,44.405817],[60.239972,44.784037],[58.689989,45.500014],[58.503127,45.586804],[55.928917,44.995858],[55.968191,41.308642],[55.455251,41.259859],[54.755345,42.043971],[54.079418,42.324109],[52.944293,42.116034],[52.50246,41.783316],[52.446339,42.027151],[52.692112,42.443895],[52.501426,42.792298],[51.342427,43.132975],[50.891292,44.031034],[50.339129,44.284016],[50.305643,44.609836],[51.278503,44.514854],[51.316899,45.245998],[52.16739,45.408391],[53.040876,45.259047],[53.220866,46.234646],[53.042737,46.853006],[52.042023,46.804637],[51.191945,47.048705],[50.034083,46.60899],[49.10116,46.39933],[48.593241,46.561034],[48.694734,47.075628],[48.057253,47.743753],[47.315231,47.715847],[46.466446,48.394152],[47.043672,49.152039],[46.751596,49.356006],[47.54948,50.454698],[48.577841,49.87476],[48.702382,50.605128],[50.766648,51.692762],[52.328724,51.718652],[54.532878,51.02624],[55.716941,50.621717],[56.777961,51.043551],[58.363291,51.063653],[59.642282,50.545442],[59.932807,50.842194],[61.337424,50.79907],[61.588003,51.272659],[59.967534,51.96042],[60.927269,52.447548],[60.739993,52.719986],[61.699986,52.979996],[60.978066,53.664993],[61.436591,54.006265],[65.178534,54.354228],[65.666876,54.601267],[68.1691,54.970392],[69.068167,55.38525],[70.865267,55.169734],[71.180131,54.133285],[72.22415,54.376655],[73.508516,54.035617],[73.425679,53.48981],[74.384845,53.546861],[76.8911,54.490524],[76.525179,54.177003],[77.800916,53.404415],[80.03556,50.864751],[80.568447,51.388336],[81.945986,50.812196],[83.383004,51.069183],[83.935115,50.889246],[84.416377,50.3114],[85.11556,50.117303],[85.54127,49.692859],[86.829357,49.826675],[87.35997,49.214981],[86.598776,48.549182],[85.768233,48.455751],[85.720484,47.452969],[85.16429,47.000956],[83.180484,47.330031],[82.458926,45.53965],[81.947071,45.317027],[79.966106,44.917517],[80.866206,43.180362],[80.18015,42.920068],[80.25999,42.349999],[79.643645,42.496683],[79.142177,42.856092],[77.658392,42.960686],[76.000354,42.988022],[75.636965,42.8779],[74.212866,43.298339],[73.645304,43.091272],[73.489758,42.500894],[71.844638,42.845395],[71.186281,42.704293],[70.962315,42.266154]]]}}, {"type":"Feature","id":"KEN","properties":{"name":"Kenya"},"geometry":{"type":"Polygon","coordinates":[[[40.993,-0.85829],[41.58513,-1.68325],[40.88477,-2.08255],[40.63785,-2.49979],[40.26304,-2.57309],[40.12119,-3.27768],[39.80006,-3.68116],[39.60489,-4.34653],[39.20222,-4.67677],[37.7669,-3.67712],[37.69869,-3.09699],[34.07262,-1.05982],[33.903711,-0.95],[33.893569,0.109814],[34.18,0.515],[34.6721,1.17694],[35.03599,1.90584],[34.59607,3.05374],[34.47913,3.5556],[34.005,4.249885],[34.620196,4.847123],[35.298007,5.506],[35.817448,5.338232],[35.817448,4.776966],[36.159079,4.447864],[36.855093,4.447864],[38.120915,3.598605],[38.43697,3.58851],[38.67114,3.61607],[38.89251,3.50074],[39.559384,3.42206],[39.85494,3.83879],[40.76848,4.25702],[41.1718,3.91909],[41.855083,3.918912],[40.98105,2.78452],[40.993,-0.85829]]]}}, {"type":"Feature","id":"KGZ","properties":{"name":"Kyrgyzstan"},"geometry":{"type":"Polygon","coordinates":[[[70.962315,42.266154],[71.186281,42.704293],[71.844638,42.845395],[73.489758,42.500894],[73.645304,43.091272],[74.212866,43.298339],[75.636965,42.8779],[76.000354,42.988022],[77.658392,42.960686],[79.142177,42.856092],[79.643645,42.496683],[80.25999,42.349999],[80.11943,42.123941],[78.543661,41.582243],[78.187197,41.185316],[76.904484,41.066486],[76.526368,40.427946],[75.467828,40.562072],[74.776862,40.366425],[73.822244,39.893973],[73.960013,39.660008],[73.675379,39.431237],[71.784694,39.279463],[70.549162,39.604198],[69.464887,39.526683],[69.55961,40.103211],[70.648019,39.935754],[71.014198,40.244366],[71.774875,40.145844],[73.055417,40.866033],[71.870115,41.3929],[71.157859,41.143587],[70.420022,41.519998],[71.259248,42.167711],[70.962315,42.266154]]]}}, {"type":"Feature","id":"KHM","properties":{"name":"Cambodia"},"geometry":{"type":"Polygon","coordinates":[[[103.49728,10.632555],[103.09069,11.153661],[102.584932,12.186595],[102.348099,13.394247],[102.988422,14.225721],[104.281418,14.416743],[105.218777,14.273212],[106.043946,13.881091],[106.496373,14.570584],[107.382727,14.202441],[107.614548,13.535531],[107.491403,12.337206],[105.810524,11.567615],[106.24967,10.961812],[105.199915,10.88931],[104.334335,10.486544],[103.49728,10.632555]]]}}, {"type":"Feature","id":"KOR","properties":{"name":"South Korea"},"geometry":{"type":"Polygon","coordinates":[[[128.349716,38.612243],[129.21292,37.432392],[129.46045,36.784189],[129.468304,35.632141],[129.091377,35.082484],[128.18585,34.890377],[127.386519,34.475674],[126.485748,34.390046],[126.37392,34.93456],[126.559231,35.684541],[126.117398,36.725485],[126.860143,36.893924],[126.174759,37.749686],[126.237339,37.840378],[126.68372,37.804773],[127.073309,38.256115],[127.780035,38.304536],[128.205746,38.370397],[128.349716,38.612243]]]}}, {"type":"Feature","id":"CS-KM","properties":{"name":"Kosovo"},"geometry":{"type":"Polygon","coordinates":[[[20.76216,42.05186],[20.71731,41.84711],[20.59023,41.85541],[20.52295,42.21787],[20.28374,42.32025],[20.0707,42.58863],[20.25758,42.81275],[20.49679,42.88469],[20.63508,43.21671],[20.81448,43.27205],[20.95651,43.13094],[21.143395,43.068685],[21.27421,42.90959],[21.43866,42.86255],[21.63302,42.67717],[21.77505,42.6827],[21.66292,42.43922],[21.54332,42.32025],[21.576636,42.245224],[21.3527,42.2068],[20.76216,42.05186]]]}}, {"type":"Feature","id":"KWT","properties":{"name":"Kuwait"},"geometry":{"type":"Polygon","coordinates":[[[47.974519,29.975819],[48.183189,29.534477],[48.093943,29.306299],[48.416094,28.552004],[47.708851,28.526063],[47.459822,29.002519],[46.568713,29.099025],[47.302622,30.05907],[47.974519,29.975819]]]}}, {"type":"Feature","id":"LAO","properties":{"name":"Laos"},"geometry":{"type":"Polygon","coordinates":[[[105.218777,14.273212],[105.544338,14.723934],[105.589039,15.570316],[104.779321,16.441865],[104.716947,17.428859],[103.956477,18.240954],[103.200192,18.309632],[102.998706,17.961695],[102.413005,17.932782],[102.113592,18.109102],[101.059548,17.512497],[101.035931,18.408928],[101.282015,19.462585],[100.606294,19.508344],[100.548881,20.109238],[100.115988,20.41785],[100.329101,20.786122],[101.180005,21.436573],[101.270026,21.201652],[101.80312,21.174367],[101.652018,22.318199],[102.170436,22.464753],[102.754896,21.675137],[103.203861,20.766562],[104.435,20.758733],[104.822574,19.886642],[104.183388,19.624668],[103.896532,19.265181],[105.094598,18.666975],[105.925762,17.485315],[106.556008,16.604284],[107.312706,15.908538],[107.564525,15.202173],[107.382727,14.202441],[106.496373,14.570584],[106.043946,13.881091],[105.218777,14.273212]]]}}, {"type":"Feature","id":"LBN","properties":{"name":"Lebanon"},"geometry":{"type":"Polygon","coordinates":[[[35.821101,33.277426],[35.552797,33.264275],[35.460709,33.08904],[35.126053,33.0909],[35.482207,33.90545],[35.979592,34.610058],[35.998403,34.644914],[36.448194,34.593935],[36.61175,34.201789],[36.06646,33.824912],[35.821101,33.277426]]]}}, {"type":"Feature","id":"LBR","properties":{"name":"Liberia"},"geometry":{"type":"Polygon","coordinates":[[[-7.712159,4.364566],[-7.974107,4.355755],[-9.004794,4.832419],[-9.91342,5.593561],[-10.765384,6.140711],[-11.438779,6.785917],[-11.199802,7.105846],[-11.146704,7.396706],[-10.695595,7.939464],[-10.230094,8.406206],[-10.016567,8.428504],[-9.755342,8.541055],[-9.33728,7.928534],[-9.403348,7.526905],[-9.208786,7.313921],[-8.926065,7.309037],[-8.722124,7.711674],[-8.439298,7.686043],[-8.485446,7.395208],[-8.385452,6.911801],[-8.60288,6.467564],[-8.311348,6.193033],[-7.993693,6.12619],[-7.570153,5.707352],[-7.539715,5.313345],[-7.635368,5.188159],[-7.712159,4.364566]]]}}, {"type":"Feature","id":"LBY","properties":{"name":"Libya"},"geometry":{"type":"Polygon","coordinates":[[[14.8513,22.86295],[14.143871,22.491289],[13.581425,23.040506],[11.999506,23.471668],[11.560669,24.097909],[10.771364,24.562532],[10.303847,24.379313],[9.948261,24.936954],[9.910693,25.365455],[9.319411,26.094325],[9.716286,26.512206],[9.629056,27.140953],[9.756128,27.688259],[9.683885,28.144174],[9.859998,28.95999],[9.805634,29.424638],[9.48214,30.307556],[9.970017,30.539325],[10.056575,30.961831],[9.950225,31.37607],[10.636901,31.761421],[10.94479,32.081815],[11.432253,32.368903],[11.488787,33.136996],[12.66331,32.79278],[13.08326,32.87882],[13.91868,32.71196],[15.24563,32.26508],[15.71394,31.37626],[16.61162,31.18218],[18.02109,30.76357],[19.08641,30.26639],[19.57404,30.52582],[20.05335,30.98576],[19.82033,31.75179],[20.13397,32.2382],[20.85452,32.7068],[21.54298,32.8432],[22.89576,32.63858],[23.2368,32.19149],[23.60913,32.18726],[23.9275,32.01667],[24.92114,31.89936],[25.16482,31.56915],[24.80287,31.08929],[24.95762,30.6616],[24.70007,30.04419],[25,29.238655],[25,25.6825],[25,22],[25,20.00304],[23.85,20],[23.83766,19.58047],[19.84926,21.49509],[15.86085,23.40972],[14.8513,22.86295]]]}}, {"type":"Feature","id":"LKA","properties":{"name":"Sri Lanka"},"geometry":{"type":"Polygon","coordinates":[[[81.787959,7.523055],[81.637322,6.481775],[81.21802,6.197141],[80.348357,5.96837],[79.872469,6.763463],[79.695167,8.200843],[80.147801,9.824078],[80.838818,9.268427],[81.304319,8.564206],[81.787959,7.523055]]]}}, {"type":"Feature","id":"LSO","properties":{"name":"Lesotho"},"geometry":{"type":"Polygon","coordinates":[[[28.978263,-28.955597],[29.325166,-29.257387],[29.018415,-29.743766],[28.8484,-30.070051],[28.291069,-30.226217],[28.107205,-30.545732],[27.749397,-30.645106],[26.999262,-29.875954],[27.532511,-29.242711],[28.074338,-28.851469],[28.5417,-28.647502],[28.978263,-28.955597]]]}}, {"type":"Feature","id":"LTU","properties":{"name":"Lithuania"},"geometry":{"type":"Polygon","coordinates":[[[22.731099,54.327537],[22.651052,54.582741],[22.757764,54.856574],[22.315724,55.015299],[21.268449,55.190482],[21.0558,56.031076],[22.201157,56.337802],[23.878264,56.273671],[24.860684,56.372528],[25.000934,56.164531],[25.533047,56.100297],[26.494331,55.615107],[26.588279,55.167176],[25.768433,54.846963],[25.536354,54.282423],[24.450684,53.905702],[23.484128,53.912498],[23.243987,54.220567],[22.731099,54.327537]]]}}, {"type":"Feature","id":"LUX","properties":{"name":"Luxembourg"},"geometry":{"type":"Polygon","coordinates":[[[6.043073,50.128052],[6.242751,49.902226],[6.18632,49.463803],[5.897759,49.442667],[5.674052,49.529484],[5.782417,50.090328],[6.043073,50.128052]]]}}, {"type":"Feature","id":"LVA","properties":{"name":"Latvia"},"geometry":{"type":"Polygon","coordinates":[[[21.0558,56.031076],[21.090424,56.783873],[21.581866,57.411871],[22.524341,57.753374],[23.318453,57.006236],[24.12073,57.025693],[24.312863,57.793424],[25.164594,57.970157],[25.60281,57.847529],[26.463532,57.476389],[27.288185,57.474528],[27.770016,57.244258],[27.855282,56.759326],[28.176709,56.16913],[27.10246,55.783314],[26.494331,55.615107],[25.533047,56.100297],[25.000934,56.164531],[24.860684,56.372528],[23.878264,56.273671],[22.201157,56.337802],[21.0558,56.031076]]]}}, {"type":"Feature","id":"MAR","properties":{"name":"Morocco"},"geometry":{"type":"Polygon","coordinates":[[[-5.193863,35.755182],[-4.591006,35.330712],[-3.640057,35.399855],[-2.604306,35.179093],[-2.169914,35.168396],[-1.792986,34.527919],[-1.733455,33.919713],[-1.388049,32.864015],[-1.124551,32.651522],[-1.307899,32.262889],[-2.616605,32.094346],[-3.06898,31.724498],[-3.647498,31.637294],[-3.690441,30.896952],[-4.859646,30.501188],[-5.242129,30.000443],[-6.060632,29.7317],[-7.059228,29.579228],[-8.674116,28.841289],[-8.66559,27.656426],[-8.817809,27.656426],[-8.817828,27.656426],[-8.794884,27.120696],[-9.413037,27.088476],[-9.735343,26.860945],[-10.189424,26.860945],[-10.551263,26.990808],[-11.392555,26.883424],[-11.71822,26.104092],[-12.030759,26.030866],[-12.500963,24.770116],[-13.89111,23.691009],[-14.221168,22.310163],[-14.630833,21.86094],[-14.750955,21.5006],[-17.002962,21.420734],[-17.020428,21.42231],[-16.973248,21.885745],[-16.589137,22.158234],[-16.261922,22.67934],[-16.326414,23.017768],[-15.982611,23.723358],[-15.426004,24.359134],[-15.089332,24.520261],[-14.824645,25.103533],[-14.800926,25.636265],[-14.43994,26.254418],[-13.773805,26.618892],[-13.139942,27.640148],[-13.121613,27.654148],[-12.618837,28.038186],[-11.688919,28.148644],[-10.900957,28.832142],[-10.399592,29.098586],[-9.564811,29.933574],[-9.814718,31.177736],[-9.434793,32.038096],[-9.300693,32.564679],[-8.657476,33.240245],[-7.654178,33.697065],[-6.912544,34.110476],[-6.244342,35.145865],[-5.929994,35.759988],[-5.193863,35.755182]]]}}, {"type":"Feature","id":"MDA","properties":{"name":"Moldova"},"geometry":{"type":"Polygon","coordinates":[[[26.619337,48.220726],[26.857824,48.368211],[27.522537,48.467119],[28.259547,48.155562],[28.670891,48.118149],[29.122698,47.849095],[29.050868,47.510227],[29.415135,47.346645],[29.559674,46.928583],[29.908852,46.674361],[29.83821,46.525326],[30.024659,46.423937],[29.759972,46.349988],[29.170654,46.379262],[29.072107,46.517678],[28.862972,46.437889],[28.933717,46.25883],[28.659987,45.939987],[28.485269,45.596907],[28.233554,45.488283],[28.054443,45.944586],[28.160018,46.371563],[28.12803,46.810476],[27.551166,47.405117],[27.233873,47.826771],[26.924176,48.123264],[26.619337,48.220726]]]}}, {"type":"Feature","id":"MDG","properties":{"name":"Madagascar"},"geometry":{"type":"Polygon","coordinates":[[[49.543519,-12.469833],[49.808981,-12.895285],[50.056511,-13.555761],[50.217431,-14.758789],[50.476537,-15.226512],[50.377111,-15.706069],[50.200275,-16.000263],[49.860606,-15.414253],[49.672607,-15.710204],[49.863344,-16.451037],[49.774564,-16.875042],[49.498612,-17.106036],[49.435619,-17.953064],[49.041792,-19.118781],[48.548541,-20.496888],[47.930749,-22.391501],[47.547723,-23.781959],[47.095761,-24.94163],[46.282478,-25.178463],[45.409508,-25.601434],[44.833574,-25.346101],[44.03972,-24.988345],[43.763768,-24.460677],[43.697778,-23.574116],[43.345654,-22.776904],[43.254187,-22.057413],[43.433298,-21.336475],[43.893683,-21.163307],[43.89637,-20.830459],[44.374325,-20.072366],[44.464397,-19.435454],[44.232422,-18.961995],[44.042976,-18.331387],[43.963084,-17.409945],[44.312469,-16.850496],[44.446517,-16.216219],[44.944937,-16.179374],[45.502732,-15.974373],[45.872994,-15.793454],[46.312243,-15.780018],[46.882183,-15.210182],[47.70513,-14.594303],[48.005215,-14.091233],[47.869047,-13.663869],[48.293828,-13.784068],[48.84506,-13.089175],[48.863509,-12.487868],[49.194651,-12.040557],[49.543519,-12.469833]]]}}, {"type":"Feature","id":"MEX","properties":{"name":"Mexico"},"geometry":{"type":"Polygon","coordinates":[[[-97.140008,25.869997],[-97.528072,24.992144],[-97.702946,24.272343],[-97.776042,22.93258],[-97.872367,22.444212],[-97.699044,21.898689],[-97.38896,21.411019],[-97.189333,20.635433],[-96.525576,19.890931],[-96.292127,19.320371],[-95.900885,18.828024],[-94.839063,18.562717],[-94.42573,18.144371],[-93.548651,18.423837],[-92.786114,18.524839],[-92.037348,18.704569],[-91.407903,18.876083],[-90.77187,19.28412],[-90.53359,19.867418],[-90.451476,20.707522],[-90.278618,20.999855],[-89.601321,21.261726],[-88.543866,21.493675],[-87.658417,21.458846],[-87.05189,21.543543],[-86.811982,21.331515],[-86.845908,20.849865],[-87.383291,20.255405],[-87.621054,19.646553],[-87.43675,19.472403],[-87.58656,19.04013],[-87.837191,18.259816],[-88.090664,18.516648],[-88.300031,18.499982],[-88.490123,18.486831],[-88.848344,17.883198],[-89.029857,18.001511],[-89.150909,17.955468],[-89.14308,17.808319],[-90.067934,17.819326],[-91.00152,17.817595],[-91.002269,17.254658],[-91.453921,17.252177],[-91.08167,16.918477],[-90.711822,16.687483],[-90.600847,16.470778],[-90.438867,16.41011],[-90.464473,16.069562],[-91.74796,16.066565],[-92.229249,15.251447],[-92.087216,15.064585],[-92.20323,14.830103],[-92.22775,14.538829],[-93.359464,15.61543],[-93.875169,15.940164],[-94.691656,16.200975],[-95.250227,16.128318],[-96.053382,15.752088],[-96.557434,15.653515],[-97.263592,15.917065],[-98.01303,16.107312],[-98.947676,16.566043],[-99.697397,16.706164],[-100.829499,17.171071],[-101.666089,17.649026],[-101.918528,17.91609],[-102.478132,17.975751],[-103.50099,18.292295],[-103.917527,18.748572],[-104.99201,19.316134],[-105.493038,19.946767],[-105.731396,20.434102],[-105.397773,20.531719],[-105.500661,20.816895],[-105.270752,21.076285],[-105.265817,21.422104],[-105.603161,21.871146],[-105.693414,22.26908],[-106.028716,22.773752],[-106.90998,23.767774],[-107.915449,24.548915],[-108.401905,25.172314],[-109.260199,25.580609],[-109.444089,25.824884],[-109.291644,26.442934],[-109.801458,26.676176],[-110.391732,27.162115],[-110.641019,27.859876],[-111.178919,27.941241],[-111.759607,28.467953],[-112.228235,28.954409],[-112.271824,29.266844],[-112.809594,30.021114],[-113.163811,30.786881],[-113.148669,31.170966],[-113.871881,31.567608],[-114.205737,31.524045],[-114.776451,31.799532],[-114.9367,31.393485],[-114.771232,30.913617],[-114.673899,30.162681],[-114.330974,29.750432],[-113.588875,29.061611],[-113.424053,28.826174],[-113.271969,28.754783],[-113.140039,28.411289],[-112.962298,28.42519],[-112.761587,27.780217],[-112.457911,27.525814],[-112.244952,27.171727],[-111.616489,26.662817],[-111.284675,25.73259],[-110.987819,25.294606],[-110.710007,24.826004],[-110.655049,24.298595],[-110.172856,24.265548],[-109.771847,23.811183],[-109.409104,23.364672],[-109.433392,23.185588],[-109.854219,22.818272],[-110.031392,22.823078],[-110.295071,23.430973],[-110.949501,24.000964],[-111.670568,24.484423],[-112.182036,24.738413],[-112.148989,25.470125],[-112.300711,26.012004],[-112.777297,26.32196],[-113.464671,26.768186],[-113.59673,26.63946],[-113.848937,26.900064],[-114.465747,27.14209],[-115.055142,27.722727],[-114.982253,27.7982],[-114.570366,27.741485],[-114.199329,28.115003],[-114.162018,28.566112],[-114.931842,29.279479],[-115.518654,29.556362],[-115.887365,30.180794],[-116.25835,30.836464],[-116.721526,31.635744],[-117.12776,32.53534],[-115.99135,32.61239],[-114.72139,32.72083],[-114.815,32.52528],[-113.30498,32.03914],[-111.02361,31.33472],[-109.035,31.34194],[-108.24194,31.34222],[-108.24,31.754854],[-106.50759,31.75452],[-106.1429,31.39995],[-105.63159,31.08383],[-105.03737,30.64402],[-104.70575,30.12173],[-104.45697,29.57196],[-103.94,29.27],[-103.11,28.97],[-102.48,29.76],[-101.6624,29.7793],[-100.9576,29.38071],[-100.45584,28.69612],[-100.11,28.11],[-99.52,27.54],[-99.3,26.84],[-99.02,26.37],[-98.24,26.06],[-97.53,25.84],[-97.140008,25.869997]]]}}, {"type":"Feature","id":"MKD","properties":{"name":"Macedonia"},"geometry":{"type":"Polygon","coordinates":[[[20.59023,41.85541],[20.71731,41.84711],[20.76216,42.05186],[21.3527,42.2068],[21.576636,42.245224],[21.91708,42.30364],[22.380526,42.32026],[22.881374,41.999297],[22.952377,41.337994],[22.76177,41.3048],[22.597308,41.130487],[22.055378,41.149866],[21.674161,40.931275],[21.02004,40.842727],[20.60518,41.08622],[20.46315,41.51509],[20.59023,41.85541]]]}}, {"type":"Feature","id":"MLI","properties":{"name":"Mali"},"geometry":{"type":"Polygon","coordinates":[[[-12.17075,14.616834],[-11.834208,14.799097],[-11.666078,15.388208],[-11.349095,15.411256],[-10.650791,15.132746],[-10.086846,15.330486],[-9.700255,15.264107],[-9.550238,15.486497],[-5.537744,15.50169],[-5.315277,16.201854],[-5.488523,16.325102],[-5.971129,20.640833],[-6.453787,24.956591],[-4.923337,24.974574],[-1.550055,22.792666],[1.823228,20.610809],[2.060991,20.142233],[2.683588,19.85623],[3.146661,19.693579],[3.158133,19.057364],[4.267419,19.155265],[4.27021,16.852227],[3.723422,16.184284],[3.638259,15.56812],[2.749993,15.409525],[1.385528,15.323561],[1.015783,14.968182],[0.374892,14.928908],[-0.266257,14.924309],[-0.515854,15.116158],[-1.066363,14.973815],[-2.001035,14.559008],[-2.191825,14.246418],[-2.967694,13.79815],[-3.103707,13.541267],[-3.522803,13.337662],[-4.006391,13.472485],[-4.280405,13.228444],[-4.427166,12.542646],[-5.220942,11.713859],[-5.197843,11.375146],[-5.470565,10.95127],[-5.404342,10.370737],[-5.816926,10.222555],[-6.050452,10.096361],[-6.205223,10.524061],[-6.493965,10.411303],[-6.666461,10.430811],[-6.850507,10.138994],[-7.622759,10.147236],[-7.89959,10.297382],[-8.029944,10.206535],[-8.335377,10.494812],[-8.282357,10.792597],[-8.407311,10.909257],[-8.620321,10.810891],[-8.581305,11.136246],[-8.376305,11.393646],[-8.786099,11.812561],[-8.905265,12.088358],[-9.127474,12.30806],[-9.327616,12.334286],[-9.567912,12.194243],[-9.890993,12.060479],[-10.165214,11.844084],[-10.593224,11.923975],[-10.87083,12.177887],[-11.036556,12.211245],[-11.297574,12.077971],[-11.456169,12.076834],[-11.513943,12.442988],[-11.467899,12.754519],[-11.553398,13.141214],[-11.927716,13.422075],[-12.124887,13.994727],[-12.17075,14.616834]]]}}, {"type":"Feature","id":"MLT","properties":{"name":"Malta"},"geometry":{"type":"MultiPolygon","coordinates":[[[[14.566171,35.852721],[14.532684,35.820191],[14.436463,35.821664],[14.352334,35.872281],[14.3513,35.978399],[14.448348,35.957444],[14.537025,35.886285],[14.566171,35.852721]]],[[[14.313473,36.027569],[14.253632,36.012143],[14.194204,36.042245],[14.180354,36.060383],[14.263243,36.075809],[14.303758,36.062295],[14.320914,36.03625],[14.313473,36.027569]]]]}}, {"type":"Feature","id":"MMR","properties":{"name":"Myanmar"},"geometry":{"type":"Polygon","coordinates":[[[99.543309,20.186598],[98.959676,19.752981],[98.253724,19.708203],[97.797783,18.62708],[97.375896,18.445438],[97.859123,17.567946],[98.493761,16.837836],[98.903348,16.177824],[98.537376,15.308497],[98.192074,15.123703],[98.430819,14.622028],[99.097755,13.827503],[99.212012,13.269294],[99.196354,12.804748],[99.587286,11.892763],[99.038121,10.960546],[98.553551,9.93296],[98.457174,10.675266],[98.764546,11.441292],[98.428339,12.032987],[98.509574,13.122378],[98.103604,13.64046],[97.777732,14.837286],[97.597072,16.100568],[97.16454,16.928734],[96.505769,16.427241],[95.369352,15.71439],[94.808405,15.803454],[94.188804,16.037936],[94.533486,17.27724],[94.324817,18.213514],[93.540988,19.366493],[93.663255,19.726962],[93.078278,19.855145],[92.368554,20.670883],[92.303234,21.475485],[92.652257,21.324048],[92.672721,22.041239],[93.166128,22.27846],[93.060294,22.703111],[93.286327,23.043658],[93.325188,24.078556],[94.106742,23.850741],[94.552658,24.675238],[94.603249,25.162495],[95.155153,26.001307],[95.124768,26.573572],[96.419366,27.264589],[97.133999,27.083774],[97.051989,27.699059],[97.402561,27.882536],[97.327114,28.261583],[97.911988,28.335945],[98.246231,27.747221],[98.68269,27.508812],[98.712094,26.743536],[98.671838,25.918703],[97.724609,25.083637],[97.60472,23.897405],[98.660262,24.063286],[98.898749,23.142722],[99.531992,22.949039],[99.240899,22.118314],[99.983489,21.742937],[100.416538,21.558839],[101.150033,21.849984],[101.180005,21.436573],[100.329101,20.786122],[100.115988,20.41785],[99.543309,20.186598]]]}}, {"type":"Feature","id":"MNE","properties":{"name":"Montenegro"},"geometry":{"type":"Polygon","coordinates":[[[19.801613,42.500093],[19.738051,42.688247],[19.30449,42.19574],[19.37177,41.87755],[19.16246,41.95502],[18.88214,42.28151],[18.45,42.48],[18.56,42.65],[18.70648,43.20011],[19.03165,43.43253],[19.21852,43.52384],[19.48389,43.35229],[19.63,43.21378],[19.95857,43.10604],[20.3398,42.89852],[20.25758,42.81275],[20.0707,42.58863],[19.801613,42.500093]]]}}, {"type":"Feature","id":"MNG","properties":{"name":"Mongolia"},"geometry":{"type":"Polygon","coordinates":[[[87.751264,49.297198],[88.805567,49.470521],[90.713667,50.331812],[92.234712,50.802171],[93.104219,50.49529],[94.147566,50.480537],[94.815949,50.013433],[95.814028,49.977467],[97.259728,49.726061],[98.231762,50.422401],[97.82574,51.010995],[98.861491,52.047366],[99.981732,51.634006],[100.88948,51.516856],[102.065223,51.259921],[102.255909,50.510561],[103.676545,50.089966],[104.621552,50.275329],[105.886591,50.406019],[106.888804,50.274296],[107.868176,49.793705],[108.475167,49.282548],[109.402449,49.292961],[110.662011,49.130128],[111.581231,49.377968],[112.89774,49.543565],[114.362456,50.248303],[114.96211,50.140247],[115.485695,49.805177],[116.678801,49.888531],[116.191802,49.134598],[115.485282,48.135383],[115.742837,47.726545],[116.308953,47.85341],[117.295507,47.697709],[118.064143,48.06673],[118.866574,47.74706],[119.772824,47.048059],[119.66327,46.69268],[118.874326,46.805412],[117.421701,46.672733],[116.717868,46.388202],[115.985096,45.727235],[114.460332,45.339817],[113.463907,44.808893],[112.436062,45.011646],[111.873306,45.102079],[111.348377,44.457442],[111.667737,44.073176],[111.829588,43.743118],[111.129682,43.406834],[110.412103,42.871234],[109.243596,42.519446],[107.744773,42.481516],[106.129316,42.134328],[104.964994,41.59741],[104.522282,41.908347],[103.312278,41.907468],[101.83304,42.514873],[100.845866,42.663804],[99.515817,42.524691],[97.451757,42.74889],[96.349396,42.725635],[95.762455,43.319449],[95.306875,44.241331],[94.688929,44.352332],[93.480734,44.975472],[92.133891,45.115076],[90.94554,45.286073],[90.585768,45.719716],[90.970809,46.888146],[90.280826,47.693549],[88.854298,48.069082],[88.013832,48.599463],[87.751264,49.297198]]]}}, {"type":"Feature","id":"MOZ","properties":{"name":"Mozambique"},"geometry":{"type":"Polygon","coordinates":[[[34.559989,-11.52002],[35.312398,-11.439146],[36.514082,-11.720938],[36.775151,-11.594537],[37.471284,-11.568751],[37.827645,-11.268769],[38.427557,-11.285202],[39.52103,-10.896854],[40.316589,-10.317096],[40.478387,-10.765441],[40.437253,-11.761711],[40.560811,-12.639177],[40.59962,-14.201975],[40.775475,-14.691764],[40.477251,-15.406294],[40.089264,-16.100774],[39.452559,-16.720891],[38.538351,-17.101023],[37.411133,-17.586368],[36.281279,-18.659688],[35.896497,-18.84226],[35.1984,-19.552811],[34.786383,-19.784012],[34.701893,-20.497043],[35.176127,-21.254361],[35.373428,-21.840837],[35.385848,-22.14],[35.562546,-22.09],[35.533935,-23.070788],[35.371774,-23.535359],[35.60747,-23.706563],[35.458746,-24.12261],[35.040735,-24.478351],[34.215824,-24.816314],[33.01321,-25.357573],[32.574632,-25.727318],[32.660363,-26.148584],[32.915955,-26.215867],[32.83012,-26.742192],[32.071665,-26.73382],[31.985779,-26.29178],[31.837778,-25.843332],[31.752408,-25.484284],[31.930589,-24.369417],[31.670398,-23.658969],[31.191409,-22.25151],[32.244988,-21.116489],[32.508693,-20.395292],[32.659743,-20.30429],[32.772708,-19.715592],[32.611994,-19.419383],[32.654886,-18.67209],[32.849861,-17.979057],[32.847639,-16.713398],[32.328239,-16.392074],[31.852041,-16.319417],[31.636498,-16.07199],[31.173064,-15.860944],[30.338955,-15.880839],[30.274256,-15.507787],[30.179481,-14.796099],[33.214025,-13.97186],[33.7897,-14.451831],[34.064825,-14.35995],[34.459633,-14.61301],[34.517666,-15.013709],[34.307291,-15.478641],[34.381292,-16.18356],[35.03381,-16.8013],[35.339063,-16.10744],[35.771905,-15.896859],[35.686845,-14.611046],[35.267956,-13.887834],[34.907151,-13.565425],[34.559989,-13.579998],[34.280006,-12.280025],[34.559989,-11.52002]]]}}, {"type":"Feature","id":"MRT","properties":{"name":"Mauritania"},"geometry":{"type":"Polygon","coordinates":[[[-12.17075,14.616834],[-12.830658,15.303692],[-13.435738,16.039383],[-14.099521,16.304302],[-14.577348,16.598264],[-15.135737,16.587282],[-15.623666,16.369337],[-16.12069,16.455663],[-16.463098,16.135036],[-16.549708,16.673892],[-16.270552,17.166963],[-16.146347,18.108482],[-16.256883,19.096716],[-16.377651,19.593817],[-16.277838,20.092521],[-16.536324,20.567866],[-17.063423,20.999752],[-16.845194,21.333323],[-12.929102,21.327071],[-13.118754,22.77122],[-12.874222,23.284832],[-11.937224,23.374594],[-11.969419,25.933353],[-8.687294,25.881056],[-8.6844,27.395744],[-4.923337,24.974574],[-6.453787,24.956591],[-5.971129,20.640833],[-5.488523,16.325102],[-5.315277,16.201854],[-5.537744,15.50169],[-9.550238,15.486497],[-9.700255,15.264107],[-10.086846,15.330486],[-10.650791,15.132746],[-11.349095,15.411256],[-11.666078,15.388208],[-11.834208,14.799097],[-12.17075,14.616834]]]}}, {"type":"Feature","id":"MWI","properties":{"name":"Malawi"},"geometry":{"type":"Polygon","coordinates":[[[34.559989,-11.52002],[34.280006,-12.280025],[34.559989,-13.579998],[34.907151,-13.565425],[35.267956,-13.887834],[35.686845,-14.611046],[35.771905,-15.896859],[35.339063,-16.10744],[35.03381,-16.8013],[34.381292,-16.18356],[34.307291,-15.478641],[34.517666,-15.013709],[34.459633,-14.61301],[34.064825,-14.35995],[33.7897,-14.451831],[33.214025,-13.97186],[32.688165,-13.712858],[32.991764,-12.783871],[33.306422,-12.435778],[33.114289,-11.607198],[33.31531,-10.79655],[33.485688,-10.525559],[33.231388,-9.676722],[32.759375,-9.230599],[33.739729,-9.417151],[33.940838,-9.693674],[34.280006,-10.16],[34.559989,-11.52002]]]}}, {"type":"Feature","id":"MYS","properties":{"name":"Malaysia"},"geometry":{"type":"MultiPolygon","coordinates":[[[[101.075516,6.204867],[101.154219,5.691384],[101.814282,5.810808],[102.141187,6.221636],[102.371147,6.128205],[102.961705,5.524495],[103.381215,4.855001],[103.438575,4.181606],[103.332122,3.726698],[103.429429,3.382869],[103.502448,2.791019],[103.854674,2.515454],[104.247932,1.631141],[104.228811,1.293048],[103.519707,1.226334],[102.573615,1.967115],[101.390638,2.760814],[101.27354,3.270292],[100.695435,3.93914],[100.557408,4.76728],[100.196706,5.312493],[100.30626,6.040562],[100.085757,6.464489],[100.259596,6.642825],[101.075516,6.204867]]],[[[118.618321,4.478202],[117.882035,4.137551],[117.015214,4.306094],[115.865517,4.306559],[115.519078,3.169238],[115.134037,2.821482],[114.621355,1.430688],[113.80585,1.217549],[112.859809,1.49779],[112.380252,1.410121],[111.797548,0.904441],[111.159138,0.976478],[110.514061,0.773131],[109.830227,1.338136],[109.66326,2.006467],[110.396135,1.663775],[111.168853,1.850637],[111.370081,2.697303],[111.796928,2.885897],[112.995615,3.102395],[113.712935,3.893509],[114.204017,4.525874],[114.659596,4.007637],[114.869557,4.348314],[115.347461,4.316636],[115.4057,4.955228],[115.45071,5.44773],[116.220741,6.143191],[116.725103,6.924771],[117.129626,6.928053],[117.643393,6.422166],[117.689075,5.98749],[118.347691,5.708696],[119.181904,5.407836],[119.110694,5.016128],[118.439727,4.966519],[118.618321,4.478202]]]]}}, {"type":"Feature","id":"NAM","properties":{"name":"Namibia"},"geometry":{"type":"Polygon","coordinates":[[[16.344977,-28.576705],[15.601818,-27.821247],[15.210472,-27.090956],[14.989711,-26.117372],[14.743214,-25.39292],[14.408144,-23.853014],[14.385717,-22.656653],[14.257714,-22.111208],[13.868642,-21.699037],[13.352498,-20.872834],[12.826845,-19.673166],[12.608564,-19.045349],[11.794919,-18.069129],[11.734199,-17.301889],[12.215461,-17.111668],[12.814081,-16.941343],[13.462362,-16.971212],[14.058501,-17.423381],[14.209707,-17.353101],[18.263309,-17.309951],[18.956187,-17.789095],[21.377176,-17.930636],[23.215048,-17.523116],[24.033862,-17.295843],[24.682349,-17.353411],[25.07695,-17.578823],[25.084443,-17.661816],[24.520705,-17.887125],[24.217365,-17.889347],[23.579006,-18.281261],[23.196858,-17.869038],[21.65504,-18.219146],[20.910641,-18.252219],[20.881134,-21.814327],[19.895458,-21.849157],[19.895768,-24.76779],[19.894734,-28.461105],[19.002127,-28.972443],[18.464899,-29.045462],[17.836152,-28.856378],[17.387497,-28.783514],[17.218929,-28.355943],[16.824017,-28.082162],[16.344977,-28.576705]]]}}, {"type":"Feature","id":"NCL","properties":{"name":"New Caledonia"},"geometry":{"type":"Polygon","coordinates":[[[165.77999,-21.080005],[166.599991,-21.700019],[167.120011,-22.159991],[166.740035,-22.399976],[166.189732,-22.129708],[165.474375,-21.679607],[164.829815,-21.14982],[164.167995,-20.444747],[164.029606,-20.105646],[164.459967,-20.120012],[165.020036,-20.459991],[165.460009,-20.800022],[165.77999,-21.080005]]]}}, {"type":"Feature","id":"NER","properties":{"name":"Niger"},"geometry":{"type":"Polygon","coordinates":[[[2.154474,11.94015],[2.177108,12.625018],[1.024103,12.851826],[0.993046,13.33575],[0.429928,13.988733],[0.295646,14.444235],[0.374892,14.928908],[1.015783,14.968182],[1.385528,15.323561],[2.749993,15.409525],[3.638259,15.56812],[3.723422,16.184284],[4.27021,16.852227],[4.267419,19.155265],[5.677566,19.601207],[8.572893,21.565661],[11.999506,23.471668],[13.581425,23.040506],[14.143871,22.491289],[14.8513,22.86295],[15.096888,21.308519],[15.471077,21.048457],[15.487148,20.730415],[15.903247,20.387619],[15.685741,19.95718],[15.300441,17.92795],[15.247731,16.627306],[13.972202,15.684366],[13.540394,14.367134],[13.956699,13.996691],[13.954477,13.353449],[14.595781,13.330427],[14.495787,12.859396],[14.213531,12.802035],[14.181336,12.483657],[13.995353,12.461565],[13.318702,13.556356],[13.083987,13.596147],[12.302071,13.037189],[11.527803,13.32898],[10.989593,13.387323],[10.701032,13.246918],[10.114814,13.277252],[9.524928,12.851102],[9.014933,12.826659],[7.804671,13.343527],[7.330747,13.098038],[6.820442,13.115091],[6.445426,13.492768],[5.443058,13.865924],[4.368344,13.747482],[4.107946,13.531216],[3.967283,12.956109],[3.680634,12.552903],[3.61118,11.660167],[2.848643,12.235636],[2.490164,12.233052],[2.154474,11.94015]]]}}, {"type":"Feature","id":"NGA","properties":{"name":"Nigeria"},"geometry":{"type":"Polygon","coordinates":[[[8.500288,4.771983],[7.462108,4.412108],[7.082596,4.464689],[6.698072,4.240594],[5.898173,4.262453],[5.362805,4.887971],[5.033574,5.611802],[4.325607,6.270651],[3.57418,6.2583],[2.691702,6.258817],[2.749063,7.870734],[2.723793,8.506845],[2.912308,9.137608],[3.220352,9.444153],[3.705438,10.06321],[3.60007,10.332186],[3.797112,10.734746],[3.572216,11.327939],[3.61118,11.660167],[3.680634,12.552903],[3.967283,12.956109],[4.107946,13.531216],[4.368344,13.747482],[5.443058,13.865924],[6.445426,13.492768],[6.820442,13.115091],[7.330747,13.098038],[7.804671,13.343527],[9.014933,12.826659],[9.524928,12.851102],[10.114814,13.277252],[10.701032,13.246918],[10.989593,13.387323],[11.527803,13.32898],[12.302071,13.037189],[13.083987,13.596147],[13.318702,13.556356],[13.995353,12.461565],[14.181336,12.483657],[14.577178,12.085361],[14.468192,11.904752],[14.415379,11.572369],[13.57295,10.798566],[13.308676,10.160362],[13.1676,9.640626],[12.955468,9.417772],[12.753672,8.717763],[12.218872,8.305824],[12.063946,7.799808],[11.839309,7.397042],[11.745774,6.981383],[11.058788,6.644427],[10.497375,7.055358],[10.118277,7.03877],[9.522706,6.453482],[9.233163,6.444491],[8.757533,5.479666],[8.500288,4.771983]]]}}, {"type":"Feature","id":"NIC","properties":{"name":"Nicaragua"},"geometry":{"type":"Polygon","coordinates":[[[-85.71254,11.088445],[-86.058488,11.403439],[-86.52585,11.806877],[-86.745992,12.143962],[-87.167516,12.458258],[-87.668493,12.90991],[-87.557467,13.064552],[-87.392386,12.914018],[-87.316654,12.984686],[-87.005769,13.025794],[-86.880557,13.254204],[-86.733822,13.263093],[-86.755087,13.754845],[-86.520708,13.778487],[-86.312142,13.771356],[-86.096264,14.038187],[-85.801295,13.836055],[-85.698665,13.960078],[-85.514413,14.079012],[-85.165365,14.35437],[-85.148751,14.560197],[-85.052787,14.551541],[-84.924501,14.790493],[-84.820037,14.819587],[-84.649582,14.666805],[-84.449336,14.621614],[-84.228342,14.748764],[-83.975721,14.749436],[-83.628585,14.880074],[-83.489989,15.016267],[-83.147219,14.995829],[-83.233234,14.899866],[-83.284162,14.676624],[-83.182126,14.310703],[-83.4125,13.970078],[-83.519832,13.567699],[-83.552207,13.127054],[-83.498515,12.869292],[-83.473323,12.419087],[-83.626104,12.32085],[-83.719613,11.893124],[-83.650858,11.629032],[-83.85547,11.373311],[-83.808936,11.103044],[-83.655612,10.938764],[-83.895054,10.726839],[-84.190179,10.79345],[-84.355931,10.999226],[-84.673069,11.082657],[-84.903003,10.952303],[-85.561852,11.217119],[-85.71254,11.088445]]]}}, {"type":"Feature","id":"NLD","properties":{"name":"Netherlands"},"geometry":{"type":"Polygon","coordinates":[[[6.074183,53.510403],[6.90514,53.482162],[7.092053,53.144043],[6.84287,52.22844],[6.589397,51.852029],[5.988658,51.851616],[6.156658,50.803721],[5.606976,51.037298],[4.973991,51.475024],[4.047071,51.267259],[3.314971,51.345755],[3.830289,51.620545],[4.705997,53.091798],[6.074183,53.510403]]]}}, {"type":"Feature","id":"NOR","properties":{"name":"Norway"},"geometry":{"type":"MultiPolygon","coordinates":[[[[28.165547,71.185474],[31.293418,70.453788],[30.005435,70.186259],[31.101079,69.55808],[29.399581,69.156916],[28.59193,69.064777],[29.015573,69.766491],[27.732292,70.164193],[26.179622,69.825299],[25.689213,69.092114],[24.735679,68.649557],[23.66205,68.891247],[22.356238,68.841741],[21.244936,69.370443],[20.645593,69.106247],[20.025269,69.065139],[19.87856,68.407194],[17.993868,68.567391],[17.729182,68.010552],[16.768879,68.013937],[16.108712,67.302456],[15.108411,66.193867],[13.55569,64.787028],[13.919905,64.445421],[13.571916,64.049114],[12.579935,64.066219],[11.930569,63.128318],[11.992064,61.800362],[12.631147,61.293572],[12.300366,60.117933],[11.468272,59.432393],[11.027369,58.856149],[10.356557,59.469807],[8.382,58.313288],[7.048748,58.078884],[5.665835,58.588155],[5.308234,59.663232],[4.992078,61.970998],[5.9129,62.614473],[8.553411,63.454008],[10.527709,64.486038],[12.358347,65.879726],[14.761146,67.810642],[16.435927,68.563205],[19.184028,69.817444],[21.378416,70.255169],[23.023742,70.202072],[24.546543,71.030497],[26.37005,70.986262],[28.165547,71.185474]]],[[[24.72412,77.85385],[22.49032,77.44493],[20.72601,77.67704],[21.41611,77.93504],[20.8119,78.25463],[22.88426,78.45494],[23.28134,78.07954],[24.72412,77.85385]]],[[[18.25183,79.70175],[21.54383,78.95611],[19.02737,78.5626],[18.47172,77.82669],[17.59441,77.63796],[17.1182,76.80941],[15.91315,76.77045],[13.76259,77.38035],[14.66956,77.73565],[13.1706,78.02493],[11.22231,78.8693],[10.44453,79.65239],[13.17077,80.01046],[13.71852,79.66039],[15.14282,79.67431],[15.52255,80.01608],[16.99085,80.05086],[18.25183,79.70175]]],[[[25.447625,80.40734],[27.407506,80.056406],[25.924651,79.517834],[23.024466,79.400012],[20.075188,79.566823],[19.897266,79.842362],[18.462264,79.85988],[17.368015,80.318896],[20.455992,80.598156],[21.907945,80.357679],[22.919253,80.657144],[25.447625,80.40734]]]]}}, {"type":"Feature","id":"NPL","properties":{"name":"Nepal"},"geometry":{"type":"Polygon","coordinates":[[[88.120441,27.876542],[88.043133,27.445819],[88.174804,26.810405],[88.060238,26.414615],[87.227472,26.397898],[86.024393,26.630985],[85.251779,26.726198],[84.675018,27.234901],[83.304249,27.364506],[81.999987,27.925479],[81.057203,28.416095],[80.088425,28.79447],[80.476721,29.729865],[81.111256,30.183481],[81.525804,30.422717],[82.327513,30.115268],[83.337115,29.463732],[83.898993,29.320226],[84.23458,28.839894],[85.011638,28.642774],[85.82332,28.203576],[86.954517,27.974262],[88.120441,27.876542]]]}}, {"type":"Feature","id":"NZL","properties":{"name":"New Zealand"},"geometry":{"type":"MultiPolygon","coordinates":[[[[173.020375,-40.919052],[173.247234,-41.331999],[173.958405,-40.926701],[174.247587,-41.349155],[174.248517,-41.770008],[173.876447,-42.233184],[173.22274,-42.970038],[172.711246,-43.372288],[173.080113,-43.853344],[172.308584,-43.865694],[171.452925,-44.242519],[171.185138,-44.897104],[170.616697,-45.908929],[169.831422,-46.355775],[169.332331,-46.641235],[168.411354,-46.619945],[167.763745,-46.290197],[166.676886,-46.219917],[166.509144,-45.852705],[167.046424,-45.110941],[168.303763,-44.123973],[168.949409,-43.935819],[169.667815,-43.555326],[170.52492,-43.031688],[171.12509,-42.512754],[171.569714,-41.767424],[171.948709,-41.514417],[172.097227,-40.956104],[172.79858,-40.493962],[173.020375,-40.919052]]],[[[174.612009,-36.156397],[175.336616,-37.209098],[175.357596,-36.526194],[175.808887,-36.798942],[175.95849,-37.555382],[176.763195,-37.881253],[177.438813,-37.961248],[178.010354,-37.579825],[178.517094,-37.695373],[178.274731,-38.582813],[177.97046,-39.166343],[177.206993,-39.145776],[176.939981,-39.449736],[177.032946,-39.879943],[176.885824,-40.065978],[176.508017,-40.604808],[176.01244,-41.289624],[175.239567,-41.688308],[175.067898,-41.425895],[174.650973,-41.281821],[175.22763,-40.459236],[174.900157,-39.908933],[173.824047,-39.508854],[173.852262,-39.146602],[174.574802,-38.797683],[174.743474,-38.027808],[174.697017,-37.381129],[174.292028,-36.711092],[174.319004,-36.534824],[173.840997,-36.121981],[173.054171,-35.237125],[172.636005,-34.529107],[173.007042,-34.450662],[173.551298,-35.006183],[174.32939,-35.265496],[174.612009,-36.156397]]]]}}, {"type":"Feature","id":"OMN","properties":{"name":"Oman"},"geometry":{"type":"MultiPolygon","coordinates":[[[[58.861141,21.114035],[58.487986,20.428986],[58.034318,20.481437],[57.826373,20.243002],[57.665762,19.736005],[57.7887,19.06757],[57.694391,18.94471],[57.234264,18.947991],[56.609651,18.574267],[56.512189,18.087113],[56.283521,17.876067],[55.661492,17.884128],[55.269939,17.632309],[55.2749,17.228354],[54.791002,16.950697],[54.239253,17.044981],[53.570508,16.707663],[53.108573,16.651051],[52.782184,17.349742],[52.00001,19.000003],[54.999982,19.999994],[55.666659,22.000001],[55.208341,22.70833],[55.234489,23.110993],[55.525841,23.524869],[55.528632,23.933604],[55.981214,24.130543],[55.804119,24.269604],[55.886233,24.920831],[56.396847,24.924732],[56.84514,24.241673],[57.403453,23.878594],[58.136948,23.747931],[58.729211,23.565668],[59.180502,22.992395],[59.450098,22.660271],[59.80806,22.533612],[59.806148,22.310525],[59.442191,21.714541],[59.282408,21.433886],[58.861141,21.114035]]],[[[56.391421,25.895991],[56.261042,25.714606],[56.070821,26.055464],[56.362017,26.395934],[56.485679,26.309118],[56.391421,25.895991]]]]}}, {"type":"Feature","id":"PAK","properties":{"name":"Pakistan"},"geometry":{"type":"Polygon","coordinates":[[[75.158028,37.133031],[75.896897,36.666806],[76.192848,35.898403],[77.837451,35.49401],[76.871722,34.653544],[75.757061,34.504923],[74.240203,34.748887],[73.749948,34.317699],[74.104294,33.441473],[74.451559,32.7649],[75.258642,32.271105],[74.405929,31.692639],[74.42138,30.979815],[73.450638,29.976413],[72.823752,28.961592],[71.777666,27.91318],[70.616496,27.989196],[69.514393,26.940966],[70.168927,26.491872],[70.282873,25.722229],[70.844699,25.215102],[71.04324,24.356524],[68.842599,24.359134],[68.176645,23.691965],[67.443667,23.944844],[67.145442,24.663611],[66.372828,25.425141],[64.530408,25.237039],[62.905701,25.218409],[61.497363,25.078237],[61.874187,26.239975],[63.316632,26.756532],[63.233898,27.217047],[62.755426,27.378923],[62.72783,28.259645],[61.771868,28.699334],[61.369309,29.303276],[60.874248,29.829239],[62.549857,29.318572],[63.550261,29.468331],[64.148002,29.340819],[64.350419,29.560031],[65.046862,29.472181],[66.346473,29.887943],[66.381458,30.738899],[66.938891,31.304911],[67.683394,31.303154],[67.792689,31.58293],[68.556932,31.71331],[68.926677,31.620189],[69.317764,31.901412],[69.262522,32.501944],[69.687147,33.105499],[70.323594,33.358533],[69.930543,34.02012],[70.881803,33.988856],[71.156773,34.348911],[71.115019,34.733126],[71.613076,35.153203],[71.498768,35.650563],[71.262348,36.074388],[71.846292,36.509942],[72.920025,36.720007],[74.067552,36.836176],[74.575893,37.020841],[75.158028,37.133031]]]}}, {"type":"Feature","id":"PAN","properties":{"name":"Panama"},"geometry":{"type":"Polygon","coordinates":[[[-77.881571,7.223771],[-78.214936,7.512255],[-78.429161,8.052041],[-78.182096,8.319182],[-78.435465,8.387705],[-78.622121,8.718124],[-79.120307,8.996092],[-79.557877,8.932375],[-79.760578,8.584515],[-80.164481,8.333316],[-80.382659,8.298409],[-80.480689,8.090308],[-80.00369,7.547524],[-80.276671,7.419754],[-80.421158,7.271572],[-80.886401,7.220541],[-81.059543,7.817921],[-81.189716,7.647906],[-81.519515,7.70661],[-81.721311,8.108963],[-82.131441,8.175393],[-82.390934,8.292362],[-82.820081,8.290864],[-82.850958,8.073823],[-82.965783,8.225028],[-82.913176,8.423517],[-82.829771,8.626295],[-82.868657,8.807266],[-82.719183,8.925709],[-82.927155,9.07433],[-82.932891,9.476812],[-82.546196,9.566135],[-82.187123,9.207449],[-82.207586,8.995575],[-81.808567,8.950617],[-81.714154,9.031955],[-81.439287,8.786234],[-80.947302,8.858504],[-80.521901,9.111072],[-79.9146,9.312765],[-79.573303,9.61161],[-79.021192,9.552931],[-79.05845,9.454565],[-78.500888,9.420459],[-78.055928,9.24773],[-77.729514,8.946844],[-77.353361,8.670505],[-77.474723,8.524286],[-77.242566,7.935278],[-77.431108,7.638061],[-77.753414,7.70984],[-77.881571,7.223771]]]}}, {"type":"Feature","id":"PER","properties":{"name":"Peru"},"geometry":{"type":"Polygon","coordinates":[[[-69.590424,-17.580012],[-69.858444,-18.092694],[-70.372572,-18.347975],[-71.37525,-17.773799],[-71.462041,-17.363488],[-73.44453,-16.359363],[-75.237883,-15.265683],[-76.009205,-14.649286],[-76.423469,-13.823187],[-76.259242,-13.535039],[-77.106192,-12.222716],[-78.092153,-10.377712],[-79.036953,-8.386568],[-79.44592,-7.930833],[-79.760578,-7.194341],[-80.537482,-6.541668],[-81.249996,-6.136834],[-80.926347,-5.690557],[-81.410943,-4.736765],[-81.09967,-4.036394],[-80.302561,-3.404856],[-80.184015,-3.821162],[-80.469295,-4.059287],[-80.442242,-4.425724],[-80.028908,-4.346091],[-79.624979,-4.454198],[-79.205289,-4.959129],[-78.639897,-4.547784],[-78.450684,-3.873097],[-77.837905,-3.003021],[-76.635394,-2.608678],[-75.544996,-1.56161],[-75.233723,-0.911417],[-75.373223,-0.152032],[-75.106625,-0.057205],[-74.441601,-0.53082],[-74.122395,-1.002833],[-73.659504,-1.260491],[-73.070392,-2.308954],[-72.325787,-2.434218],[-71.774761,-2.16979],[-71.413646,-2.342802],[-70.813476,-2.256865],[-70.047709,-2.725156],[-70.692682,-3.742872],[-70.394044,-3.766591],[-69.893635,-4.298187],[-70.794769,-4.251265],[-70.928843,-4.401591],[-71.748406,-4.593983],[-72.891928,-5.274561],[-72.964507,-5.741251],[-73.219711,-6.089189],[-73.120027,-6.629931],[-73.724487,-6.918595],[-73.723401,-7.340999],[-73.987235,-7.52383],[-73.571059,-8.424447],[-73.015383,-9.032833],[-73.226713,-9.462213],[-72.563033,-9.520194],[-72.184891,-10.053598],[-71.302412,-10.079436],[-70.481894,-9.490118],[-70.548686,-11.009147],[-70.093752,-11.123972],[-69.529678,-10.951734],[-68.66508,-12.5613],[-68.88008,-12.899729],[-68.929224,-13.602684],[-68.948887,-14.453639],[-69.339535,-14.953195],[-69.160347,-15.323974],[-69.389764,-15.660129],[-68.959635,-16.500698],[-69.590424,-17.580012]]]}}, {"type":"Feature","id":"PHL","properties":{"name":"Philippines"},"geometry":{"type":"MultiPolygon","coordinates":[[[[126.376814,8.414706],[126.478513,7.750354],[126.537424,7.189381],[126.196773,6.274294],[125.831421,7.293715],[125.363852,6.786485],[125.683161,6.049657],[125.396512,5.581003],[124.219788,6.161355],[123.93872,6.885136],[124.243662,7.36061],[123.610212,7.833527],[123.296071,7.418876],[122.825506,7.457375],[122.085499,6.899424],[121.919928,7.192119],[122.312359,8.034962],[122.942398,8.316237],[123.487688,8.69301],[123.841154,8.240324],[124.60147,8.514158],[124.764612,8.960409],[125.471391,8.986997],[125.412118,9.760335],[126.222714,9.286074],[126.306637,8.782487],[126.376814,8.414706]]],[[[123.982438,10.278779],[123.623183,9.950091],[123.309921,9.318269],[122.995883,9.022189],[122.380055,9.713361],[122.586089,9.981045],[122.837081,10.261157],[122.947411,10.881868],[123.49885,10.940624],[123.337774,10.267384],[124.077936,11.232726],[123.982438,10.278779]]],[[[118.504581,9.316383],[117.174275,8.3675],[117.664477,9.066889],[118.386914,9.6845],[118.987342,10.376292],[119.511496,11.369668],[119.689677,10.554291],[119.029458,10.003653],[118.504581,9.316383]]],[[[121.883548,11.891755],[122.483821,11.582187],[123.120217,11.58366],[123.100838,11.165934],[122.637714,10.741308],[122.00261,10.441017],[121.967367,10.905691],[122.03837,11.415841],[121.883548,11.891755]]],[[[125.502552,12.162695],[125.783465,11.046122],[125.011884,11.311455],[125.032761,10.975816],[125.277449,10.358722],[124.801819,10.134679],[124.760168,10.837995],[124.459101,10.88993],[124.302522,11.495371],[124.891013,11.415583],[124.87799,11.79419],[124.266762,12.557761],[125.227116,12.535721],[125.502552,12.162695]]],[[[121.527394,13.06959],[121.26219,12.20556],[120.833896,12.704496],[120.323436,13.466413],[121.180128,13.429697],[121.527394,13.06959]]],[[[121.321308,18.504065],[121.937601,18.218552],[122.246006,18.47895],[122.336957,18.224883],[122.174279,17.810283],[122.515654,17.093505],[122.252311,16.262444],[121.662786,15.931018],[121.50507,15.124814],[121.728829,14.328376],[122.258925,14.218202],[122.701276,14.336541],[123.950295,13.782131],[123.855107,13.237771],[124.181289,12.997527],[124.077419,12.536677],[123.298035,13.027526],[122.928652,13.55292],[122.671355,13.185836],[122.03465,13.784482],[121.126385,13.636687],[120.628637,13.857656],[120.679384,14.271016],[120.991819,14.525393],[120.693336,14.756671],[120.564145,14.396279],[120.070429,14.970869],[119.920929,15.406347],[119.883773,16.363704],[120.286488,16.034629],[120.390047,17.599081],[120.715867,18.505227],[121.321308,18.504065]]]]}}, {"type":"Feature","id":"PNG","properties":{"name":"Papua New Guinea"},"geometry":{"type":"MultiPolygon","coordinates":[[[[155.880026,-6.819997],[155.599991,-6.919991],[155.166994,-6.535931],[154.729192,-5.900828],[154.514114,-5.139118],[154.652504,-5.042431],[154.759991,-5.339984],[155.062918,-5.566792],[155.547746,-6.200655],[156.019965,-6.540014],[155.880026,-6.819997]]],[[[151.982796,-5.478063],[151.459107,-5.56028],[151.30139,-5.840728],[150.754447,-6.083763],[150.241197,-6.317754],[149.709963,-6.316513],[148.890065,-6.02604],[148.318937,-5.747142],[148.401826,-5.437756],[149.298412,-5.583742],[149.845562,-5.505503],[149.99625,-5.026101],[150.139756,-5.001348],[150.236908,-5.53222],[150.807467,-5.455842],[151.089672,-5.113693],[151.647881,-4.757074],[151.537862,-4.167807],[152.136792,-4.14879],[152.338743,-4.312966],[152.318693,-4.867661],[151.982796,-5.478063]]],[[[147.191874,-7.388024],[148.084636,-8.044108],[148.734105,-9.104664],[149.306835,-9.071436],[149.266631,-9.514406],[150.038728,-9.684318],[149.738798,-9.872937],[150.801628,-10.293687],[150.690575,-10.582713],[150.028393,-10.652476],[149.78231,-10.393267],[148.923138,-10.280923],[147.913018,-10.130441],[147.135443,-9.492444],[146.567881,-8.942555],[146.048481,-8.067414],[144.744168,-7.630128],[143.897088,-7.91533],[143.286376,-8.245491],[143.413913,-8.983069],[142.628431,-9.326821],[142.068259,-9.159596],[141.033852,-9.117893],[141.017057,-5.859022],[141.00021,-2.600151],[142.735247,-3.289153],[144.583971,-3.861418],[145.27318,-4.373738],[145.829786,-4.876498],[145.981922,-5.465609],[147.648073,-6.083659],[147.891108,-6.614015],[146.970905,-6.721657],[147.191874,-7.388024]]],[[[153.140038,-4.499983],[152.827292,-4.766427],[152.638673,-4.176127],[152.406026,-3.789743],[151.953237,-3.462062],[151.384279,-3.035422],[150.66205,-2.741486],[150.939965,-2.500002],[151.479984,-2.779985],[151.820015,-2.999972],[152.239989,-3.240009],[152.640017,-3.659983],[153.019994,-3.980015],[153.140038,-4.499983]]]]}}, {"type":"Feature","id":"POL","properties":{"name":"Poland"},"geometry":{"type":"Polygon","coordinates":[[[15.016996,51.106674],[14.607098,51.745188],[14.685026,52.089947],[14.4376,52.62485],[14.074521,52.981263],[14.353315,53.248171],[14.119686,53.757029],[14.8029,54.050706],[16.363477,54.513159],[17.622832,54.851536],[18.620859,54.682606],[18.696255,54.438719],[19.66064,54.426084],[20.892245,54.312525],[22.731099,54.327537],[23.243987,54.220567],[23.484128,53.912498],[23.527536,53.470122],[23.804935,53.089731],[23.799199,52.691099],[23.199494,52.486977],[23.508002,52.023647],[23.527071,51.578454],[24.029986,50.705407],[23.922757,50.424881],[23.426508,50.308506],[22.51845,49.476774],[22.776419,49.027395],[22.558138,49.085738],[21.607808,49.470107],[20.887955,49.328772],[20.415839,49.431453],[19.825023,49.217125],[19.320713,49.571574],[18.909575,49.435846],[18.853144,49.49623],[18.392914,49.988629],[17.649445,50.049038],[17.554567,50.362146],[16.868769,50.473974],[16.719476,50.215747],[16.176253,50.422607],[16.238627,50.697733],[15.490972,50.78473],[15.016996,51.106674]]]}}, {"type":"Feature","id":"PRI","properties":{"name":"Puerto Rico"},"geometry":{"type":"Polygon","coordinates":[[[-66.282434,18.514762],[-65.771303,18.426679],[-65.591004,18.228035],[-65.847164,17.975906],[-66.599934,17.981823],[-67.184162,17.946553],[-67.242428,18.37446],[-67.100679,18.520601],[-66.282434,18.514762]]]}}, {"type":"Feature","id":"PRK","properties":{"name":"North Korea"},"geometry":{"type":"Polygon","coordinates":[[[130.640016,42.395009],[130.780007,42.220007],[130.400031,42.280004],[129.965949,41.941368],[129.667362,41.601104],[129.705189,40.882828],[129.188115,40.661808],[129.0104,40.485436],[128.633368,40.189847],[127.967414,40.025413],[127.533436,39.75685],[127.50212,39.323931],[127.385434,39.213472],[127.783343,39.050898],[128.349716,38.612243],[128.205746,38.370397],[127.780035,38.304536],[127.073309,38.256115],[126.68372,37.804773],[126.237339,37.840378],[126.174759,37.749686],[125.689104,37.94001],[125.568439,37.752089],[125.27533,37.669071],[125.240087,37.857224],[124.981033,37.948821],[124.712161,38.108346],[124.985994,38.548474],[125.221949,38.665857],[125.132859,38.848559],[125.38659,39.387958],[125.321116,39.551385],[124.737482,39.660344],[124.265625,39.928493],[125.079942,40.569824],[126.182045,41.107336],[126.869083,41.816569],[127.343783,41.503152],[128.208433,41.466772],[128.052215,41.994285],[129.596669,42.424982],[129.994267,42.985387],[130.640016,42.395009]]]}}, {"type":"Feature","id":"PRT","properties":{"name":"Portugal"},"geometry":{"type":"Polygon","coordinates":[[[-9.034818,41.880571],[-8.671946,42.134689],[-8.263857,42.280469],[-8.013175,41.790886],[-7.422513,41.792075],[-7.251309,41.918346],[-6.668606,41.883387],[-6.389088,41.381815],[-6.851127,41.111083],[-6.86402,40.330872],[-7.026413,40.184524],[-7.066592,39.711892],[-7.498632,39.629571],[-7.098037,39.030073],[-7.374092,38.373059],[-7.029281,38.075764],[-7.166508,37.803894],[-7.537105,37.428904],[-7.453726,37.097788],[-7.855613,36.838269],[-8.382816,36.97888],[-8.898857,36.868809],[-8.746101,37.651346],[-8.839998,38.266243],[-9.287464,38.358486],[-9.526571,38.737429],[-9.446989,39.392066],[-9.048305,39.755093],[-8.977353,40.159306],[-8.768684,40.760639],[-8.790853,41.184334],[-8.990789,41.543459],[-9.034818,41.880571]]]}}, {"type":"Feature","id":"PRY","properties":{"name":"Paraguay"},"geometry":{"type":"Polygon","coordinates":[[[-62.685057,-22.249029],[-62.291179,-21.051635],[-62.265961,-20.513735],[-61.786326,-19.633737],[-60.043565,-19.342747],[-59.115042,-19.356906],[-58.183471,-19.868399],[-58.166392,-20.176701],[-57.870674,-20.732688],[-57.937156,-22.090176],[-56.88151,-22.282154],[-56.473317,-22.0863],[-55.797958,-22.35693],[-55.610683,-22.655619],[-55.517639,-23.571998],[-55.400747,-23.956935],[-55.027902,-24.001274],[-54.652834,-23.839578],[-54.29296,-24.021014],[-54.293476,-24.5708],[-54.428946,-25.162185],[-54.625291,-25.739255],[-54.788795,-26.621786],[-55.695846,-27.387837],[-56.486702,-27.548499],[-57.60976,-27.395899],[-58.618174,-27.123719],[-57.63366,-25.603657],[-57.777217,-25.16234],[-58.807128,-24.771459],[-60.028966,-24.032796],[-60.846565,-23.880713],[-62.685057,-22.249029]]]}}, {"type":"Feature","id":"QAT","properties":{"name":"Qatar"},"geometry":{"type":"Polygon","coordinates":[[[50.810108,24.754743],[50.743911,25.482424],[51.013352,26.006992],[51.286462,26.114582],[51.589079,25.801113],[51.6067,25.21567],[51.389608,24.627386],[51.112415,24.556331],[50.810108,24.754743]]]}}, {"type":"Feature","id":"ROU","properties":{"name":"Romania"},"geometry":{"type":"Polygon","coordinates":[[[22.710531,47.882194],[23.142236,48.096341],[23.760958,47.985598],[24.402056,47.981878],[24.866317,47.737526],[25.207743,47.891056],[25.945941,47.987149],[26.19745,48.220881],[26.619337,48.220726],[26.924176,48.123264],[27.233873,47.826771],[27.551166,47.405117],[28.12803,46.810476],[28.160018,46.371563],[28.054443,45.944586],[28.233554,45.488283],[28.679779,45.304031],[29.149725,45.464925],[29.603289,45.293308],[29.626543,45.035391],[29.141612,44.82021],[28.837858,44.913874],[28.558081,43.707462],[27.970107,43.812468],[27.2424,44.175986],[26.065159,43.943494],[25.569272,43.688445],[24.100679,43.741051],[23.332302,43.897011],[22.944832,43.823785],[22.65715,44.234923],[22.474008,44.409228],[22.705726,44.578003],[22.459022,44.702517],[22.145088,44.478422],[21.562023,44.768947],[21.483526,45.18117],[20.874313,45.416375],[20.762175,45.734573],[20.220192,46.127469],[21.021952,46.316088],[21.626515,46.994238],[22.099768,47.672439],[22.710531,47.882194]]]}}, {"type":"Feature","id":"RUS","properties":{"name":"Russia"},"geometry":{"type":"MultiPolygon","coordinates":[[[[143.648007,50.7476],[144.654148,48.976391],[143.173928,49.306551],[142.558668,47.861575],[143.533492,46.836728],[143.505277,46.137908],[142.747701,46.740765],[142.09203,45.966755],[141.906925,46.805929],[142.018443,47.780133],[141.904445,48.859189],[142.1358,49.615163],[142.179983,50.952342],[141.594076,51.935435],[141.682546,53.301966],[142.606934,53.762145],[142.209749,54.225476],[142.654786,54.365881],[142.914616,53.704578],[143.260848,52.74076],[143.235268,51.75666],[143.648007,50.7476]]],[[[22.731099,54.327537],[20.892245,54.312525],[19.66064,54.426084],[19.888481,54.86616],[21.268449,55.190482],[22.315724,55.015299],[22.757764,54.856574],[22.651052,54.582741],[22.731099,54.327537]]],[[[-175.01425,66.58435],[-174.33983,66.33556],[-174.57182,67.06219],[-171.85731,66.91308],[-169.89958,65.97724],[-170.89107,65.54139],[-172.53025,65.43791],[-172.555,64.46079],[-172.95533,64.25269],[-173.89184,64.2826],[-174.65392,64.63125],[-175.98353,64.92288],[-176.20716,65.35667],[-177.22266,65.52024],[-178.35993,65.39052],[-178.90332,65.74044],[-178.68611,66.11211],[-179.88377,65.87456],[-179.43268,65.40411],[-180,64.979709],[-180,68.963636],[-177.55,68.2],[-174.92825,67.20589],[-175.01425,66.58435]]],[[[180,70.832199],[178.903425,70.78114],[178.7253,71.0988],[180,71.515714],[180,70.832199]]],[[[-178.69378,70.89302],[-180,70.832199],[-180,71.515714],[-179.871875,71.55762],[-179.02433,71.55553],[-177.577945,71.26948],[-177.663575,71.13277],[-178.69378,70.89302]]],[[[143.60385,73.21244],[142.08763,73.20544],[140.038155,73.31692],[139.86312,73.36983],[140.81171,73.76506],[142.06207,73.85758],[143.48283,73.47525],[143.60385,73.21244]]],[[[150.73167,75.08406],[149.575925,74.68892],[147.977465,74.778355],[146.11919,75.17298],[146.358485,75.49682],[148.22223,75.345845],[150.73167,75.08406]]],[[[145.086285,75.562625],[144.3,74.82],[140.61381,74.84768],[138.95544,74.61148],[136.97439,75.26167],[137.51176,75.94917],[138.831075,76.13676],[141.471615,76.09289],[145.086285,75.562625]]],[[[57.535693,70.720464],[56.944979,70.632743],[53.677375,70.762658],[53.412017,71.206662],[51.601895,71.474759],[51.455754,72.014881],[52.478275,72.229442],[52.444169,72.774731],[54.427614,73.627548],[53.50829,73.749814],[55.902459,74.627486],[55.631933,75.081412],[57.868644,75.60939],[61.170044,76.251883],[64.498368,76.439055],[66.210977,76.809782],[68.15706,76.939697],[68.852211,76.544811],[68.180573,76.233642],[64.637326,75.737755],[61.583508,75.260885],[58.477082,74.309056],[56.986786,73.333044],[55.419336,72.371268],[55.622838,71.540595],[57.535693,70.720464]]],[[[106.97013,76.97419],[107.24,76.48],[108.1538,76.72335],[111.07726,76.71],[113.33151,76.22224],[114.13417,75.84764],[113.88539,75.32779],[112.77918,75.03186],[110.15125,74.47673],[109.4,74.18],[110.64,74.04],[112.11919,73.78774],[113.01954,73.97693],[113.52958,73.33505],[113.96881,73.59488],[115.56782,73.75285],[118.77633,73.58772],[119.02,73.12],[123.20066,72.97122],[123.25777,73.73503],[125.38,73.56],[126.97644,73.56549],[128.59126,73.03871],[129.05157,72.39872],[128.46,71.98],[129.71599,71.19304],[131.28858,70.78699],[132.2535,71.8363],[133.85766,71.38642],[135.56193,71.65525],[137.49755,71.34763],[138.23409,71.62803],[139.86983,71.48783],[139.14791,72.41619],[140.46817,72.84941],[149.5,72.2],[150.35118,71.60643],[152.9689,70.84222],[157.00688,71.03141],[158.99779,70.86672],[159.83031,70.45324],[159.70866,69.72198],[160.94053,69.43728],[162.27907,69.64204],[164.05248,69.66823],[165.94037,69.47199],[167.83567,69.58269],[169.57763,68.6938],[170.81688,69.01363],[170.0082,69.65276],[170.45345,70.09703],[173.64391,69.81743],[175.72403,69.87725],[178.6,69.4],[180,68.963636],[180,64.979709],[179.99281,64.97433],[178.7072,64.53493],[177.41128,64.60821],[178.313,64.07593],[178.90825,63.25197],[179.37034,62.98262],[179.48636,62.56894],[179.22825,62.3041],[177.3643,62.5219],[174.56929,61.76915],[173.68013,61.65261],[172.15,60.95],[170.6985,60.33618],[170.33085,59.88177],[168.90046,60.57355],[166.29498,59.78855],[165.84,60.16],[164.87674,59.7316],[163.53929,59.86871],[163.21711,59.21101],[162.01733,58.24328],[162.05297,57.83912],[163.19191,57.61503],[163.05794,56.15924],[162.12958,56.12219],[161.70146,55.28568],[162.11749,54.85514],[160.36877,54.34433],[160.02173,53.20257],[158.53094,52.95868],[158.23118,51.94269],[156.78979,51.01105],[156.42,51.7],[155.99182,53.15895],[155.43366,55.38103],[155.91442,56.76792],[156.75815,57.3647],[156.81035,57.83204],[158.36433,58.05575],[160.15064,59.31477],[161.87204,60.343],[163.66969,61.1409],[164.47355,62.55061],[163.25842,62.46627],[162.65791,61.6425],[160.12148,60.54423],[159.30232,61.77396],[156.72068,61.43442],[154.21806,59.75818],[155.04375,59.14495],[152.81185,58.88385],[151.26573,58.78089],[151.33815,59.50396],[149.78371,59.65573],[148.54481,59.16448],[145.48722,59.33637],[142.19782,59.03998],[138.95848,57.08805],[135.12619,54.72959],[136.70171,54.60355],[137.19342,53.97732],[138.1647,53.75501],[138.80463,54.25455],[139.90151,54.18968],[141.34531,53.08957],[141.37923,52.23877],[140.59742,51.23967],[140.51308,50.04553],[140.06193,48.44671],[138.55472,46.99965],[138.21971,46.30795],[136.86232,45.1435],[135.51535,43.989],[134.86939,43.39821],[133.53687,42.81147],[132.90627,42.79849],[132.27807,43.28456],[130.93587,42.55274],[130.78,42.22],[130.64,42.395],[130.633866,42.903015],[131.144688,42.92999],[131.288555,44.11152],[131.02519,44.96796],[131.883454,45.321162],[133.09712,45.14409],[133.769644,46.116927],[134.11235,47.21248],[134.50081,47.57845],[135.026311,48.47823],[133.373596,48.183442],[132.50669,47.78896],[130.98726,47.79013],[130.582293,48.729687],[129.397818,49.4406],[127.6574,49.76027],[127.287456,50.739797],[126.939157,51.353894],[126.564399,51.784255],[125.946349,52.792799],[125.068211,53.161045],[123.57147,53.4588],[122.245748,53.431726],[121.003085,53.251401],[120.177089,52.753886],[120.725789,52.516226],[120.7382,51.96411],[120.18208,51.64355],[119.27939,50.58292],[119.288461,50.142883],[117.879244,49.510983],[116.678801,49.888531],[115.485695,49.805177],[114.96211,50.140247],[114.362456,50.248303],[112.89774,49.543565],[111.581231,49.377968],[110.662011,49.130128],[109.402449,49.292961],[108.475167,49.282548],[107.868176,49.793705],[106.888804,50.274296],[105.886591,50.406019],[104.62158,50.27532],[103.676545,50.089966],[102.25589,50.51056],[102.06521,51.25991],[100.88948,51.516856],[99.981732,51.634006],[98.861491,52.047366],[97.82574,51.010995],[98.231762,50.422401],[97.25976,49.72605],[95.81402,49.97746],[94.815949,50.013433],[94.147566,50.480537],[93.10421,50.49529],[92.234712,50.802171],[90.713667,50.331812],[88.805567,49.470521],[87.751264,49.297198],[87.35997,49.214981],[86.829357,49.826675],[85.54127,49.692859],[85.11556,50.117303],[84.416377,50.3114],[83.935115,50.889246],[83.383004,51.069183],[81.945986,50.812196],[80.568447,51.388336],[80.03556,50.864751],[77.800916,53.404415],[76.525179,54.177003],[76.8911,54.490524],[74.38482,53.54685],[73.425679,53.48981],[73.508516,54.035617],[72.22415,54.376655],[71.180131,54.133285],[70.865267,55.169734],[69.068167,55.38525],[68.1691,54.970392],[65.66687,54.60125],[65.178534,54.354228],[61.4366,54.00625],[60.978066,53.664993],[61.699986,52.979996],[60.739993,52.719986],[60.927269,52.447548],[59.967534,51.96042],[61.588003,51.272659],[61.337424,50.79907],[59.932807,50.842194],[59.642282,50.545442],[58.36332,51.06364],[56.77798,51.04355],[55.71694,50.62171],[54.532878,51.02624],[52.328724,51.718652],[50.766648,51.692762],[48.702382,50.605128],[48.577841,49.87476],[47.54948,50.454698],[46.751596,49.356006],[47.043672,49.152039],[46.466446,48.394152],[47.31524,47.71585],[48.05725,47.74377],[48.694734,47.075628],[48.59325,46.56104],[49.10116,46.39933],[48.64541,45.80629],[47.67591,45.64149],[46.68201,44.6092],[47.59094,43.66016],[47.49252,42.98658],[48.58437,41.80888],[47.987283,41.405819],[47.815666,41.151416],[47.373315,41.219732],[46.686071,41.827137],[46.404951,41.860675],[45.7764,42.09244],[45.470279,42.502781],[44.537623,42.711993],[43.93121,42.55496],[43.75599,42.74083],[42.3944,43.2203],[40.92219,43.38215],[40.076965,43.553104],[39.955009,43.434998],[38.68,44.28],[37.53912,44.65721],[36.67546,45.24469],[37.40317,45.40451],[38.23295,46.24087],[37.67372,46.63657],[39.14767,47.04475],[39.1212,47.26336],[38.223538,47.10219],[38.255112,47.5464],[38.77057,47.82562],[39.738278,47.898937],[39.89562,48.23241],[39.67465,48.78382],[40.080789,49.30743],[40.06904,49.60105],[38.594988,49.926462],[38.010631,49.915662],[37.39346,50.383953],[36.626168,50.225591],[35.356116,50.577197],[35.37791,50.77394],[35.022183,51.207572],[34.224816,51.255993],[34.141978,51.566413],[34.391731,51.768882],[33.7527,52.335075],[32.715761,52.238465],[32.412058,52.288695],[32.15944,52.06125],[31.78597,52.10168],[31.540018,52.742052],[31.305201,53.073996],[31.49764,53.16743],[32.304519,53.132726],[32.693643,53.351421],[32.405599,53.618045],[31.731273,53.794029],[31.791424,53.974639],[31.384472,54.157056],[30.757534,54.811771],[30.971836,55.081548],[30.873909,55.550976],[29.896294,55.789463],[29.371572,55.670091],[29.229513,55.918344],[28.176709,56.16913],[27.855282,56.759326],[27.770016,57.244258],[27.288185,57.474528],[27.716686,57.791899],[27.42015,58.72457],[28.131699,59.300825],[27.98112,59.47537],[29.1177,60.02805],[28.07,60.50352],[30.211107,61.780028],[31.139991,62.357693],[31.516092,62.867687],[30.035872,63.552814],[30.444685,64.204453],[29.54443,64.948672],[30.21765,65.80598],[29.054589,66.944286],[29.977426,67.698297],[28.445944,68.364613],[28.59193,69.064777],[29.39955,69.15692],[31.10108,69.55811],[32.13272,69.90595],[33.77547,69.30142],[36.51396,69.06342],[40.29234,67.9324],[41.05987,67.45713],[41.12595,66.79158],[40.01583,66.26618],[38.38295,65.99953],[33.91871,66.75961],[33.18444,66.63253],[34.81477,65.90015],[34.878574,65.436213],[34.94391,64.41437],[36.23129,64.10945],[37.01273,63.84983],[37.14197,64.33471],[36.539579,64.76446],[37.17604,65.14322],[39.59345,64.52079],[40.4356,64.76446],[39.7626,65.49682],[42.09309,66.47623],[43.01604,66.41858],[43.94975,66.06908],[44.53226,66.75634],[43.69839,67.35245],[44.18795,67.95051],[43.45282,68.57079],[46.25,68.25],[46.82134,67.68997],[45.55517,67.56652],[45.56202,67.01005],[46.34915,66.66767],[47.89416,66.88455],[48.13876,67.52238],[50.22766,67.99867],[53.71743,68.85738],[54.47171,68.80815],[53.48582,68.20131],[54.72628,68.09702],[55.44268,68.43866],[57.31702,68.46628],[58.802,68.88082],[59.94142,68.27844],[61.07784,68.94069],[60.03,69.52],[60.55,69.85],[63.504,69.54739],[64.888115,69.234835],[68.51216,68.09233],[69.18068,68.61563],[68.16444,69.14436],[68.13522,69.35649],[66.93008,69.45461],[67.25976,69.92873],[66.72492,70.70889],[66.69466,71.02897],[68.54006,71.9345],[69.19636,72.84336],[69.94,73.04],[72.58754,72.77629],[72.79603,72.22006],[71.84811,71.40898],[72.47011,71.09019],[72.79188,70.39114],[72.5647,69.02085],[73.66787,68.4079],[73.2387,67.7404],[71.28,66.32],[72.42301,66.17267],[72.82077,66.53267],[73.92099,66.78946],[74.18651,67.28429],[75.052,67.76047],[74.46926,68.32899],[74.93584,68.98918],[73.84236,69.07146],[73.60187,69.62763],[74.3998,70.63175],[73.1011,71.44717],[74.89082,72.12119],[74.65926,72.83227],[75.15801,72.85497],[75.68351,72.30056],[75.28898,71.33556],[76.35911,71.15287],[75.90313,71.87401],[77.57665,72.26717],[79.65202,72.32011],[81.5,71.75],[80.61071,72.58285],[80.51109,73.6482],[82.25,73.85],[84.65526,73.80591],[86.8223,73.93688],[86.00956,74.45967],[87.16682,75.11643],[88.31571,75.14393],[90.26,75.64],[92.90058,75.77333],[93.23421,76.0472],[95.86,76.14],[96.67821,75.91548],[98.92254,76.44689],[100.75967,76.43028],[101.03532,76.86189],[101.99084,77.28754],[104.3516,77.69792],[106.06664,77.37389],[104.705,77.1274],[106.97013,76.97419]]],[[[105.07547,78.30689],[99.43814,77.921],[101.2649,79.23399],[102.08635,79.34641],[102.837815,79.28129],[105.37243,78.71334],[105.07547,78.30689]]],[[[51.136187,80.54728],[49.793685,80.415428],[48.894411,80.339567],[48.754937,80.175468],[47.586119,80.010181],[46.502826,80.247247],[47.072455,80.559424],[44.846958,80.58981],[46.799139,80.771918],[48.318477,80.78401],[48.522806,80.514569],[49.09719,80.753986],[50.039768,80.918885],[51.522933,80.699726],[51.136187,80.54728]]],[[[99.93976,78.88094],[97.75794,78.7562],[94.97259,79.044745],[93.31288,79.4265],[92.5454,80.14379],[91.18107,80.34146],[93.77766,81.0246],[95.940895,81.2504],[97.88385,80.746975],[100.186655,79.780135],[99.93976,78.88094]]]]}}, {"type":"Feature","id":"RWA","properties":{"name":"Rwanda"},"geometry":{"type":"Polygon","coordinates":[[[30.419105,-1.134659],[30.816135,-1.698914],[30.758309,-2.28725],[30.469696,-2.413858],[29.938359,-2.348487],[29.632176,-2.917858],[29.024926,-2.839258],[29.117479,-2.292211],[29.254835,-2.21511],[29.291887,-1.620056],[29.579466,-1.341313],[29.821519,-1.443322],[30.419105,-1.134659]]]}}, {"type":"Feature","id":"ESH","properties":{"name":"Western Sahara"},"geometry":{"type":"Polygon","coordinates":[[[-8.794884,27.120696],[-8.817828,27.656426],[-8.66559,27.656426],[-8.665124,27.589479],[-8.6844,27.395744],[-8.687294,25.881056],[-11.969419,25.933353],[-11.937224,23.374594],[-12.874222,23.284832],[-13.118754,22.77122],[-12.929102,21.327071],[-16.845194,21.333323],[-17.063423,20.999752],[-17.020428,21.42231],[-17.002962,21.420734],[-14.750955,21.5006],[-14.630833,21.86094],[-14.221168,22.310163],[-13.89111,23.691009],[-12.500963,24.770116],[-12.030759,26.030866],[-11.71822,26.104092],[-11.392555,26.883424],[-10.551263,26.990808],[-10.189424,26.860945],[-9.735343,26.860945],[-9.413037,27.088476],[-8.794884,27.120696]]]}}, {"type":"Feature","id":"SAU","properties":{"name":"Saudi Arabia"},"geometry":{"type":"Polygon","coordinates":[[[42.779332,16.347891],[42.649573,16.774635],[42.347989,17.075806],[42.270888,17.474722],[41.754382,17.833046],[41.221391,18.6716],[40.939341,19.486485],[40.247652,20.174635],[39.801685,20.338862],[39.139399,21.291905],[39.023696,21.986875],[39.066329,22.579656],[38.492772,23.688451],[38.02386,24.078686],[37.483635,24.285495],[37.154818,24.858483],[37.209491,25.084542],[36.931627,25.602959],[36.639604,25.826228],[36.249137,26.570136],[35.640182,27.37652],[35.130187,28.063352],[34.632336,28.058546],[34.787779,28.607427],[34.83222,28.957483],[34.956037,29.356555],[36.068941,29.197495],[36.501214,29.505254],[36.740528,29.865283],[37.503582,30.003776],[37.66812,30.338665],[37.998849,30.5085],[37.002166,31.508413],[39.004886,32.010217],[39.195468,32.161009],[40.399994,31.889992],[41.889981,31.190009],[44.709499,29.178891],[46.568713,29.099025],[47.459822,29.002519],[47.708851,28.526063],[48.416094,28.552004],[48.807595,27.689628],[49.299554,27.461218],[49.470914,27.109999],[50.152422,26.689663],[50.212935,26.277027],[50.113303,25.943972],[50.239859,25.60805],[50.527387,25.327808],[50.660557,24.999896],[50.810108,24.754743],[51.112415,24.556331],[51.389608,24.627386],[51.579519,24.245497],[51.617708,24.014219],[52.000733,23.001154],[55.006803,22.496948],[55.208341,22.70833],[55.666659,22.000001],[54.999982,19.999994],[52.00001,19.000003],[49.116672,18.616668],[48.183344,18.166669],[47.466695,17.116682],[47.000005,16.949999],[46.749994,17.283338],[46.366659,17.233315],[45.399999,17.333335],[45.216651,17.433329],[44.062613,17.410359],[43.791519,17.319977],[43.380794,17.579987],[43.115798,17.08844],[43.218375,16.66689],[42.779332,16.347891]]]}}, {"type":"Feature","id":"SDN","properties":{"name":"Sudan"},"geometry":{"type":"Polygon","coordinates":[[[33.963393,9.464285],[33.824963,9.484061],[33.842131,9.981915],[33.721959,10.325262],[33.206938,10.720112],[33.086766,11.441141],[33.206938,12.179338],[32.743419,12.248008],[32.67475,12.024832],[32.073892,11.97333],[32.314235,11.681484],[32.400072,11.080626],[31.850716,10.531271],[31.352862,9.810241],[30.837841,9.707237],[29.996639,10.290927],[29.618957,10.084919],[29.515953,9.793074],[29.000932,9.604232],[28.966597,9.398224],[27.97089,9.398224],[27.833551,9.604232],[27.112521,9.638567],[26.752006,9.466893],[26.477328,9.55273],[25.962307,10.136421],[25.790633,10.411099],[25.069604,10.27376],[24.794926,9.810241],[24.537415,8.917538],[24.194068,8.728696],[23.88698,8.61973],[23.805813,8.666319],[23.459013,8.954286],[23.394779,9.265068],[23.55725,9.681218],[23.554304,10.089255],[22.977544,10.714463],[22.864165,11.142395],[22.87622,11.38461],[22.50869,11.67936],[22.49762,12.26024],[22.28801,12.64605],[21.93681,12.58818],[22.03759,12.95546],[22.29658,13.37232],[22.18329,13.78648],[22.51202,14.09318],[22.30351,14.32682],[22.56795,14.94429],[23.02459,15.68072],[23.88689,15.61084],[23.83766,19.58047],[23.85,20],[25,20.00304],[25,22],[29.02,22],[32.9,22],[36.86623,22],[37.18872,21.01885],[36.96941,20.83744],[37.1147,19.80796],[37.48179,18.61409],[37.86276,18.36786],[38.41009,17.998307],[37.904,17.42754],[37.16747,17.26314],[36.85253,16.95655],[36.75389,16.29186],[36.32322,14.82249],[36.42951,14.42211],[36.27022,13.56333],[35.86363,12.57828],[35.26049,12.08286],[34.83163,11.31896],[34.73115,10.91017],[34.25745,10.63009],[33.96162,9.58358],[33.963393,9.464285]]]}}, {"type":"Feature","id":"SSD","properties":{"name":"South Sudan"},"geometry":{"type":"Polygon","coordinates":[[[33.963393,9.464285],[33.97498,8.68456],[33.8255,8.37916],[33.2948,8.35458],[32.95418,7.78497],[33.56829,7.71334],[34.0751,7.22595],[34.25032,6.82607],[34.70702,6.59422],[35.298007,5.506],[34.620196,4.847123],[34.005,4.249885],[33.39,3.79],[32.68642,3.79232],[31.88145,3.55827],[31.24556,3.7819],[30.83385,3.50917],[29.95349,4.1737],[29.715995,4.600805],[29.159078,4.389267],[28.696678,4.455077],[28.428994,4.287155],[27.979977,4.408413],[27.374226,5.233944],[27.213409,5.550953],[26.465909,5.946717],[26.213418,6.546603],[25.796648,6.979316],[25.124131,7.500085],[25.114932,7.825104],[24.567369,8.229188],[23.88698,8.61973],[24.194068,8.728696],[24.537415,8.917538],[24.794926,9.810241],[25.069604,10.27376],[25.790633,10.411099],[25.962307,10.136421],[26.477328,9.55273],[26.752006,9.466893],[27.112521,9.638567],[27.833551,9.604232],[27.97089,9.398224],[28.966597,9.398224],[29.000932,9.604232],[29.515953,9.793074],[29.618957,10.084919],[29.996639,10.290927],[30.837841,9.707237],[31.352862,9.810241],[31.850716,10.531271],[32.400072,11.080626],[32.314235,11.681484],[32.073892,11.97333],[32.67475,12.024832],[32.743419,12.248008],[33.206938,12.179338],[33.086766,11.441141],[33.206938,10.720112],[33.721959,10.325262],[33.842131,9.981915],[33.824963,9.484061],[33.963393,9.464285]]]}}, {"type":"Feature","id":"SEN","properties":{"name":"Senegal"},"geometry":{"type":"Polygon","coordinates":[[[-16.713729,13.594959],[-17.126107,14.373516],[-17.625043,14.729541],[-17.185173,14.919477],[-16.700706,15.621527],[-16.463098,16.135036],[-16.12069,16.455663],[-15.623666,16.369337],[-15.135737,16.587282],[-14.577348,16.598264],[-14.099521,16.304302],[-13.435738,16.039383],[-12.830658,15.303692],[-12.17075,14.616834],[-12.124887,13.994727],[-11.927716,13.422075],[-11.553398,13.141214],[-11.467899,12.754519],[-11.513943,12.442988],[-11.658301,12.386583],[-12.203565,12.465648],[-12.278599,12.35444],[-12.499051,12.33209],[-13.217818,12.575874],[-13.700476,12.586183],[-15.548477,12.62817],[-15.816574,12.515567],[-16.147717,12.547762],[-16.677452,12.384852],[-16.841525,13.151394],[-15.931296,13.130284],[-15.691001,13.270353],[-15.511813,13.27857],[-15.141163,13.509512],[-14.712197,13.298207],[-14.277702,13.280585],[-13.844963,13.505042],[-14.046992,13.794068],[-14.376714,13.62568],[-14.687031,13.630357],[-15.081735,13.876492],[-15.39877,13.860369],[-15.624596,13.623587],[-16.713729,13.594959]]]}}, {"type":"Feature","id":"SLB","properties":{"name":"Solomon Islands"},"geometry":{"type":"MultiPolygon","coordinates":[[[[162.119025,-10.482719],[162.398646,-10.826367],[161.700032,-10.820011],[161.319797,-10.204751],[161.917383,-10.446701],[162.119025,-10.482719]]],[[[160.852229,-9.872937],[160.462588,-9.89521],[159.849447,-9.794027],[159.640003,-9.63998],[159.702945,-9.24295],[160.362956,-9.400304],[160.688518,-9.610162],[160.852229,-9.872937]]],[[[161.679982,-9.599982],[161.529397,-9.784312],[160.788253,-8.917543],[160.579997,-8.320009],[160.920028,-8.320009],[161.280006,-9.120011],[161.679982,-9.599982]]],[[[159.875027,-8.33732],[159.917402,-8.53829],[159.133677,-8.114181],[158.586114,-7.754824],[158.21115,-7.421872],[158.359978,-7.320018],[158.820001,-7.560003],[159.640003,-8.020027],[159.875027,-8.33732]]],[[[157.538426,-7.34782],[157.33942,-7.404767],[156.90203,-7.176874],[156.491358,-6.765943],[156.542828,-6.599338],[157.14,-7.021638],[157.538426,-7.34782]]]]}}, {"type":"Feature","id":"SLE","properties":{"name":"Sierra Leone"},"geometry":{"type":"Polygon","coordinates":[[[-11.438779,6.785917],[-11.708195,6.860098],[-12.428099,7.262942],[-12.949049,7.798646],[-13.124025,8.163946],[-13.24655,8.903049],[-12.711958,9.342712],[-12.596719,9.620188],[-12.425929,9.835834],[-12.150338,9.858572],[-11.917277,10.046984],[-11.117481,10.045873],[-10.839152,9.688246],[-10.622395,9.26791],[-10.65477,8.977178],[-10.494315,8.715541],[-10.505477,8.348896],[-10.230094,8.406206],[-10.695595,7.939464],[-11.146704,7.396706],[-11.199802,7.105846],[-11.438779,6.785917]]]}}, {"type":"Feature","id":"SLV","properties":{"name":"El Salvador"},"geometry":{"type":"Polygon","coordinates":[[[-87.793111,13.38448],[-87.904112,13.149017],[-88.483302,13.163951],[-88.843228,13.259734],[-89.256743,13.458533],[-89.812394,13.520622],[-90.095555,13.735338],[-90.064678,13.88197],[-89.721934,14.134228],[-89.534219,14.244816],[-89.587343,14.362586],[-89.353326,14.424133],[-89.058512,14.340029],[-88.843073,14.140507],[-88.541231,13.980155],[-88.503998,13.845486],[-88.065343,13.964626],[-87.859515,13.893312],[-87.723503,13.78505],[-87.793111,13.38448]]]}}, {"type":"Feature","id":"-99","properties":{"name":"Somaliland"},"geometry":{"type":"Polygon","coordinates":[[[48.93813,9.451749],[48.486736,8.837626],[47.78942,8.003],[46.948328,7.996877],[43.67875,9.18358],[43.296975,9.540477],[42.92812,10.02194],[42.55876,10.57258],[42.776852,10.926879],[43.145305,11.46204],[43.47066,11.27771],[43.666668,10.864169],[44.117804,10.445538],[44.614259,10.442205],[45.556941,10.698029],[46.645401,10.816549],[47.525658,11.127228],[48.021596,11.193064],[48.378784,11.375482],[48.948206,11.410622],[48.942005,11.394266],[48.938491,10.982327],[48.938233,9.9735],[48.93813,9.451749]]]}}, {"type":"Feature","id":"SOM","properties":{"name":"Somalia"},"geometry":{"type":"Polygon","coordinates":[[[49.72862,11.5789],[50.25878,11.67957],[50.73202,12.0219],[51.1112,12.02464],[51.13387,11.74815],[51.04153,11.16651],[51.04531,10.6409],[50.83418,10.27972],[50.55239,9.19874],[50.07092,8.08173],[49.4527,6.80466],[48.59455,5.33911],[47.74079,4.2194],[46.56476,2.85529],[45.56399,2.04576],[44.06815,1.05283],[43.13597,0.2922],[42.04157,-0.91916],[41.81095,-1.44647],[41.58513,-1.68325],[40.993,-0.85829],[40.98105,2.78452],[41.855083,3.918912],[42.12861,4.23413],[42.76967,4.25259],[43.66087,4.95755],[44.9636,5.00162],[47.78942,8.003],[48.486736,8.837626],[48.93813,9.451749],[48.938233,9.9735],[48.938491,10.982327],[48.942005,11.394266],[48.948205,11.410617],[49.26776,11.43033],[49.72862,11.5789]]]}}, {"type":"Feature","id":"SRB","properties":{"name":"Republic of Serbia"},"geometry":{"type":"Polygon","coordinates":[[[20.874313,45.416375],[21.483526,45.18117],[21.562023,44.768947],[22.145088,44.478422],[22.459022,44.702517],[22.705726,44.578003],[22.474008,44.409228],[22.65715,44.234923],[22.410446,44.008063],[22.500157,43.642814],[22.986019,43.211161],[22.604801,42.898519],[22.436595,42.580321],[22.545012,42.461362],[22.380526,42.32026],[21.91708,42.30364],[21.576636,42.245224],[21.54332,42.32025],[21.66292,42.43922],[21.77505,42.6827],[21.63302,42.67717],[21.43866,42.86255],[21.27421,42.90959],[21.143395,43.068685],[20.95651,43.13094],[20.81448,43.27205],[20.63508,43.21671],[20.49679,42.88469],[20.25758,42.81275],[20.3398,42.89852],[19.95857,43.10604],[19.63,43.21378],[19.48389,43.35229],[19.21852,43.52384],[19.454,43.5681],[19.59976,44.03847],[19.11761,44.42307],[19.36803,44.863],[19.00548,44.86023],[19.390476,45.236516],[19.072769,45.521511],[18.82982,45.90888],[19.596045,46.17173],[20.220192,46.127469],[20.762175,45.734573],[20.874313,45.416375]]]}}, {"type":"Feature","id":"SUR","properties":{"name":"Suriname"},"geometry":{"type":"Polygon","coordinates":[[[-57.147436,5.97315],[-55.949318,5.772878],[-55.84178,5.953125],[-55.03325,6.025291],[-53.958045,5.756548],[-54.478633,4.896756],[-54.399542,4.212611],[-54.006931,3.620038],[-54.181726,3.18978],[-54.269705,2.732392],[-54.524754,2.311849],[-55.097587,2.523748],[-55.569755,2.421506],[-55.973322,2.510364],[-56.073342,2.220795],[-55.9056,2.021996],[-55.995698,1.817667],[-56.539386,1.899523],[-57.150098,2.768927],[-57.281433,3.333492],[-57.601569,3.334655],[-58.044694,4.060864],[-57.86021,4.576801],[-57.914289,4.812626],[-57.307246,5.073567],[-57.147436,5.97315]]]}}, {"type":"Feature","id":"SVK","properties":{"name":"Slovakia"},"geometry":{"type":"Polygon","coordinates":[[[18.853144,49.49623],[18.909575,49.435846],[19.320713,49.571574],[19.825023,49.217125],[20.415839,49.431453],[20.887955,49.328772],[21.607808,49.470107],[22.558138,49.085738],[22.280842,48.825392],[22.085608,48.422264],[21.872236,48.319971],[20.801294,48.623854],[20.473562,48.56285],[20.239054,48.327567],[19.769471,48.202691],[19.661364,48.266615],[19.174365,48.111379],[18.777025,48.081768],[18.696513,47.880954],[17.857133,47.758429],[17.488473,47.867466],[16.979667,48.123497],[16.879983,48.470013],[16.960288,48.596982],[17.101985,48.816969],[17.545007,48.800019],[17.886485,48.903475],[17.913512,48.996493],[18.104973,49.043983],[18.170498,49.271515],[18.399994,49.315001],[18.554971,49.495015],[18.853144,49.49623]]]}}, {"type":"Feature","id":"SVN","properties":{"name":"Slovenia"},"geometry":{"type":"Polygon","coordinates":[[[13.806475,46.509306],[14.632472,46.431817],[15.137092,46.658703],[16.011664,46.683611],[16.202298,46.852386],[16.370505,46.841327],[16.564808,46.503751],[15.768733,46.238108],[15.67153,45.834154],[15.323954,45.731783],[15.327675,45.452316],[14.935244,45.471695],[14.595109,45.634941],[14.411968,45.466166],[13.71506,45.500324],[13.93763,45.591016],[13.69811,46.016778],[13.806475,46.509306]]]}}, {"type":"Feature","id":"SWE","properties":{"name":"Sweden"},"geometry":{"type":"MultiPolygon","coordinates":[[[[22.183173,65.723741],[21.213517,65.026005],[21.369631,64.413588],[19.778876,63.609554],[17.847779,62.7494],[17.119555,61.341166],[17.831346,60.636583],[18.787722,60.081914],[17.869225,58.953766],[16.829185,58.719827],[16.44771,57.041118],[15.879786,56.104302],[14.666681,56.200885],[14.100721,55.407781],[12.942911,55.361737],[12.625101,56.30708],[11.787942,57.441817],[11.027369,58.856149],[11.468272,59.432393],[12.300366,60.117933],[12.631147,61.293572],[11.992064,61.800362],[11.930569,63.128318],[12.579935,64.066219],[13.571916,64.049114],[13.919905,64.445421],[13.55569,64.787028],[15.108411,66.193867],[16.108712,67.302456],[16.768879,68.013937],[17.729182,68.010552],[17.993868,68.567391],[19.87856,68.407194],[20.025269,69.065139],[20.645593,69.106247],[21.978535,68.616846],[23.539473,67.936009],[23.56588,66.396051],[23.903379,66.006927],[22.183173,65.723741]],[[17.061767,57.385783],[17.210083,57.326521],[16.430053,56.179196],[16.364135,56.556455],[17.061767,57.385783]],[[19.357910,57.958588],[18.803100,57.651279],[18.825073,57.444949],[18.995361,57.441993],[18.951416,57.370976],[18.693237,57.305756],[18.709716,57.204734],[18.462524,57.127295],[18.319702,56.926992],[18.105468,56.891003],[18.187866,57.109402],[18.072509,57.267163],[18.154907,57.394664],[18.094482,57.545312],[18.660278,57.929434],[19.039306,57.941098],[19.105224,57.993543],[19.374389,57.996454],[19.357910,57.958588]],[[20.846557,63.823710],[21.066284,63.829768],[20.972900,63.715670],[20.824584,63.579121],[20.695495,63.591340],[20.819091,63.714454],[20.799865,63.780059],[20.846557,63.823710]]]]}}, {"type":"Feature","id":"SWZ","properties":{"name":"Swaziland"},"geometry":{"type":"Polygon","coordinates":[[[32.071665,-26.73382],[31.86806,-27.177927],[31.282773,-27.285879],[30.685962,-26.743845],[30.676609,-26.398078],[30.949667,-26.022649],[31.04408,-25.731452],[31.333158,-25.660191],[31.837778,-25.843332],[31.985779,-26.29178],[32.071665,-26.73382]]]}}, {"type":"Feature","id":"SYR","properties":{"name":"Syria"},"geometry":{"type":"Polygon","coordinates":[[[38.792341,33.378686],[36.834062,32.312938],[35.719918,32.709192],[35.700798,32.716014],[35.836397,32.868123],[35.821101,33.277426],[36.06646,33.824912],[36.61175,34.201789],[36.448194,34.593935],[35.998403,34.644914],[35.905023,35.410009],[36.149763,35.821535],[36.41755,36.040617],[36.685389,36.259699],[36.739494,36.81752],[37.066761,36.623036],[38.167727,36.90121],[38.699891,36.712927],[39.52258,36.716054],[40.673259,37.091276],[41.212089,37.074352],[42.349591,37.229873],[41.837064,36.605854],[41.289707,36.358815],[41.383965,35.628317],[41.006159,34.419372],[38.792341,33.378686]]]}}, {"type":"Feature","id":"TCD","properties":{"name":"Chad"},"geometry":{"type":"Polygon","coordinates":[[[14.495787,12.859396],[14.595781,13.330427],[13.954477,13.353449],[13.956699,13.996691],[13.540394,14.367134],[13.97217,15.68437],[15.247731,16.627306],[15.300441,17.92795],[15.685741,19.95718],[15.903247,20.387619],[15.487148,20.730415],[15.47106,21.04845],[15.096888,21.308519],[14.8513,22.86295],[15.86085,23.40972],[19.84926,21.49509],[23.83766,19.58047],[23.88689,15.61084],[23.02459,15.68072],[22.56795,14.94429],[22.30351,14.32682],[22.51202,14.09318],[22.18329,13.78648],[22.29658,13.37232],[22.03759,12.95546],[21.93681,12.58818],[22.28801,12.64605],[22.49762,12.26024],[22.50869,11.67936],[22.87622,11.38461],[22.864165,11.142395],[22.231129,10.971889],[21.723822,10.567056],[21.000868,9.475985],[20.059685,9.012706],[19.094008,9.074847],[18.81201,8.982915],[18.911022,8.630895],[18.389555,8.281304],[17.96493,7.890914],[16.705988,7.508328],[16.456185,7.734774],[16.290562,7.754307],[16.106232,7.497088],[15.27946,7.421925],[15.436092,7.692812],[15.120866,8.38215],[14.979996,8.796104],[14.544467,8.965861],[13.954218,9.549495],[14.171466,10.021378],[14.627201,9.920919],[14.909354,9.992129],[15.467873,9.982337],[14.923565,10.891325],[14.960152,11.555574],[14.89336,12.21905],[14.495787,12.859396]]]}}, {"type":"Feature","id":"TGO","properties":{"name":"Togo"},"geometry":{"type":"Polygon","coordinates":[[[1.865241,6.142158],[1.060122,5.928837],[0.836931,6.279979],[0.570384,6.914359],[0.490957,7.411744],[0.712029,8.312465],[0.461192,8.677223],[0.365901,9.465004],[0.36758,10.191213],[-0.049785,10.706918],[0.023803,11.018682],[0.899563,10.997339],[0.772336,10.470808],[1.077795,10.175607],[1.425061,9.825395],[1.463043,9.334624],[1.664478,9.12859],[1.618951,6.832038],[1.865241,6.142158]]]}}, {"type":"Feature","id":"THA","properties":{"name":"Thailand"},"geometry":{"type":"Polygon","coordinates":[[[102.584932,12.186595],[101.687158,12.64574],[100.83181,12.627085],[100.978467,13.412722],[100.097797,13.406856],[100.018733,12.307001],[99.478921,10.846367],[99.153772,9.963061],[99.222399,9.239255],[99.873832,9.207862],[100.279647,8.295153],[100.459274,7.429573],[101.017328,6.856869],[101.623079,6.740622],[102.141187,6.221636],[101.814282,5.810808],[101.154219,5.691384],[101.075516,6.204867],[100.259596,6.642825],[100.085757,6.464489],[99.690691,6.848213],[99.519642,7.343454],[98.988253,7.907993],[98.503786,8.382305],[98.339662,7.794512],[98.150009,8.350007],[98.25915,8.973923],[98.553551,9.93296],[99.038121,10.960546],[99.587286,11.892763],[99.196354,12.804748],[99.212012,13.269294],[99.097755,13.827503],[98.430819,14.622028],[98.192074,15.123703],[98.537376,15.308497],[98.903348,16.177824],[98.493761,16.837836],[97.859123,17.567946],[97.375896,18.445438],[97.797783,18.62708],[98.253724,19.708203],[98.959676,19.752981],[99.543309,20.186598],[100.115988,20.41785],[100.548881,20.109238],[100.606294,19.508344],[101.282015,19.462585],[101.035931,18.408928],[101.059548,17.512497],[102.113592,18.109102],[102.413005,17.932782],[102.998706,17.961695],[103.200192,18.309632],[103.956477,18.240954],[104.716947,17.428859],[104.779321,16.441865],[105.589039,15.570316],[105.544338,14.723934],[105.218777,14.273212],[104.281418,14.416743],[102.988422,14.225721],[102.348099,13.394247],[102.584932,12.186595]]]}}, {"type":"Feature","id":"TJK","properties":{"name":"Tajikistan"},"geometry":{"type":"Polygon","coordinates":[[[71.014198,40.244366],[70.648019,39.935754],[69.55961,40.103211],[69.464887,39.526683],[70.549162,39.604198],[71.784694,39.279463],[73.675379,39.431237],[73.928852,38.505815],[74.257514,38.606507],[74.864816,38.378846],[74.829986,37.990007],[74.980002,37.41999],[73.948696,37.421566],[73.260056,37.495257],[72.63689,37.047558],[72.193041,36.948288],[71.844638,36.738171],[71.448693,37.065645],[71.541918,37.905774],[71.239404,37.953265],[71.348131,38.258905],[70.806821,38.486282],[70.376304,38.138396],[70.270574,37.735165],[70.116578,37.588223],[69.518785,37.608997],[69.196273,37.151144],[68.859446,37.344336],[68.135562,37.023115],[67.83,37.144994],[68.392033,38.157025],[68.176025,38.901553],[67.44222,39.140144],[67.701429,39.580478],[68.536416,39.533453],[69.011633,40.086158],[69.329495,40.727824],[70.666622,40.960213],[70.45816,40.496495],[70.601407,40.218527],[71.014198,40.244366]]]}}, {"type":"Feature","id":"TKM","properties":{"name":"Turkmenistan"},"geometry":{"type":"Polygon","coordinates":[[[61.210817,35.650072],[61.123071,36.491597],[60.377638,36.527383],[59.234762,37.412988],[58.436154,37.522309],[57.330434,38.029229],[56.619366,38.121394],[56.180375,37.935127],[55.511578,37.964117],[54.800304,37.392421],[53.921598,37.198918],[53.735511,37.906136],[53.880929,38.952093],[53.101028,39.290574],[53.357808,39.975286],[52.693973,40.033629],[52.915251,40.876523],[53.858139,40.631034],[54.736845,40.951015],[54.008311,41.551211],[53.721713,42.123191],[52.91675,41.868117],[52.814689,41.135371],[52.50246,41.783316],[52.944293,42.116034],[54.079418,42.324109],[54.755345,42.043971],[55.455251,41.259859],[55.968191,41.308642],[57.096391,41.32231],[56.932215,41.826026],[57.78653,42.170553],[58.629011,42.751551],[59.976422,42.223082],[60.083341,41.425146],[60.465953,41.220327],[61.547179,41.26637],[61.882714,41.084857],[62.37426,40.053886],[63.518015,39.363257],[64.170223,38.892407],[65.215999,38.402695],[66.54615,37.974685],[66.518607,37.362784],[66.217385,37.39379],[65.745631,37.661164],[65.588948,37.305217],[64.746105,37.111818],[64.546479,36.312073],[63.982896,36.007957],[63.193538,35.857166],[62.984662,35.404041],[62.230651,35.270664],[61.210817,35.650072]]]}}, {"type":"Feature","id":"TLS","properties":{"name":"East Timor"},"geometry":{"type":"Polygon","coordinates":[[[124.968682,-8.89279],[125.086246,-8.656887],[125.947072,-8.432095],[126.644704,-8.398247],[126.957243,-8.273345],[127.335928,-8.397317],[126.967992,-8.668256],[125.925885,-9.106007],[125.08852,-9.393173],[125.07002,-9.089987],[124.968682,-8.89279]]]}}, {"type":"Feature","id":"TTO","properties":{"name":"Trinidad and Tobago"},"geometry":{"type":"Polygon","coordinates":[[[-61.68,10.76],[-61.105,10.89],[-60.895,10.855],[-60.935,10.11],[-61.77,10],[-61.95,10.09],[-61.66,10.365],[-61.68,10.76]]]}}, {"type":"Feature","id":"TUN","properties":{"name":"Tunisia"},"geometry":{"type":"Polygon","coordinates":[[[9.48214,30.307556],[9.055603,32.102692],[8.439103,32.506285],[8.430473,32.748337],[7.612642,33.344115],[7.524482,34.097376],[8.140981,34.655146],[8.376368,35.479876],[8.217824,36.433177],[8.420964,36.946427],[9.509994,37.349994],[10.210002,37.230002],[10.18065,36.724038],[11.028867,37.092103],[11.100026,36.899996],[10.600005,36.41],[10.593287,35.947444],[10.939519,35.698984],[10.807847,34.833507],[10.149593,34.330773],[10.339659,33.785742],[10.856836,33.76874],[11.108501,33.293343],[11.488787,33.136996],[11.432253,32.368903],[10.94479,32.081815],[10.636901,31.761421],[9.950225,31.37607],[10.056575,30.961831],[9.970017,30.539325],[9.48214,30.307556]]]}}, {"type":"Feature","id":"TUR","properties":{"name":"Turkey"},"geometry":{"type":"MultiPolygon","coordinates":[[[[36.913127,41.335358],[38.347665,40.948586],[39.512607,41.102763],[40.373433,41.013673],[41.554084,41.535656],[42.619549,41.583173],[43.582746,41.092143],[43.752658,40.740201],[43.656436,40.253564],[44.400009,40.005],[44.79399,39.713003],[44.109225,39.428136],[44.421403,38.281281],[44.225756,37.971584],[44.772699,37.170445],[44.293452,37.001514],[43.942259,37.256228],[42.779126,37.385264],[42.349591,37.229873],[41.212089,37.074352],[40.673259,37.091276],[39.52258,36.716054],[38.699891,36.712927],[38.167727,36.90121],[37.066761,36.623036],[36.739494,36.81752],[36.685389,36.259699],[36.41755,36.040617],[36.149763,35.821535],[35.782085,36.274995],[36.160822,36.650606],[35.550936,36.565443],[34.714553,36.795532],[34.026895,36.21996],[32.509158,36.107564],[31.699595,36.644275],[30.621625,36.677865],[30.391096,36.262981],[29.699976,36.144357],[28.732903,36.676831],[27.641187,36.658822],[27.048768,37.653361],[26.318218,38.208133],[26.8047,38.98576],[26.170785,39.463612],[27.28002,40.420014],[28.819978,40.460011],[29.240004,41.219991],[31.145934,41.087622],[32.347979,41.736264],[33.513283,42.01896],[35.167704,42.040225],[36.913127,41.335358]]],[[[27.192377,40.690566],[26.358009,40.151994],[26.043351,40.617754],[26.056942,40.824123],[26.294602,40.936261],[26.604196,41.562115],[26.117042,41.826905],[27.135739,42.141485],[27.99672,42.007359],[28.115525,41.622886],[28.988443,41.299934],[28.806438,41.054962],[27.619017,40.999823],[27.192377,40.690566]]]]}}, {"type":"Feature","id":"TWN","properties":{"name":"Taiwan"},"geometry":{"type":"Polygon","coordinates":[[[121.777818,24.394274],[121.175632,22.790857],[120.74708,21.970571],[120.220083,22.814861],[120.106189,23.556263],[120.69468,24.538451],[121.495044,25.295459],[121.951244,24.997596],[121.777818,24.394274]]]}}, {"type":"Feature","id":"TZA","properties":{"name":"United Republic of Tanzania"},"geometry":{"type":"Polygon","coordinates":[[[33.903711,-0.95],[34.07262,-1.05982],[37.69869,-3.09699],[37.7669,-3.67712],[39.20222,-4.67677],[38.74054,-5.90895],[38.79977,-6.47566],[39.44,-6.84],[39.47,-7.1],[39.19469,-7.7039],[39.25203,-8.00781],[39.18652,-8.48551],[39.53574,-9.11237],[39.9496,-10.0984],[40.31659,-10.3171],[39.521,-10.89688],[38.427557,-11.285202],[37.82764,-11.26879],[37.47129,-11.56876],[36.775151,-11.594537],[36.514082,-11.720938],[35.312398,-11.439146],[34.559989,-11.52002],[34.28,-10.16],[33.940838,-9.693674],[33.73972,-9.41715],[32.759375,-9.230599],[32.191865,-8.930359],[31.556348,-8.762049],[31.157751,-8.594579],[30.74,-8.34],[30.2,-7.08],[29.62,-6.52],[29.419993,-5.939999],[29.519987,-5.419979],[29.339998,-4.499983],[29.753512,-4.452389],[30.11632,-4.09012],[30.50554,-3.56858],[30.75224,-3.35931],[30.74301,-3.03431],[30.52766,-2.80762],[30.46967,-2.41383],[30.758309,-2.28725],[30.816135,-1.698914],[30.419105,-1.134659],[30.76986,-1.01455],[31.86617,-1.02736],[33.903711,-0.95]]]}}, {"type":"Feature","id":"UGA","properties":{"name":"Uganda"},"geometry":{"type":"Polygon","coordinates":[[[31.86617,-1.02736],[30.76986,-1.01455],[30.419105,-1.134659],[29.821519,-1.443322],[29.579466,-1.341313],[29.587838,-0.587406],[29.8195,-0.2053],[29.875779,0.59738],[30.086154,1.062313],[30.468508,1.583805],[30.85267,1.849396],[31.174149,2.204465],[30.77332,2.33989],[30.83385,3.50917],[31.24556,3.7819],[31.88145,3.55827],[32.68642,3.79232],[33.39,3.79],[34.005,4.249885],[34.47913,3.5556],[34.59607,3.05374],[35.03599,1.90584],[34.6721,1.17694],[34.18,0.515],[33.893569,0.109814],[33.903711,-0.95],[31.86617,-1.02736]]]}}, {"type":"Feature","id":"UKR","properties":{"name":"Ukraine"},"geometry":{"type":"Polygon","coordinates":[[[31.785998,52.101678],[32.159412,52.061267],[32.412058,52.288695],[32.715761,52.238465],[33.7527,52.335075],[34.391731,51.768882],[34.141978,51.566413],[34.224816,51.255993],[35.022183,51.207572],[35.377924,50.773955],[35.356116,50.577197],[36.626168,50.225591],[37.39346,50.383953],[38.010631,49.915662],[38.594988,49.926462],[40.069058,49.601055],[40.080789,49.30743],[39.674664,48.783818],[39.895632,48.232405],[39.738278,47.898937],[38.770585,47.825608],[38.255112,47.5464],[38.223538,47.10219],[37.425137,47.022221],[36.759855,46.6987],[35.823685,46.645964],[34.962342,46.273197],[35.020788,45.651219],[35.510009,45.409993],[36.529998,45.46999],[36.334713,45.113216],[35.239999,44.939996],[33.882511,44.361479],[33.326421,44.564877],[33.546924,45.034771],[32.454174,45.327466],[32.630804,45.519186],[33.588162,45.851569],[33.298567,46.080598],[31.74414,46.333348],[31.675307,46.706245],[30.748749,46.5831],[30.377609,46.03241],[29.603289,45.293308],[29.149725,45.464925],[28.679779,45.304031],[28.233554,45.488283],[28.485269,45.596907],[28.659987,45.939987],[28.933717,46.25883],[28.862972,46.437889],[29.072107,46.517678],[29.170654,46.379262],[29.759972,46.349988],[30.024659,46.423937],[29.83821,46.525326],[29.908852,46.674361],[29.559674,46.928583],[29.415135,47.346645],[29.050868,47.510227],[29.122698,47.849095],[28.670891,48.118149],[28.259547,48.155562],[27.522537,48.467119],[26.857824,48.368211],[26.619337,48.220726],[26.19745,48.220881],[25.945941,47.987149],[25.207743,47.891056],[24.866317,47.737526],[24.402056,47.981878],[23.760958,47.985598],[23.142236,48.096341],[22.710531,47.882194],[22.64082,48.15024],[22.085608,48.422264],[22.280842,48.825392],[22.558138,49.085738],[22.776419,49.027395],[22.51845,49.476774],[23.426508,50.308506],[23.922757,50.424881],[24.029986,50.705407],[23.527071,51.578454],[24.005078,51.617444],[24.553106,51.888461],[25.327788,51.910656],[26.337959,51.832289],[27.454066,51.592303],[28.241615,51.572227],[28.617613,51.427714],[28.992835,51.602044],[29.254938,51.368234],[30.157364,51.416138],[30.555117,51.319503],[30.619454,51.822806],[30.927549,52.042353],[31.785998,52.101678]]]}}, {"type":"Feature","id":"URY","properties":{"name":"Uruguay"},"geometry":{"type":"Polygon","coordinates":[[[-57.625133,-30.216295],[-56.976026,-30.109686],[-55.973245,-30.883076],[-55.60151,-30.853879],[-54.572452,-31.494511],[-53.787952,-32.047243],[-53.209589,-32.727666],[-53.650544,-33.202004],[-53.373662,-33.768378],[-53.806426,-34.396815],[-54.935866,-34.952647],[-55.67409,-34.752659],[-56.215297,-34.859836],[-57.139685,-34.430456],[-57.817861,-34.462547],[-58.427074,-33.909454],[-58.349611,-33.263189],[-58.132648,-33.040567],[-58.14244,-32.044504],[-57.874937,-31.016556],[-57.625133,-30.216295]]]}}, {"type":"Feature","id":"USA","properties":{"name":"United States of America"},"geometry":{"type":"MultiPolygon","coordinates":[[[[-155.54211,19.08348],[-155.68817,18.91619],[-155.93665,19.05939],[-155.90806,19.33888],[-156.07347,19.70294],[-156.02368,19.81422],[-155.85008,19.97729],[-155.91907,20.17395],[-155.86108,20.26721],[-155.78505,20.2487],[-155.40214,20.07975],[-155.22452,19.99302],[-155.06226,19.8591],[-154.80741,19.50871],[-154.83147,19.45328],[-155.22217,19.23972],[-155.54211,19.08348]]],[[[-156.07926,20.64397],[-156.41445,20.57241],[-156.58673,20.783],[-156.70167,20.8643],[-156.71055,20.92676],[-156.61258,21.01249],[-156.25711,20.91745],[-155.99566,20.76404],[-156.07926,20.64397]]],[[[-156.75824,21.17684],[-156.78933,21.06873],[-157.32521,21.09777],[-157.25027,21.21958],[-156.75824,21.17684]]],[[[-157.65283,21.32217],[-157.70703,21.26442],[-157.7786,21.27729],[-158.12667,21.31244],[-158.2538,21.53919],[-158.29265,21.57912],[-158.0252,21.71696],[-157.94161,21.65272],[-157.65283,21.32217]]],[[[-159.34512,21.982],[-159.46372,21.88299],[-159.80051,22.06533],[-159.74877,22.1382],[-159.5962,22.23618],[-159.36569,22.21494],[-159.34512,21.982]]],[[[-94.81758,49.38905],[-94.64,48.84],[-94.32914,48.67074],[-93.63087,48.60926],[-92.61,48.45],[-91.64,48.14],[-90.83,48.27],[-89.6,48.01],[-89.272917,48.019808],[-88.378114,48.302918],[-87.439793,47.94],[-86.461991,47.553338],[-85.652363,47.220219],[-84.87608,46.900083],[-84.779238,46.637102],[-84.543749,46.538684],[-84.6049,46.4396],[-84.3367,46.40877],[-84.14212,46.512226],[-84.091851,46.275419],[-83.890765,46.116927],[-83.616131,46.116927],[-83.469551,45.994686],[-83.592851,45.816894],[-82.550925,45.347517],[-82.337763,44.44],[-82.137642,43.571088],[-82.43,42.98],[-82.9,42.43],[-83.12,42.08],[-83.142,41.975681],[-83.02981,41.832796],[-82.690089,41.675105],[-82.439278,41.675105],[-81.277747,42.209026],[-80.247448,42.3662],[-78.939362,42.863611],[-78.92,42.965],[-79.01,43.27],[-79.171674,43.466339],[-78.72028,43.625089],[-77.737885,43.629056],[-76.820034,43.628784],[-76.5,44.018459],[-76.375,44.09631],[-75.31821,44.81645],[-74.867,45.00048],[-73.34783,45.00738],[-71.50506,45.0082],[-71.405,45.255],[-71.08482,45.30524],[-70.66,45.46],[-70.305,45.915],[-69.99997,46.69307],[-69.237216,47.447781],[-68.905,47.185],[-68.23444,47.35486],[-67.79046,47.06636],[-67.79134,45.70281],[-67.13741,45.13753],[-66.96466,44.8097],[-68.03252,44.3252],[-69.06,43.98],[-70.11617,43.68405],[-70.645476,43.090238],[-70.81489,42.8653],[-70.825,42.335],[-70.495,41.805],[-70.08,41.78],[-70.185,42.145],[-69.88497,41.92283],[-69.96503,41.63717],[-70.64,41.475],[-71.12039,41.49445],[-71.86,41.32],[-72.295,41.27],[-72.87643,41.22065],[-73.71,40.931102],[-72.24126,41.11948],[-71.945,40.93],[-73.345,40.63],[-73.982,40.628],[-73.952325,40.75075],[-74.25671,40.47351],[-73.96244,40.42763],[-74.17838,39.70926],[-74.90604,38.93954],[-74.98041,39.1964],[-75.20002,39.24845],[-75.52805,39.4985],[-75.32,38.96],[-75.071835,38.782032],[-75.05673,38.40412],[-75.37747,38.01551],[-75.94023,37.21689],[-76.03127,37.2566],[-75.72205,37.93705],[-76.23287,38.319215],[-76.35,39.15],[-76.542725,38.717615],[-76.32933,38.08326],[-76.989998,38.239992],[-76.30162,37.917945],[-76.25874,36.9664],[-75.9718,36.89726],[-75.86804,36.55125],[-75.72749,35.55074],[-76.36318,34.80854],[-77.397635,34.51201],[-78.05496,33.92547],[-78.55435,33.86133],[-79.06067,33.49395],[-79.20357,33.15839],[-80.301325,32.509355],[-80.86498,32.0333],[-81.33629,31.44049],[-81.49042,30.72999],[-81.31371,30.03552],[-80.98,29.18],[-80.535585,28.47213],[-80.53,28.04],[-80.056539,26.88],[-80.088015,26.205765],[-80.13156,25.816775],[-80.38103,25.20616],[-80.68,25.08],[-81.17213,25.20126],[-81.33,25.64],[-81.71,25.87],[-82.24,26.73],[-82.70515,27.49504],[-82.85526,27.88624],[-82.65,28.55],[-82.93,29.1],[-83.70959,29.93656],[-84.1,30.09],[-85.10882,29.63615],[-85.28784,29.68612],[-85.7731,30.15261],[-86.4,30.4],[-87.53036,30.27433],[-88.41782,30.3849],[-89.18049,30.31598],[-89.593831,30.159994],[-89.413735,29.89419],[-89.43,29.48864],[-89.21767,29.29108],[-89.40823,29.15961],[-89.77928,29.30714],[-90.15463,29.11743],[-90.880225,29.148535],[-91.626785,29.677],[-92.49906,29.5523],[-93.22637,29.78375],[-93.84842,29.71363],[-94.69,29.48],[-95.60026,28.73863],[-96.59404,28.30748],[-97.14,27.83],[-97.37,27.38],[-97.38,26.69],[-97.33,26.21],[-97.14,25.87],[-97.53,25.84],[-98.24,26.06],[-99.02,26.37],[-99.3,26.84],[-99.52,27.54],[-100.11,28.11],[-100.45584,28.69612],[-100.9576,29.38071],[-101.6624,29.7793],[-102.48,29.76],[-103.11,28.97],[-103.94,29.27],[-104.45697,29.57196],[-104.70575,30.12173],[-105.03737,30.64402],[-105.63159,31.08383],[-106.1429,31.39995],[-106.50759,31.75452],[-108.24,31.754854],[-108.24194,31.34222],[-109.035,31.34194],[-111.02361,31.33472],[-113.30498,32.03914],[-114.815,32.52528],[-114.72139,32.72083],[-115.99135,32.61239],[-117.12776,32.53534],[-117.295938,33.046225],[-117.944,33.621236],[-118.410602,33.740909],[-118.519895,34.027782],[-119.081,34.078],[-119.438841,34.348477],[-120.36778,34.44711],[-120.62286,34.60855],[-120.74433,35.15686],[-121.71457,36.16153],[-122.54747,37.55176],[-122.51201,37.78339],[-122.95319,38.11371],[-123.7272,38.95166],[-123.86517,39.76699],[-124.39807,40.3132],[-124.17886,41.14202],[-124.2137,41.99964],[-124.53284,42.76599],[-124.14214,43.70838],[-124.020535,44.615895],[-123.89893,45.52341],[-124.079635,46.86475],[-124.39567,47.72017],[-124.68721,48.184433],[-124.566101,48.379715],[-123.12,48.04],[-122.58736,47.096],[-122.34,47.36],[-122.5,48.18],[-122.84,49],[-120,49],[-117.03121,49],[-116.04818,49],[-113,49],[-110.05,49],[-107.05,49],[-104.04826,48.99986],[-100.65,49],[-97.22872,49.0007],[-95.15907,49],[-95.15609,49.38425],[-94.81758,49.38905]]],[[[-153.006314,57.115842],[-154.00509,56.734677],[-154.516403,56.992749],[-154.670993,57.461196],[-153.76278,57.816575],[-153.228729,57.968968],[-152.564791,57.901427],[-152.141147,57.591059],[-153.006314,57.115842]]],[[[-165.579164,59.909987],[-166.19277,59.754441],[-166.848337,59.941406],[-167.455277,60.213069],[-166.467792,60.38417],[-165.67443,60.293607],[-165.579164,59.909987]]],[[[-171.731657,63.782515],[-171.114434,63.592191],[-170.491112,63.694975],[-169.682505,63.431116],[-168.689439,63.297506],[-168.771941,63.188598],[-169.52944,62.976931],[-170.290556,63.194438],[-170.671386,63.375822],[-171.553063,63.317789],[-171.791111,63.405846],[-171.731657,63.782515]]],[[[-155.06779,71.147776],[-154.344165,70.696409],[-153.900006,70.889989],[-152.210006,70.829992],[-152.270002,70.600006],[-150.739992,70.430017],[-149.720003,70.53001],[-147.613362,70.214035],[-145.68999,70.12001],[-144.920011,69.989992],[-143.589446,70.152514],[-142.07251,69.851938],[-140.985988,69.711998],[-140.992499,66.000029],[-140.99777,60.306397],[-140.012998,60.276838],[-139.039,60.000007],[-138.34089,59.56211],[-137.4525,58.905],[-136.47972,59.46389],[-135.47583,59.78778],[-134.945,59.27056],[-134.27111,58.86111],[-133.355549,58.410285],[-132.73042,57.69289],[-131.70781,56.55212],[-130.00778,55.91583],[-129.979994,55.284998],[-130.53611,54.802753],[-131.085818,55.178906],[-131.967211,55.497776],[-132.250011,56.369996],[-133.539181,57.178887],[-134.078063,58.123068],[-135.038211,58.187715],[-136.628062,58.212209],[-137.800006,58.499995],[-139.867787,59.537762],[-140.825274,59.727517],[-142.574444,60.084447],[-143.958881,59.99918],[-145.925557,60.45861],[-147.114374,60.884656],[-148.224306,60.672989],[-148.018066,59.978329],[-148.570823,59.914173],[-149.727858,59.705658],[-150.608243,59.368211],[-151.716393,59.155821],[-151.859433,59.744984],[-151.409719,60.725803],[-150.346941,61.033588],[-150.621111,61.284425],[-151.895839,60.727198],[-152.57833,60.061657],[-154.019172,59.350279],[-153.287511,58.864728],[-154.232492,58.146374],[-155.307491,57.727795],[-156.308335,57.422774],[-156.556097,56.979985],[-158.117217,56.463608],[-158.433321,55.994154],[-159.603327,55.566686],[-160.28972,55.643581],[-161.223048,55.364735],[-162.237766,55.024187],[-163.069447,54.689737],[-164.785569,54.404173],[-164.942226,54.572225],[-163.84834,55.039431],[-162.870001,55.348043],[-161.804175,55.894986],[-160.563605,56.008055],[-160.07056,56.418055],[-158.684443,57.016675],[-158.461097,57.216921],[-157.72277,57.570001],[-157.550274,58.328326],[-157.041675,58.918885],[-158.194731,58.615802],[-158.517218,58.787781],[-159.058606,58.424186],[-159.711667,58.93139],[-159.981289,58.572549],[-160.355271,59.071123],[-161.355003,58.670838],[-161.968894,58.671665],[-162.054987,59.266925],[-161.874171,59.633621],[-162.518059,59.989724],[-163.818341,59.798056],[-164.662218,60.267484],[-165.346388,60.507496],[-165.350832,61.073895],[-166.121379,61.500019],[-165.734452,62.074997],[-164.919179,62.633076],[-164.562508,63.146378],[-163.753332,63.219449],[-163.067224,63.059459],[-162.260555,63.541936],[-161.53445,63.455817],[-160.772507,63.766108],[-160.958335,64.222799],[-161.518068,64.402788],[-160.777778,64.788604],[-161.391926,64.777235],[-162.45305,64.559445],[-162.757786,64.338605],[-163.546394,64.55916],[-164.96083,64.446945],[-166.425288,64.686672],[-166.845004,65.088896],[-168.11056,65.669997],[-166.705271,66.088318],[-164.47471,66.57666],[-163.652512,66.57666],[-163.788602,66.077207],[-161.677774,66.11612],[-162.489715,66.735565],[-163.719717,67.116395],[-164.430991,67.616338],[-165.390287,68.042772],[-166.764441,68.358877],[-166.204707,68.883031],[-164.430811,68.915535],[-163.168614,69.371115],[-162.930566,69.858062],[-161.908897,70.33333],[-160.934797,70.44769],[-159.039176,70.891642],[-158.119723,70.824721],[-156.580825,71.357764],[-155.06779,71.147776]]]]}}, {"type":"Feature","id":"UZB","properties":{"name":"Uzbekistan"},"geometry":{"type":"Polygon","coordinates":[[[66.518607,37.362784],[66.54615,37.974685],[65.215999,38.402695],[64.170223,38.892407],[63.518015,39.363257],[62.37426,40.053886],[61.882714,41.084857],[61.547179,41.26637],[60.465953,41.220327],[60.083341,41.425146],[59.976422,42.223082],[58.629011,42.751551],[57.78653,42.170553],[56.932215,41.826026],[57.096391,41.32231],[55.968191,41.308642],[55.928917,44.995858],[58.503127,45.586804],[58.689989,45.500014],[60.239972,44.784037],[61.05832,44.405817],[62.0133,43.504477],[63.185787,43.650075],[64.900824,43.728081],[66.098012,42.99766],[66.023392,41.994646],[66.510649,41.987644],[66.714047,41.168444],[67.985856,41.135991],[68.259896,40.662325],[68.632483,40.668681],[69.070027,41.384244],[70.388965,42.081308],[70.962315,42.266154],[71.259248,42.167711],[70.420022,41.519998],[71.157859,41.143587],[71.870115,41.3929],[73.055417,40.866033],[71.774875,40.145844],[71.014198,40.244366],[70.601407,40.218527],[70.45816,40.496495],[70.666622,40.960213],[69.329495,40.727824],[69.011633,40.086158],[68.536416,39.533453],[67.701429,39.580478],[67.44222,39.140144],[68.176025,38.901553],[68.392033,38.157025],[67.83,37.144994],[67.075782,37.356144],[66.518607,37.362784]]]}}, {"type":"Feature","id":"VEN","properties":{"name":"Venezuela"},"geometry":{"type":"Polygon","coordinates":[[[-71.331584,11.776284],[-71.360006,11.539994],[-71.94705,11.423282],[-71.620868,10.96946],[-71.633064,10.446494],[-72.074174,9.865651],[-71.695644,9.072263],[-71.264559,9.137195],[-71.039999,9.859993],[-71.350084,10.211935],[-71.400623,10.968969],[-70.155299,11.375482],[-70.293843,11.846822],[-69.943245,12.162307],[-69.5843,11.459611],[-68.882999,11.443385],[-68.233271,10.885744],[-68.194127,10.554653],[-67.296249,10.545868],[-66.227864,10.648627],[-65.655238,10.200799],[-64.890452,10.077215],[-64.329479,10.389599],[-64.318007,10.641418],[-63.079322,10.701724],[-61.880946,10.715625],[-62.730119,10.420269],[-62.388512,9.948204],[-61.588767,9.873067],[-60.830597,9.38134],[-60.671252,8.580174],[-60.150096,8.602757],[-59.758285,8.367035],[-60.550588,7.779603],[-60.637973,7.415],[-60.295668,7.043911],[-60.543999,6.856584],[-61.159336,6.696077],[-61.139415,6.234297],[-61.410303,5.959068],[-60.733574,5.200277],[-60.601179,4.918098],[-60.966893,4.536468],[-62.08543,4.162124],[-62.804533,4.006965],[-63.093198,3.770571],[-63.888343,4.02053],[-64.628659,4.148481],[-64.816064,4.056445],[-64.368494,3.79721],[-64.408828,3.126786],[-64.269999,2.497006],[-63.422867,2.411068],[-63.368788,2.2009],[-64.083085,1.916369],[-64.199306,1.492855],[-64.611012,1.328731],[-65.354713,1.095282],[-65.548267,0.789254],[-66.325765,0.724452],[-66.876326,1.253361],[-67.181294,2.250638],[-67.447092,2.600281],[-67.809938,2.820655],[-67.303173,3.318454],[-67.337564,3.542342],[-67.621836,3.839482],[-67.823012,4.503937],[-67.744697,5.221129],[-67.521532,5.55687],[-67.34144,6.095468],[-67.695087,6.267318],[-68.265052,6.153268],[-68.985319,6.206805],[-69.38948,6.099861],[-70.093313,6.960376],[-70.674234,7.087785],[-71.960176,6.991615],[-72.198352,7.340431],[-72.444487,7.423785],[-72.479679,7.632506],[-72.360901,8.002638],[-72.439862,8.405275],[-72.660495,8.625288],[-72.78873,9.085027],[-73.304952,9.152],[-73.027604,9.73677],[-72.905286,10.450344],[-72.614658,10.821975],[-72.227575,11.108702],[-71.973922,11.608672],[-71.331584,11.776284]]]}}, {"type":"Feature","id":"VNM","properties":{"name":"Vietnam"},"geometry":{"type":"Polygon","coordinates":[[[108.05018,21.55238],[106.715068,20.696851],[105.881682,19.75205],[105.662006,19.058165],[106.426817,18.004121],[107.361954,16.697457],[108.269495,16.079742],[108.877107,15.276691],[109.33527,13.426028],[109.200136,11.666859],[108.36613,11.008321],[107.220929,10.364484],[106.405113,9.53084],[105.158264,8.59976],[104.795185,9.241038],[105.076202,9.918491],[104.334335,10.486544],[105.199915,10.88931],[106.24967,10.961812],[105.810524,11.567615],[107.491403,12.337206],[107.614548,13.535531],[107.382727,14.202441],[107.564525,15.202173],[107.312706,15.908538],[106.556008,16.604284],[105.925762,17.485315],[105.094598,18.666975],[103.896532,19.265181],[104.183388,19.624668],[104.822574,19.886642],[104.435,20.758733],[103.203861,20.766562],[102.754896,21.675137],[102.170436,22.464753],[102.706992,22.708795],[103.504515,22.703757],[104.476858,22.81915],[105.329209,23.352063],[105.811247,22.976892],[106.725403,22.794268],[106.567273,22.218205],[107.04342,21.811899],[108.05018,21.55238]]]}}, {"type":"Feature","id":"VUT","properties":{"name":"Vanuatu"},"geometry":{"type":"MultiPolygon","coordinates":[[[[167.844877,-16.466333],[167.515181,-16.59785],[167.180008,-16.159995],[167.216801,-15.891846],[167.844877,-16.466333]]],[[[167.107712,-14.93392],[167.270028,-15.740021],[167.001207,-15.614602],[166.793158,-15.668811],[166.649859,-15.392704],[166.629137,-14.626497],[167.107712,-14.93392]]]]}}, {"type":"Feature","id":"PSE","properties":{"name":"West Bank"},"geometry":{"type":"Polygon","coordinates":[[[35.545665,32.393992],[35.545252,31.782505],[35.397561,31.489086],[34.927408,31.353435],[34.970507,31.616778],[35.225892,31.754341],[34.974641,31.866582],[35.18393,32.532511],[35.545665,32.393992]]]}}, {"type":"Feature","id":"YEM","properties":{"name":"Yemen"},"geometry":{"type":"Polygon","coordinates":[[[53.108573,16.651051],[52.385206,16.382411],[52.191729,15.938433],[52.168165,15.59742],[51.172515,15.17525],[49.574576,14.708767],[48.679231,14.003202],[48.238947,13.94809],[47.938914,14.007233],[47.354454,13.59222],[46.717076,13.399699],[45.877593,13.347764],[45.62505,13.290946],[45.406459,13.026905],[45.144356,12.953938],[44.989533,12.699587],[44.494576,12.721653],[44.175113,12.58595],[43.482959,12.6368],[43.222871,13.22095],[43.251448,13.767584],[43.087944,14.06263],[42.892245,14.802249],[42.604873,15.213335],[42.805015,15.261963],[42.702438,15.718886],[42.823671,15.911742],[42.779332,16.347891],[43.218375,16.66689],[43.115798,17.08844],[43.380794,17.579987],[43.791519,17.319977],[44.062613,17.410359],[45.216651,17.433329],[45.399999,17.333335],[46.366659,17.233315],[46.749994,17.283338],[47.000005,16.949999],[47.466695,17.116682],[48.183344,18.166669],[49.116672,18.616668],[52.00001,19.000003],[52.782184,17.349742],[53.108573,16.651051]]]}}, {"type":"Feature","id":"ZAF","properties":{"name":"South Africa"},"geometry":{"type":"Polygon","coordinates":[[[31.521001,-29.257387],[31.325561,-29.401978],[30.901763,-29.909957],[30.622813,-30.423776],[30.055716,-31.140269],[28.925553,-32.172041],[28.219756,-32.771953],[27.464608,-33.226964],[26.419452,-33.61495],[25.909664,-33.66704],[25.780628,-33.944646],[25.172862,-33.796851],[24.677853,-33.987176],[23.594043,-33.794474],[22.988189,-33.916431],[22.574157,-33.864083],[21.542799,-34.258839],[20.689053,-34.417175],[20.071261,-34.795137],[19.616405,-34.819166],[19.193278,-34.462599],[18.855315,-34.444306],[18.424643,-33.997873],[18.377411,-34.136521],[18.244499,-33.867752],[18.25008,-33.281431],[17.92519,-32.611291],[18.24791,-32.429131],[18.221762,-31.661633],[17.566918,-30.725721],[17.064416,-29.878641],[17.062918,-29.875954],[16.344977,-28.576705],[16.824017,-28.082162],[17.218929,-28.355943],[17.387497,-28.783514],[17.836152,-28.856378],[18.464899,-29.045462],[19.002127,-28.972443],[19.894734,-28.461105],[19.895768,-24.76779],[20.165726,-24.917962],[20.758609,-25.868136],[20.66647,-26.477453],[20.889609,-26.828543],[21.605896,-26.726534],[22.105969,-26.280256],[22.579532,-25.979448],[22.824271,-25.500459],[23.312097,-25.26869],[23.73357,-25.390129],[24.211267,-25.670216],[25.025171,-25.71967],[25.664666,-25.486816],[25.765849,-25.174845],[25.941652,-24.696373],[26.485753,-24.616327],[26.786407,-24.240691],[27.11941,-23.574323],[28.017236,-22.827754],[29.432188,-22.091313],[29.839037,-22.102216],[30.322883,-22.271612],[30.659865,-22.151567],[31.191409,-22.25151],[31.670398,-23.658969],[31.930589,-24.369417],[31.752408,-25.484284],[31.837778,-25.843332],[31.333158,-25.660191],[31.04408,-25.731452],[30.949667,-26.022649],[30.676609,-26.398078],[30.685962,-26.743845],[31.282773,-27.285879],[31.86806,-27.177927],[32.071665,-26.73382],[32.83012,-26.742192],[32.580265,-27.470158],[32.462133,-28.301011],[32.203389,-28.752405],[31.521001,-29.257387]],[[28.978263,-28.955597],[28.5417,-28.647502],[28.074338,-28.851469],[27.532511,-29.242711],[26.999262,-29.875954],[27.749397,-30.645106],[28.107205,-30.545732],[28.291069,-30.226217],[28.8484,-30.070051],[29.018415,-29.743766],[29.325166,-29.257387],[28.978263,-28.955597]]]}}, {"type":"Feature","id":"ZMB","properties":{"name":"Zambia"},"geometry":{"type":"Polygon","coordinates":[[[32.759375,-9.230599],[33.231388,-9.676722],[33.485688,-10.525559],[33.31531,-10.79655],[33.114289,-11.607198],[33.306422,-12.435778],[32.991764,-12.783871],[32.688165,-13.712858],[33.214025,-13.97186],[30.179481,-14.796099],[30.274256,-15.507787],[29.516834,-15.644678],[28.947463,-16.043051],[28.825869,-16.389749],[28.467906,-16.4684],[27.598243,-17.290831],[27.044427,-17.938026],[26.706773,-17.961229],[26.381935,-17.846042],[25.264226,-17.73654],[25.084443,-17.661816],[25.07695,-17.578823],[24.682349,-17.353411],[24.033862,-17.295843],[23.215048,-17.523116],[22.562478,-16.898451],[21.887843,-16.08031],[21.933886,-12.898437],[24.016137,-12.911046],[23.930922,-12.565848],[24.079905,-12.191297],[23.904154,-11.722282],[24.017894,-11.237298],[23.912215,-10.926826],[24.257155,-10.951993],[24.314516,-11.262826],[24.78317,-11.238694],[25.418118,-11.330936],[25.75231,-11.784965],[26.553088,-11.92444],[27.16442,-11.608748],[27.388799,-12.132747],[28.155109,-12.272481],[28.523562,-12.698604],[28.934286,-13.248958],[29.699614,-13.257227],[29.616001,-12.178895],[29.341548,-12.360744],[28.642417,-11.971569],[28.372253,-11.793647],[28.49607,-10.789884],[28.673682,-9.605925],[28.449871,-9.164918],[28.734867,-8.526559],[29.002912,-8.407032],[30.346086,-8.238257],[30.740015,-8.340007],[31.157751,-8.594579],[31.556348,-8.762049],[32.191865,-8.930359],[32.759375,-9.230599]]]}}, {"type":"Feature","id":"ZWE","properties":{"name":"Zimbabwe"},"geometry":{"type":"Polygon","coordinates":[[[31.191409,-22.25151],[30.659865,-22.151567],[30.322883,-22.271612],[29.839037,-22.102216],[29.432188,-22.091313],[28.794656,-21.639454],[28.02137,-21.485975],[27.727228,-20.851802],[27.724747,-20.499059],[27.296505,-20.39152],[26.164791,-19.293086],[25.850391,-18.714413],[25.649163,-18.536026],[25.264226,-17.73654],[26.381935,-17.846042],[26.706773,-17.961229],[27.044427,-17.938026],[27.598243,-17.290831],[28.467906,-16.4684],[28.825869,-16.389749],[28.947463,-16.043051],[29.516834,-15.644678],[30.274256,-15.507787],[30.338955,-15.880839],[31.173064,-15.860944],[31.636498,-16.07199],[31.852041,-16.319417],[32.328239,-16.392074],[32.847639,-16.713398],[32.849861,-17.979057],[32.654886,-18.67209],[32.611994,-19.419383],[32.772708,-19.715592],[32.659743,-20.30429],[32.508693,-20.395292],[32.244988,-21.116489],[31.191409,-22.25151]]]}} ]} ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/App.java ================================================ package com.vasilkoff.easyvpnfree; import android.app.Application; import android.content.Context; import android.content.res.Configuration; import com.crashlytics.android.Crashlytics; import com.crashlytics.android.answers.Answers; import com.google.android.gms.analytics.GoogleAnalytics; import com.google.android.gms.analytics.Tracker; import io.fabric.sdk.android.Fabric; public class App extends Application { private static App instance; private Tracker mTracker; private static final String PROPERTY_ID = "UA-89622148-1"; private static final String PROPERTY_ID_PRO = "UA-89641705-1"; @Override public void onCreate() { super.onCreate(); if (!BuildConfig.DEBUG) Fabric.with(this, new Crashlytics()); instance = this; } synchronized public Tracker getDefaultTracker() { if (mTracker == null) { GoogleAnalytics analytics = GoogleAnalytics.getInstance(this); mTracker = analytics.newTracker(BuildConfig.FLAVOR == "pro" ? PROPERTY_ID_PRO : PROPERTY_ID); } return mTracker; } @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); } public static String getResourceString(int resId) { return instance.getString(resId); } public static App getInstance() { return instance; } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/activity/AboutActivity.java ================================================ package com.vasilkoff.easyvpnfree.activity; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Bundle; import android.widget.TextView; import com.vasilkoff.easyvpnfree.R; public class AboutActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_about); try { PackageInfo pinfo = getPackageManager().getPackageInfo(getPackageName(), 0); int versionNumber = pinfo.versionCode; String versionName = pinfo.versionName; String appName = AboutActivity.this.getString(R.string.app_name); TextView versionText = (TextView)findViewById(R.id.appVersion); versionText.setText( String.format("%s version %s build %d", appName, versionName, versionNumber ) ); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/activity/BaseActivity.java ================================================ package com.vasilkoff.easyvpnfree.activity; import android.content.Intent; import android.support.v4.widget.DrawerLayout; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; import android.util.DisplayMetrics; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.FrameLayout; import com.androidnetworking.AndroidNetworking; import com.androidnetworking.common.Priority; import com.androidnetworking.error.ANError; import com.androidnetworking.interfaces.JSONArrayRequestListener; import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import com.google.android.gms.analytics.HitBuilders; import com.google.android.gms.analytics.Tracker; import com.vasilkoff.easyvpnfree.App; import com.vasilkoff.easyvpnfree.BuildConfig; import com.vasilkoff.easyvpnfree.R; import com.vasilkoff.easyvpnfree.database.DBHelper; import com.vasilkoff.easyvpnfree.model.Server; import com.vasilkoff.easyvpnfree.util.CountriesNames; import com.vasilkoff.easyvpnfree.util.PropertiesService; import com.vasilkoff.easyvpnfree.util.TotalTraffic; import com.vasilkoff.easyvpnfree.util.iap.IabHelper; import com.vasilkoff.easyvpnfree.util.iap.IabResult; import com.vasilkoff.easyvpnfree.util.iap.Inventory; import com.vasilkoff.easyvpnfree.util.iap.Purchase; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Random; import de.blinkt.openvpn.core.VpnStatus; /** * Created by Kusenko on 20.10.2016. */ public abstract class BaseActivity extends AppCompatActivity { private DrawerLayout fullLayout; private Toolbar toolbar; static final int ADBLOCK_REQUEST = 10001; static final int PREMIUM_SERVERS_REQUEST = 10002; public static Server connectedServer = null; boolean hideCurrentConnection = false; IabHelper iapHelper; public static final String IAP_TAG = "IAP"; static final String TEST_ITEM_SKU = "android.test.purchased"; static final String ADBLOCK_ITEM_SKU = "adblock"; static final String MORE_SERVERS_ITEM_SKU = "more_servers"; static String key = ""; static boolean availableFilterAds = false; static boolean premiumServers = false; static String adblockSKU; static String moreServersSKU; static String currentSKU; int widthWindow ; int heightWindow; static DBHelper dbHelper; Map localeCountries; static Tracker mTracker; @Override public void setContentView(int layoutResID) { if (BuildConfig.FLAVOR == "pro" || BuildConfig.FLAVOR == "underground") { availableFilterAds = true; premiumServers = true; } fullLayout = (DrawerLayout) getLayoutInflater().inflate(R.layout.activity_base, null); FrameLayout activityContainer = (FrameLayout) fullLayout.findViewById(R.id.activity_content); getLayoutInflater().inflate(layoutResID, activityContainer, true); super.setContentView(fullLayout); toolbar = (Toolbar) findViewById(R.id.toolbar); if (useToolbar()) { setSupportActionBar(toolbar); } else { toolbar.setVisibility(View.GONE); } if (useHomeButton()) { if (getSupportActionBar() != null){ getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true); } } if (BuildConfig.DEBUG) { moreServersSKU = TEST_ITEM_SKU; adblockSKU = TEST_ITEM_SKU; } else { moreServersSKU = MORE_SERVERS_ITEM_SKU; adblockSKU = ADBLOCK_ITEM_SKU; } dbHelper = new DBHelper(this); DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); widthWindow = dm.widthPixels; heightWindow = dm.heightPixels; localeCountries = CountriesNames.getCountries(); App application = (App) getApplication(); mTracker = application.getDefaultTracker(); } @Override protected void onPause() { super.onPause(); TotalTraffic.saveTotal(); } @Override protected void onDestroy() { super.onDestroy(); if (iapHelper != null) iapHelper.dispose(); iapHelper = null; } private void initPurchaseHelper() { if (iapHelper == null) { iapHelper = new IabHelper(this, getString(R.string.base64EncodedPublicKey)); iapHelper.enableDebugLogging(BuildConfig.DEBUG); iapHelper.startSetup(new IabHelper.OnIabSetupFinishedListener() { @Override public void onIabSetupFinished(IabResult result) { if (result.isSuccess()) { // Have we been disposed of in the meantime? If so, quit. if (iapHelper == null) return; // IAB is fully set up. Now, let's get an inventory of stuff we own. Log.d(IAP_TAG, "Setup successful. Querying inventory."); checkPurchase(); } else { Log.d(IAP_TAG, "Oh noes, there was a problem."); } } }); } } private void checkPurchase() { iapHelper.flagEndAsync(); if(iapHelper.isSetupDone() && !iapHelper.isAsyncInProgress() && !iapHelper.isDisposed()) { iapHelper.queryInventoryAsync(mGotInventoryListener); } } void launchPurchase(String sku, int request) { currentSKU = sku; String base64EncodedPublicKey = getString(R.string.base64EncodedPublicKey); Random random = new Random(); key = base64EncodedPublicKey.substring(random.nextInt(base64EncodedPublicKey.length() - 2)); iapHelper.flagEndAsync(); if (iapHelper.isSetupDone() && !iapHelper.isAsyncInProgress() && !iapHelper.isDisposed()) { iapHelper.launchPurchaseFlow(this, sku, request, mPurchaseFinishedListener, key + sku); } } /** Verifies the developer payload of a purchase. */ boolean verifyDeveloperPayload(Purchase p, String sku) { String responsePayload = p.getDeveloperPayload(); String computedPayload = key + sku; return responsePayload != null && responsePayload.equals(computedPayload); } IabHelper.QueryInventoryFinishedListener mGotInventoryListener = new IabHelper.QueryInventoryFinishedListener() { @Override public void onQueryInventoryFinished(IabResult result, Inventory inventory) { if (result.isFailure()) { Log.d(IAP_TAG, "Purchase finished: " + result + ", purchase: " + inventory); } else { if (inventory.hasPurchase(adblockSKU)) { availableFilterAds = true; } if (inventory.hasPurchase(moreServersSKU)) { premiumServers = true; } } } }; IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener = new IabHelper.OnIabPurchaseFinishedListener() { @Override public void onIabPurchaseFinished(IabResult result, Purchase purchase) { if (result.isFailure()) { Log.d(IAP_TAG, "Oh noes, there was a problem."); return; } else { if (purchase.getSku().equals(currentSKU) && verifyDeveloperPayload(purchase, currentSKU)) { availableFilterAds = true; } } } }; protected boolean useToolbar() { return true; } protected boolean useHomeButton() { return true; } protected boolean useMenu() { return true; } @Override protected void onResume() { super.onResume(); if (BuildConfig.FLAVOR == "free") { initPurchaseHelper(); } mTracker.setScreenName(getClass().getSimpleName()); mTracker.send(new HitBuilders.ScreenViewBuilder().build()); if (!BuildConfig.DEBUG) Answers.getInstance().logCustom(new CustomEvent("Viewed activity") .putCustomAttribute("activity", getClass().getSimpleName())); } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_main, menu); for (int i = 0; i < menu.size(); i++) { if (menu.getItem(i).getItemId() == R.id.actionCurrentServer && (connectedServer == null || hideCurrentConnection || !VpnStatus.isVPNActive())) menu.getItem(i).setVisible(false); if (premiumServers && menu.getItem(i).getItemId() == R.id.actionGetMoreServers) menu.getItem(i).setTitle(getString(R.string.current_servers_list)); if (BuildConfig.FLAVOR == "underground" && menu.getItem(i).getItemId() == R.id.actionShare) menu.getItem(i).setVisible(false); } return useMenu(); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: onBackPressed(); return true; case R.id.actionRefresh: startActivity(new Intent(getApplicationContext(), LoaderActivity.class)); finish(); return true; case R.id.actionAbout: startActivity(new Intent(getApplicationContext(), AboutActivity.class)); return true; case R.id.actionShare: sendTouchButton("Share"); Intent sendIntent = new Intent(); sendIntent.setAction(Intent.ACTION_SEND); sendIntent.putExtra(Intent.EXTRA_TEXT, getString(R.string.share_text)); sendIntent.setType("text/plain"); startActivity(sendIntent); return true; case R.id.actionCurrentServer: if (connectedServer != null) startActivity(new Intent(this, ServerActivity.class)); return true; case R.id.actionGetMoreServers: if (premiumServers) { startActivity(new Intent(this, ServersInfo.class)); } else { sendTouchButton("GetMoreServers"); launchPurchase(moreServersSKU, PREMIUM_SERVERS_REQUEST); } return true; case R.id.action_settings: sendTouchButton("Settings"); startActivity(new Intent(this, MyPreferencesActivity.class)); return true; case R.id.action_bookmarks: startActivity(new Intent(this, BookmarkServerListActivity.class)); return true; } return super.onOptionsItemSelected(item); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode == RESULT_OK) { switch (requestCode) { case PREMIUM_SERVERS_REQUEST: Log.d(IAP_TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data); if (iapHelper == null) return; if (iapHelper.handleActivityResult(requestCode, resultCode, data)) { Log.d(IAP_TAG, "onActivityResult handled by IABUtil."); Intent intent = new Intent(getApplicationContext(), LoaderActivity.class); intent.putExtra("firstPremiumLoad", true); startActivity(intent); finish(); } break; } } } public Server getRandomServer() { Server randomServer; if (PropertiesService.getCountryPriority()) { randomServer = dbHelper.getGoodRandomServer(PropertiesService.getSelectedCountry()); } else { randomServer = dbHelper.getGoodRandomServer(null); } return randomServer; } public void newConnecting(Server server, boolean fastConnection, boolean autoConnection) { if (server != null) { Intent intent = new Intent(this, ServerActivity.class); intent.putExtra(Server.class.getCanonicalName(), server); intent.putExtra("fastConnection", fastConnection); intent.putExtra("autoConnection", autoConnection); startActivity(intent); } } public static void sendTouchButton(String button) { if (!BuildConfig.DEBUG) Answers.getInstance().logCustom(new CustomEvent("Touches buttons") .putCustomAttribute("Button", button)); mTracker.send(new HitBuilders.EventBuilder() .setCategory("Touches buttons") .setAction(button) .build()); } protected void ipInfoResult() {} protected void getIpInfo(Server server) { List serverList = new ArrayList(); serverList.add(server); getIpInfo(serverList); } protected void getIpInfo(final List serverList) { JSONArray jsonArray = new JSONArray(); for (Server server : serverList) { JSONObject jsonObject = new JSONObject(); try { jsonObject.put("query", server.getIp()); jsonObject.put("lang", Locale.getDefault().getLanguage()); jsonArray.put(jsonObject); } catch (JSONException e) { e.printStackTrace(); } } AndroidNetworking.post(getString(R.string.url_check_ip_batch)) .addJSONArrayBody(jsonArray) .setTag("getIpInfo") .setPriority(Priority.MEDIUM) .build() .getAsJSONArray(new JSONArrayRequestListener() { @Override public void onResponse(JSONArray response) { if (dbHelper.setIpInfo(response, serverList)) ipInfoResult(); } @Override public void onError(ANError error) { } }); } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/activity/BookmarkServerListActivity.java ================================================ package com.vasilkoff.easyvpnfree.activity; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import com.vasilkoff.easyvpnfree.R; import com.vasilkoff.easyvpnfree.adapter.BookmarkServerListAdapter; import com.vasilkoff.easyvpnfree.model.Server; import java.util.List; public class BookmarkServerListActivity extends BaseActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_bookmark_server_list); } @Override protected void onResume() { super.onResume(); final List serverList = dbHelper.getBookmarks(); BookmarkServerListAdapter adapter = new BookmarkServerListAdapter(serverList, this, dbHelper); RecyclerView recyclerView = (RecyclerView) findViewById(R.id.bookmarkRv); LinearLayoutManager llm = new LinearLayoutManager(this); recyclerView.setLayoutManager(llm); recyclerView.setAdapter(adapter); } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/activity/HomeActivity.java ================================================ package com.vasilkoff.easyvpnfree.activity; import android.content.Intent; import android.content.res.Configuration; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.support.v4.content.ContextCompat; import android.text.method.LinkMovementMethod; import android.text.util.Linkify; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.PopupWindow; import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.vasilkoff.easyvpnfree.BuildConfig; import com.vasilkoff.easyvpnfree.R; import com.vasilkoff.easyvpnfree.model.Country; import com.vasilkoff.easyvpnfree.model.Server; import com.vasilkoff.easyvpnfree.util.BitmapGenerator; import com.vasilkoff.easyvpnfree.util.ConnectionQuality; import com.vasilkoff.easyvpnfree.util.LoadData; import com.vasilkoff.easyvpnfree.util.PropertiesService; import com.vasilkoff.easyvpnfree.util.map.MapCreator; import com.vasilkoff.easyvpnfree.util.map.MyMarker; import org.mapsforge.core.graphics.Bitmap; import org.mapsforge.core.model.LatLong; import org.mapsforge.core.model.Point; import org.mapsforge.map.android.graphics.AndroidGraphicFactory; import org.mapsforge.map.android.view.MapView; import org.mapsforge.map.layer.Layers; import org.mapsforge.map.layer.overlay.Marker; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; public class HomeActivity extends BaseActivity { private MapView mapView; public static final String EXTRA_COUNTRY = "country"; private PopupWindow popupWindow; private RelativeLayout homeContextRL; private List countryList; private final String COUNTRY_FILE_NAME = "countries.json"; private List countryLatLonList = null; private Layers layers; private List markerList; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_home); homeContextRL = (RelativeLayout) findViewById(R.id.homeContextRL); countryList = dbHelper.getUniqueCountries(); long totalServ = dbHelper.getCount(); if (!BuildConfig.DEBUG) Answers.getInstance().logCustom(new CustomEvent("Total servers") .putCustomAttribute("Total servers", totalServ)); String totalServers = String.format(getResources().getString(R.string.total_servers), totalServ); ((TextView) findViewById(R.id.homeTotalServers)).setText(totalServers); initMap(); } @Override protected void onResume() { super.onResume(); invalidateOptionsMenu(); initDetailsServerOnMap(); if (PropertiesService.getShowNote()) { homeContextRL.post(new Runnable() { @Override public void run() { showNote(); } }); } } @Override protected void onDestroy() { mapView.destroyAll(); AndroidGraphicFactory.clearResourceMemoryCache(); super.onDestroy(); } private void initMap() { AndroidGraphicFactory.createInstance(getApplication()); mapView = new MapView(this); mapView.setClickable(true); mapView.getMapScaleBar().setVisible(false); mapView.setBuiltInZoomControls(false); mapView.setZoomLevelMin((byte) 2); mapView.setZoomLevelMax((byte) 10); mapView.setZoomLevel((byte) 2); mapView.getModel().displayModel.setBackgroundColor(ContextCompat.getColor(this, R.color.mapBackground)); layers = mapView.getLayerManager().getLayers(); MapCreator mapCreator = new MapCreator(this, layers); mapCreator.parseGeoJson("world_map.geo.json"); initServerOnMap(layers); LinearLayout map = (LinearLayout) findViewById(R.id.map); map.addView(mapView); } @Override protected boolean useHomeButton() { return false; } public void homeOnClick(View view) { switch (view.getId()) { case R.id.homeBtnChooseCountry: sendTouchButton("homeBtnChooseCountry"); chooseCountry(); break; case R.id.homeBtnRandomConnection: sendTouchButton("homeBtnRandomConnection"); Server randomServer = getRandomServer(); if (randomServer != null) { newConnecting(randomServer, true, true); } else { String randomError = String.format(getResources().getString(R.string.error_random_country), PropertiesService.getSelectedCountry()); Toast.makeText(this, randomError, Toast.LENGTH_LONG).show(); } break; } } private void chooseCountry() { View view = initPopUp(R.layout.pop_up_choose_country, 0.6f, 0.8f, 0.8f, 0.7f); final List countryListName = new ArrayList(); for (Server server : countryList) { String localeCountryName = localeCountries.get(server.getCountryShort()) != null ? localeCountries.get(server.getCountryShort()) : server.getCountryLong(); countryListName.add(localeCountryName); } ListView lvCountry = (ListView) view.findViewById(R.id.homeCountryList); ArrayAdapter adapter = new ArrayAdapter(this, android.R.layout.simple_list_item_1, countryListName); lvCountry.setAdapter(adapter); lvCountry.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { popupWindow.dismiss(); onSelectCountry(countryList.get(position)); } }); popupWindow.showAtLocation(homeContextRL, Gravity.CENTER,0, 0); } private void showNote() { View view = initPopUp(R.layout.pop_up_note, 0.6f, 0.5f, 0.9f, 0.4f); ((TextView) view.findViewById(R.id.noteLink)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent in=new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.vpngate.net/en/join.aspx")); startActivity(in); } }); popupWindow.showAtLocation(homeContextRL, Gravity.CENTER,0, 0); PropertiesService.setShowNote(false); } private View initPopUp(int resourse, float landPercentW, float landPercentH, float portraitPercentW, float portraitPercentH) { LayoutInflater inflater = (LayoutInflater) getApplicationContext().getSystemService(LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(resourse, null); if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) { popupWindow = new PopupWindow( view, (int)(widthWindow * landPercentW), (int)(heightWindow * landPercentH) ); } else { popupWindow = new PopupWindow( view, (int)(widthWindow * portraitPercentW), (int)(heightWindow * portraitPercentH) ); } popupWindow.setOutsideTouchable(false); popupWindow.setFocusable(true); popupWindow.setBackgroundDrawable(new BitmapDrawable()); return view; } private void onSelectCountry(Server server) { Intent intent = new Intent(getApplicationContext(), ServersListActivity.class); intent.putExtra(EXTRA_COUNTRY, server.getCountryShort()); startActivity(intent); } private void initDetailsServerOnMap() { if (markerList != null && markerList.size() > 0) { for (Marker marker : markerList) { layers.remove(marker); } } List serverList = dbHelper.getServersWithGPS(); markerList = new ArrayList(); for (Server server : serverList) { LatLong position = new LatLong(server.getLat(), server.getLon()); Bitmap bitmap = AndroidGraphicFactory.convertToBitmap(ContextCompat.getDrawable(this, getResources().getIdentifier(ConnectionQuality.getSimplePointIcon(server.getQuality()), "drawable", getPackageName()))); Marker serverMarker = new Marker(position, bitmap, 0, 0); markerList.add(serverMarker); layers.add(serverMarker); } } private void initServerOnMap(Layers layers) { Type listType = new TypeToken>(){}.getType(); countryLatLonList = new Gson().fromJson(LoadData.fromFile(COUNTRY_FILE_NAME, this), listType); for (Server server : countryList) { for (Country country : countryLatLonList) { if (server.getCountryShort().equals(country.getCountryCode())) { LatLong position = new LatLong(country.getCapitalLatitude(), country.getCapitalLongitude()); Bitmap bitmap = AndroidGraphicFactory.convertToBitmap(ContextCompat.getDrawable(this, getResources().getIdentifier(ConnectionQuality.getPointIcon(server.getQuality()), "drawable", getPackageName()))); MyMarker countryMarker = new MyMarker(position, bitmap, 0, 0, server) { @Override public boolean onTap(LatLong geoPoint, Point viewPosition, Point tapPoint) { if (contains(viewPosition, tapPoint)) { onSelectCountry((Server)getRelationObject()); return true; } return false; } }; layers.add(countryMarker); String localeCountryName = localeCountries.get(country.getCountryCode()) != null ? localeCountries.get(country.getCountryCode()) : country.getCountryName(); Drawable drawable = new BitmapDrawable(getResources(), BitmapGenerator.getTextAsBitmap(localeCountryName, 20, ContextCompat.getColor(this,R.color.mapNameCountry))); Bitmap bitmapName = AndroidGraphicFactory.convertToBitmap(drawable); Marker countryNameMarker = new Marker(position, bitmapName, 0, bitmap.getHeight() / 2); layers.add(countryNameMarker); } } } } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/activity/LauncherActivity.java ================================================ package com.vasilkoff.easyvpnfree.activity; import android.app.Activity; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.support.v7.app.AlertDialog; import com.vasilkoff.easyvpnfree.R; import com.vasilkoff.easyvpnfree.util.NetworkState; public class LauncherActivity extends Activity { private static boolean loadStatus = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (NetworkState.isOnline()) { if (loadStatus) { Intent myIntent = new Intent(this, HomeActivity.class); startActivity(myIntent); finish(); } else { loadStatus = true; Intent myIntent = new Intent(this, LoaderActivity.class); startActivity(myIntent); finish(); } } else { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(getString(R.string.network_error)) .setMessage(getString(R.string.network_error_message)) .setNegativeButton(getString(R.string.ok), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); onBackPressed(); } }); AlertDialog alert = builder.create(); alert.show(); } } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/activity/LoaderActivity.java ================================================ package com.vasilkoff.easyvpnfree.activity; import android.content.Intent; import android.os.Handler; import android.os.Message; import android.os.Bundle; import android.view.View; import android.widget.TextView; import com.androidnetworking.AndroidNetworking; import com.androidnetworking.common.Priority; import com.androidnetworking.error.ANError; import com.androidnetworking.interfaces.DownloadListener; import com.androidnetworking.interfaces.DownloadProgressListener; import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import com.daimajia.numberprogressbar.NumberProgressBar; import com.vasilkoff.easyvpnfree.BuildConfig; import com.vasilkoff.easyvpnfree.R; import com.vasilkoff.easyvpnfree.model.Server; import com.vasilkoff.easyvpnfree.util.PropertiesService; import com.vasilkoff.easyvpnfree.util.Stopwatch; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; import java.util.concurrent.TimeUnit; import okhttp3.OkHttpClient; public class LoaderActivity extends BaseActivity { private NumberProgressBar progressBar; private TextView commentsText; private Handler updateHandler; private final int LOAD_ERROR = 0; private final int DOWNLOAD_PROGRESS = 1; private final int PARSE_PROGRESS = 2; private final int LOADING_SUCCESS = 3; private final int SWITCH_TO_RESULT = 4; private final String BASE_URL = "http://www.vpngate.net/api/iphone/"; private final String BASE_FILE_NAME = "vpngate.csv"; private boolean premiumStage = true; private final String PREMIUM_URL = "http://easyvpn.vasilkoff.com/?type=csv"; private final String PREMIUM_FILE_NAME = "premiumServers.csv"; private int percentDownload = 0; private Stopwatch stopwatch; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_loader); progressBar = (NumberProgressBar)findViewById(R.id.number_progress_bar); commentsText = (TextView)findViewById(R.id.commentsText); if (getIntent().getBooleanExtra("firstPremiumLoad", false)) ((TextView)findViewById(R.id.loaderPremiumText)).setVisibility(View.VISIBLE); progressBar.setMax(100); updateHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { switch (msg.arg1) { case LOAD_ERROR: { commentsText.setText(msg.arg2); progressBar.setProgress(100); } break; case DOWNLOAD_PROGRESS: { commentsText.setText(R.string.downloading_csv_text); progressBar.setProgress(msg.arg2); } break; case PARSE_PROGRESS: { commentsText.setText(R.string.parsing_csv_text); progressBar.setProgress(msg.arg2); } break; case LOADING_SUCCESS: { commentsText.setText(R.string.successfully_loaded); progressBar.setProgress(100); Message end = new Message(); end.arg1 = SWITCH_TO_RESULT; updateHandler.sendMessageDelayed(end,500); } break; case SWITCH_TO_RESULT: { if (!BuildConfig.DEBUG) Answers.getInstance().logCustom(new CustomEvent("Time servers loading") .putCustomAttribute("Time servers loading", stopwatch.getElapsedTime())); if (PropertiesService.getConnectOnStart()) { Server randomServer = getRandomServer(); if (randomServer != null) { newConnecting(randomServer, true, true); } else { startActivity(new Intent(LoaderActivity.this, HomeActivity.class)); } } else { startActivity(new Intent(LoaderActivity.this, HomeActivity.class)); } } } return true; } }); progressBar.setProgress(0); } @Override protected void onResume() { super.onResume(); downloadCSVFile(BASE_URL, BASE_FILE_NAME); } @Override protected boolean useHomeButton() { return false; } @Override protected boolean useMenu() { return false; } private void downloadCSVFile(String url, String fileName) { stopwatch = new Stopwatch(); OkHttpClient okHttpClient = new OkHttpClient().newBuilder() .connectTimeout(60, TimeUnit.SECONDS) .readTimeout(60, TimeUnit.SECONDS) .writeTimeout(60, TimeUnit.SECONDS) .build(); AndroidNetworking.download(url, getCacheDir().getPath(), fileName) .setTag("downloadCSV") .setPriority(Priority.MEDIUM) .setOkHttpClient(okHttpClient) .build() .setDownloadProgressListener(new DownloadProgressListener() { @Override public void onProgress(long bytesDownloaded, long totalBytes) { if(totalBytes <= 0) { // when we dont know the file size, assume it is 1200000 bytes :) totalBytes = 1200000; } if (!premiumServers || !premiumStage) { if (percentDownload <= 90) percentDownload = percentDownload + (int)((100 * bytesDownloaded) / totalBytes); } else { percentDownload = (int)((100 * bytesDownloaded) / totalBytes); } Message msg = new Message(); msg.arg1 = DOWNLOAD_PROGRESS; msg.arg2 = percentDownload; updateHandler.sendMessage(msg); } }) .startDownload(new DownloadListener() { @Override public void onDownloadComplete() { if (premiumServers && premiumStage) { premiumStage = false; downloadCSVFile(PREMIUM_URL, PREMIUM_FILE_NAME); } else { parseCSVFile(BASE_FILE_NAME); } } @Override public void onError(ANError error) { Message msg = new Message(); msg.arg1 = LOAD_ERROR; msg.arg2 = R.string.network_error; updateHandler.sendMessage(msg); } }); } private void parseCSVFile(String fileName) { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader(getCacheDir().getPath().concat("/").concat(fileName))); } catch (IOException e) { e.printStackTrace(); Message msg = new Message(); msg.arg1 = LOAD_ERROR; msg.arg2 = R.string.csv_file_error; updateHandler.sendMessage(msg); } if (reader != null) { try { int startLine = 2; int type = 0; if (premiumServers && premiumStage) { startLine = 0; type = 1; } else { dbHelper.clearTable(); } int counter = 0; String line = null; while ((line = reader.readLine()) != null) { if (counter >= startLine) { dbHelper.putLine(line, type); } counter++; if (!premiumServers || !premiumStage) { Message msg = new Message(); msg.arg1 = PARSE_PROGRESS; msg.arg2 = counter;// we know that the server returns 100 records updateHandler.sendMessage(msg); } } if (premiumServers && !premiumStage) { premiumStage = true; parseCSVFile(PREMIUM_FILE_NAME); } else { Message end = new Message(); end.arg1 = LOADING_SUCCESS; updateHandler.sendMessageDelayed(end,200); } } catch (Exception e) { e.printStackTrace(); Message msg = new Message(); msg.arg1 = LOAD_ERROR; msg.arg2 = R.string.csv_file_error_parsing; updateHandler.sendMessage(msg); } } } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/activity/MyPreferencesActivity.java ================================================ package com.vasilkoff.easyvpnfree.activity; import android.os.Bundle; import android.preference.ListPreference; import android.preference.PreferenceActivity; import android.preference.PreferenceCategory; import android.preference.PreferenceFragment; import android.support.v7.widget.Toolbar; import android.view.View; import com.google.android.gms.analytics.HitBuilders; import com.google.android.gms.analytics.Tracker; import com.vasilkoff.easyvpnfree.App; import com.vasilkoff.easyvpnfree.R; import com.vasilkoff.easyvpnfree.database.DBHelper; import com.vasilkoff.easyvpnfree.model.Country; import com.vasilkoff.easyvpnfree.model.Server; import com.vasilkoff.easyvpnfree.util.CountriesNames; import com.vasilkoff.easyvpnfree.util.PropertiesService; import java.util.ArrayList; import java.util.List; import static com.vasilkoff.easyvpnfree.R.id.toolbar; /** * Created by Kusenko on 13.12.2016. */ public class MyPreferencesActivity extends PreferenceActivity { private Toolbar toolbar; Tracker mTracker; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_preference); toolbar = (Toolbar) findViewById(R.id.preferenceToolbar); toolbar.setTitle(R.string.app_name); toolbar.setNavigationOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); getFragmentManager().beginTransaction().replace(R.id.preferenceContent, new MyPreferenceFragment()).commit(); App application = (App) getApplication(); mTracker = application.getDefaultTracker(); } public static class MyPreferenceFragment extends PreferenceFragment { @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preferences); DBHelper dbHelper = new DBHelper(getActivity().getApplicationContext()); List countryList = dbHelper.getUniqueCountries(); CharSequence entriesValues[] = new CharSequence[countryList.size()]; CharSequence entries[] = new CharSequence[countryList.size()]; for (int i = 0; i < countryList.size(); i++) { entriesValues[i] = countryList.get(i).getCountryLong(); String localeCountryName = CountriesNames.getCountries().get(countryList.get(i).getCountryShort()) != null ? CountriesNames.getCountries().get(countryList.get(i).getCountryShort()) : countryList.get(i).getCountryLong(); entries[i] = localeCountryName; } ListPreference listPreference = (ListPreference) findPreference("selectedCountry"); if (entries.length == 0) { PreferenceCategory countryPriorityCategory = (PreferenceCategory) findPreference("countryPriorityCategory"); getPreferenceScreen().removePreference(countryPriorityCategory); } else { listPreference.setEntries(entries); listPreference.setEntryValues(entriesValues); if (PropertiesService.getSelectedCountry() == null) listPreference.setValueIndex(0); } } } @Override protected void onResume() { super.onResume(); mTracker.setScreenName("Preference"); mTracker.send(new HitBuilders.ScreenViewBuilder().build()); } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/activity/ServerActivity.java ================================================ package com.vasilkoff.easyvpnfree.activity; import android.app.Activity; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.net.VpnService; import android.os.AsyncTask; import android.os.IBinder; import android.support.v4.content.ContextCompat; import android.view.ViewGroup.LayoutParams; import android.os.Bundle; import android.support.v7.app.AlertDialog; import android.util.Base64; import android.util.Log; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.animation.AnimationUtils; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.PopupWindow; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import com.crashlytics.android.answers.Answers; import com.crashlytics.android.answers.CustomEvent; import com.vasilkoff.easyvpnfree.BuildConfig; import com.vasilkoff.easyvpnfree.R; import com.vasilkoff.easyvpnfree.model.Server; import com.vasilkoff.easyvpnfree.util.ConnectionQuality; import com.vasilkoff.easyvpnfree.util.PropertiesService; import com.vasilkoff.easyvpnfree.util.Stopwatch; import com.vasilkoff.easyvpnfree.util.TotalTraffic; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.math.BigDecimal; import java.math.RoundingMode; import java.util.concurrent.TimeUnit; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.ConfigParser; import de.blinkt.openvpn.core.OpenVPNService; import de.blinkt.openvpn.core.ProfileManager; import de.blinkt.openvpn.core.VPNLaunchHelper; import de.blinkt.openvpn.core.VpnStatus; public class ServerActivity extends BaseActivity { private static final int START_VPN_PROFILE = 70; private BroadcastReceiver br; private BroadcastReceiver trafficReceiver; public final static String BROADCAST_ACTION = "de.blinkt.openvpn.VPN_STATUS"; private static OpenVPNService mVPNService; private VpnProfile vpnProfile; private Server currentServer = null; private Button unblockCheck; private CheckBox adbBlockCheck; private Button serverConnect; private TextView lastLog; private ProgressBar connectingProgress; private PopupWindow popupWindow; private LinearLayout parentLayout; private TextView trafficInTotally; private TextView trafficOutTotally; private TextView trafficIn; private TextView trafficOut; private ImageButton bookmark; private static boolean filterAds = false; private static boolean defaultFilterAds = true; private boolean autoConnection; private boolean fastConnection; private Server autoServer; private boolean statusConnection = false; private boolean firstData = true; private WaitConnectionAsync waitConnection; private boolean inBackground; private static Stopwatch stopwatch; private boolean isBindedService = false; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_server); bookmark = (ImageButton) findViewById(R.id.serverBookmark); parentLayout = (LinearLayout) findViewById(R.id.serverParentLayout); unblockCheck = (Button) findViewById(R.id.serverUnblockCheck); adbBlockCheck = (CheckBox) findViewById(R.id.serverBlockingCheck); connectingProgress = (ProgressBar) findViewById(R.id.serverConnectingProgress); lastLog = (TextView) findViewById(R.id.serverStatus); serverConnect = (Button) findViewById(R.id.serverConnect); String totalIn = String.format(getResources().getString(R.string.traffic_in), TotalTraffic.getTotalTraffic().get(0)); trafficInTotally = (TextView) findViewById(R.id.serverTrafficInTotally); trafficInTotally.setText(totalIn); String totalOut = String.format(getResources().getString(R.string.traffic_out), TotalTraffic.getTotalTraffic().get(1)); trafficOutTotally = (TextView) findViewById(R.id.serverTrafficOutTotally); trafficOutTotally.setText(totalOut); trafficIn = (TextView) findViewById(R.id.serverTrafficIn); trafficIn.setText(""); trafficOut = (TextView) findViewById(R.id.serverTrafficOut); trafficOut.setText(""); br = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { receiveStatus(context, intent); } }; registerReceiver(br, new IntentFilter(BROADCAST_ACTION)); trafficReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { receiveTraffic(context, intent); } }; registerReceiver(trafficReceiver, new IntentFilter(TotalTraffic.TRAFFIC_ACTION)); unblockCheck.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendTouchButton("adsFiltering"); launchPurchase(adblockSKU, ADBLOCK_REQUEST); } }); adbBlockCheck.setChecked(defaultFilterAds); adbBlockCheck.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (!checkStatus()) defaultFilterAds = isChecked; } }); lastLog.setText(R.string.server_not_connected); initView(getIntent()); checkAvailableFilter(); } private void initView(Intent intent) { autoConnection = intent.getBooleanExtra("autoConnection", false); fastConnection = intent.getBooleanExtra("fastConnection", false); currentServer = (Server)intent.getParcelableExtra(Server.class.getCanonicalName()); if (currentServer == null) { if (connectedServer != null) { currentServer = connectedServer; } else { onBackPressed(); return; } } int bookmarkBg = dbHelper.checkBookmark(currentServer) ? R.drawable.ic_bookmark_red : R.drawable.ic_bookmark_grey; bookmark.setBackground(ContextCompat.getDrawable(this, bookmarkBg)); String code = currentServer.getCountryShort().toLowerCase(); if (code.equals("do")) code = "dom"; ((ImageView) findViewById(R.id.serverFlag)) .setImageResource( getResources().getIdentifier(code, "drawable", getPackageName())); String localeCountryName = localeCountries.get(currentServer.getCountryShort()) != null ? localeCountries.get(currentServer.getCountryShort()) : currentServer.getCountryLong(); ((TextView) findViewById(R.id.serverCountry)).setText(localeCountryName); ((TextView) findViewById(R.id.serverIP)).setText(currentServer.getIp()); ((TextView) findViewById(R.id.serverCity)).setText(currentServer.getCity()); ((TextView) findViewById(R.id.serverSessions)).setText(currentServer.getNumVpnSessions()); ((ImageView) findViewById(R.id.serverImageConnect)) .setImageResource( getResources().getIdentifier(ConnectionQuality.getConnectIcon(currentServer.getQuality()), "drawable", getPackageName())); String ping = currentServer.getPing() + " " + getString(R.string.ms); ((TextView) findViewById(R.id.serverPing)).setText(ping); double speedValue = (double) Integer.parseInt(currentServer.getSpeed()) / 1048576; speedValue = new BigDecimal(speedValue).setScale(3, RoundingMode.UP).doubleValue(); String speed = String.valueOf(speedValue) + " " + getString(R.string.mbps); ((TextView) findViewById(R.id.serverSpeed)).setText(speed); if (checkStatus()) { adbBlockCheck.setEnabled(false); adbBlockCheck.setChecked(filterAds); serverConnect.setText(getString(R.string.server_btn_disconnect)); ((TextView) findViewById(R.id.serverStatus)).setText(VpnStatus.getLastCleanLogMessage(getApplicationContext())); } else { serverConnect.setText(getString(R.string.server_btn_connect)); } } @Override protected void onNewIntent(Intent intent) { super.onNewIntent(intent); initView(intent); } private void receiveTraffic(Context context, Intent intent) { if (checkStatus()) { String in = ""; String out = ""; if (firstData) { firstData = false; } else { in = String.format(getResources().getString(R.string.traffic_in), intent.getStringExtra(TotalTraffic.DOWNLOAD_SESSION)); out = String.format(getResources().getString(R.string.traffic_out), intent.getStringExtra(TotalTraffic.UPLOAD_SESSION)); } trafficIn.setText(in); trafficOut.setText(out); String inTotal = String.format(getResources().getString(R.string.traffic_in), intent.getStringExtra(TotalTraffic.DOWNLOAD_ALL)); trafficInTotally.setText(inTotal); String outTotal = String.format(getResources().getString(R.string.traffic_out), intent.getStringExtra(TotalTraffic.UPLOAD_ALL)); trafficOutTotally.setText(outTotal); } } private void receiveStatus(Context context, Intent intent) { if (checkStatus()) { changeServerStatus(VpnStatus.ConnectionStatus.valueOf(intent.getStringExtra("status"))); lastLog.setText(VpnStatus.getLastCleanLogMessage(getApplicationContext())); } if (intent.getStringExtra("detailstatus").equals("NOPROCESS")) { try { TimeUnit.SECONDS.sleep(1); if (!VpnStatus.isVPNActive()) prepareStopVPN(); } catch (InterruptedException e) { e.printStackTrace(); } } } private void checkAvailableFilter() { if (availableFilterAds) { adbBlockCheck.setVisibility(View.VISIBLE); unblockCheck.setVisibility(View.GONE); } else { adbBlockCheck.setVisibility(View.GONE); unblockCheck.setVisibility(View.VISIBLE); } } @Override public void onBackPressed() { super.onBackPressed(); if (waitConnection != null) waitConnection.cancel(false); if (isTaskRoot()) { startActivity(new Intent(this, HomeActivity.class)); finish(); } } private boolean checkStatus() { if (connectedServer != null && connectedServer.getHostName().equals(currentServer.getHostName())) { return VpnStatus.isVPNActive(); } return false; } private void changeServerStatus(VpnStatus.ConnectionStatus status) { switch (status) { case LEVEL_CONNECTED: statusConnection = true; connectingProgress.setVisibility(View.GONE); if (!inBackground) { if (PropertiesService.getDownloaded() >= 104857600 && PropertiesService.getShowRating() && BuildConfig.FLAVOR != "underground") { PropertiesService.setShowRating(false); showRating(); } else { chooseAction(); } } serverConnect.setText(getString(R.string.server_btn_disconnect)); break; case LEVEL_NOTCONNECTED: serverConnect.setText(getString(R.string.server_btn_connect)); break; default: serverConnect.setText(getString(R.string.server_btn_disconnect)); statusConnection = false; connectingProgress.setVisibility(View.VISIBLE); } } private void prepareVpn() { connectingProgress.setVisibility(View.VISIBLE); if (loadVpnProfile()) { waitConnection = new WaitConnectionAsync(); waitConnection.execute(); serverConnect.setText(getString(R.string.server_btn_disconnect)); startVpn(); } else { connectingProgress.setVisibility(View.GONE); Toast.makeText(this, getString(R.string.server_error_loading_profile), Toast.LENGTH_SHORT).show(); } } public void serverOnClick(View view) { switch (view.getId()) { case R.id.serverConnect: sendTouchButton("serverConnect"); if (checkStatus()) { stopVpn(); } else { prepareVpn(); } break; case R.id.serverBtnCheckIp: sendTouchButton("serverBtnCheckIp"); Intent browse = new Intent( Intent.ACTION_VIEW , Uri.parse(getString(R.string.url_check_ip))); startActivity(browse); break; case R.id.serverBookmark: sendTouchButton("serverBookmark"); bookmark.startAnimation(AnimationUtils.loadAnimation(this, R.anim.scale)); if (dbHelper.checkBookmark(currentServer)) { dbHelper.delBookmark(currentServer); bookmark.setBackground(ContextCompat.getDrawable(this, R.drawable.ic_bookmark_grey)); } else { dbHelper.setBookmark(currentServer); bookmark.setBackground(ContextCompat.getDrawable(this, R.drawable.ic_bookmark_red)); } break; } } private boolean loadVpnProfile() { byte[] data; try { data = Base64.decode(currentServer.getConfigData(), Base64.DEFAULT); } catch (Exception e) { e.printStackTrace(); return false; } ConfigParser cp = new ConfigParser(); InputStreamReader isr = new InputStreamReader(new ByteArrayInputStream(data)); try { cp.parseConfig(isr); vpnProfile = cp.convertProfile(); vpnProfile.mName = currentServer.getCountryLong(); filterAds = adbBlockCheck.isChecked(); if (filterAds) { vpnProfile.mOverrideDNS = true; vpnProfile.mDNS1 = "198.101.242.72"; vpnProfile.mDNS2 = "23.253.163.53"; } ProfileManager.getInstance(this).addProfile(vpnProfile); } catch (IOException | ConfigParser.ConfigParseError e) { e.printStackTrace(); return false; } return true; } private void prepareStopVPN() { if (!BuildConfig.DEBUG) { try { String download = trafficIn.getText().toString(); download = download.substring(download.lastIndexOf(":") + 2); Answers.getInstance().logCustom(new CustomEvent("Connection info") .putCustomAttribute("Country", connectedServer.getCountryLong()) .putCustomAttribute("Download", download) .putCustomAttribute("Time", stopwatch.getElapsedTime())); } catch (Exception e) { } } statusConnection = false; if (waitConnection != null) waitConnection.cancel(false); connectingProgress.setVisibility(View.GONE); adbBlockCheck.setEnabled(availableFilterAds); lastLog.setText(R.string.server_not_connected); serverConnect.setText(getString(R.string.server_btn_connect)); connectedServer = null; } private void stopVpn() { //prepareStopVPN(); ProfileManager.setConntectedVpnProfileDisconnected(this); if (mVPNService != null && mVPNService.getManagement() != null) mVPNService.getManagement().stopVPN(false); } private void startVpn() { stopwatch = new Stopwatch(); connectedServer = currentServer; hideCurrentConnection = true; adbBlockCheck.setEnabled(false); Intent intent = VpnService.prepare(this); if (intent != null) { VpnStatus.updateStateString("USER_VPN_PERMISSION", "", R.string.state_user_vpn_permission, VpnStatus.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT); // Start the query try { startActivityForResult(intent, START_VPN_PROFILE); } catch (ActivityNotFoundException ane) { // Shame on you Sony! At least one user reported that // an official Sony Xperia Arc S image triggers this exception VpnStatus.logError(R.string.no_vpn_support_image); } } else { onActivityResult(START_VPN_PROFILE, Activity.RESULT_OK, null); } } @Override protected void ipInfoResult() { ((TextView) findViewById(R.id.serverCity)).setText(currentServer.getCity()); } @Override protected void onResume() { super.onResume(); inBackground = false; if (currentServer.getCity() == null) getIpInfo(currentServer); if (connectedServer != null && currentServer.getIp().equals(connectedServer.getIp())) { hideCurrentConnection = true; invalidateOptionsMenu(); } Intent intent = new Intent(this, OpenVPNService.class); intent.setAction(OpenVPNService.START_SERVICE); isBindedService = bindService(intent, mConnection, Context.BIND_AUTO_CREATE); if (checkStatus()) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } if (!checkStatus()) { connectedServer = null; serverConnect.setText(getString(R.string.server_btn_connect)); lastLog.setText(R.string.server_not_connected); } } else { serverConnect.setText(getString(R.string.server_btn_connect)); if (autoConnection) { prepareVpn(); } } } @Override protected void onPause() { super.onPause(); inBackground = true; if (isBindedService) { isBindedService = false; unbindService(mConnection); } } @Override protected void onDestroy() { super.onDestroy(); unregisterReceiver(br); unregisterReceiver(trafficReceiver); if ( popupWindow != null && popupWindow.isShowing() ){ popupWindow.dismiss(); } } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (resultCode == RESULT_OK) { switch (requestCode) { case START_VPN_PROFILE : VPNLaunchHelper.startOpenVpn(vpnProfile, getBaseContext()); break; case ADBLOCK_REQUEST: Log.d(IAP_TAG, "onActivityResult(" + requestCode + "," + resultCode + "," + data); if (iapHelper == null) return; if (iapHelper.handleActivityResult(requestCode, resultCode, data)) { Log.d(IAP_TAG, "onActivityResult handled by IABUtil."); checkAvailableFilter(); } break; } } } private void chooseAction() { LayoutInflater inflater = (LayoutInflater) getApplicationContext().getSystemService(LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(R.layout.pop_up_success_conected,null); popupWindow = new PopupWindow( view, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT ); popupWindow.setOutsideTouchable(false); popupWindow.setFocusable(true); popupWindow.setBackgroundDrawable(new BitmapDrawable()); Button marketButton = (Button)view.findViewById(R.id.successPopUpBtnPlayMarket); marketButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendTouchButton("successPopUpBtnPlayMarket"); final String appPackageName = getPackageName(); try { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + appPackageName))); } catch (android.content.ActivityNotFoundException anfe) { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=" + appPackageName))); } } }); if (BuildConfig.FLAVOR == "underground") marketButton.setVisibility(View.GONE); ((Button)view.findViewById(R.id.successPopUpBtnBrowser)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendTouchButton("successPopUpBtnBrowser"); startActivity( new Intent( Intent.ACTION_VIEW, Uri.parse("http://google.com"))); } }); ((Button)view.findViewById(R.id.successPopUpBtnDesktop)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendTouchButton("successPopUpBtnDesktop"); Intent startMain = new Intent(Intent.ACTION_MAIN); startMain.addCategory(Intent.CATEGORY_HOME); startMain.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); startActivity(startMain); } }); ((Button)view.findViewById(R.id.successPopUpBtnClose)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { sendTouchButton("successPopUpBtnClose"); popupWindow.dismiss(); } }); popupWindow.showAtLocation(parentLayout, Gravity.CENTER,0, 0); } private void showRating() { LayoutInflater inflater = (LayoutInflater) getApplicationContext().getSystemService(LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(R.layout.pop_up_rating,null); popupWindow = new PopupWindow( view, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT ); popupWindow.setOutsideTouchable(false); popupWindow.setFocusable(true); popupWindow.setBackgroundDrawable(new BitmapDrawable()); ((Button)view.findViewById(R.id.ratingBtnSure)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { popupWindow.dismiss(); final String appPackageName = getPackageName(); try { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + appPackageName))); } catch (android.content.ActivityNotFoundException anfe) { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://play.google.com/store/apps/details?id=" + appPackageName))); } } }); ((Button)view.findViewById(R.id.ratingBtnNot)).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { popupWindow.dismiss(); } }); popupWindow.showAtLocation(parentLayout, Gravity.CENTER,0, 0); } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { // We've bound to LocalService, cast the IBinder and get LocalService instance OpenVPNService.LocalBinder binder = (OpenVPNService.LocalBinder) service; mVPNService = binder.getService(); } @Override public void onServiceDisconnected(ComponentName arg0) { mVPNService = null; } }; private class WaitConnectionAsync extends AsyncTask { @Override protected Void doInBackground(Void... params) { try { TimeUnit.SECONDS.sleep(PropertiesService.getAutomaticSwitchingSeconds()); } catch (InterruptedException e) { e.printStackTrace(); } return null; } @Override protected void onPostExecute(Void aVoid) { super.onPostExecute(aVoid); if (!statusConnection) { if (currentServer != null) dbHelper.setInactive(currentServer.getIp()); if (fastConnection) { stopVpn(); newConnecting(getRandomServer(), true, true); } else if (PropertiesService.getAutomaticSwitching()){ if (!inBackground) showAlert(); } } } } private void showAlert() { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(getString(R.string.try_another_server_text)) .setPositiveButton(getString(R.string.try_another_server_ok), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { dialog.cancel(); stopVpn(); autoServer = dbHelper.getSimilarServer(currentServer.getCountryLong(), currentServer.getIp()); if (autoServer != null) { newConnecting(autoServer, false, true); } else { onBackPressed(); } } }) .setNegativeButton(getString(R.string.try_another_server_no), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { if (!statusConnection) { waitConnection = new WaitConnectionAsync(); waitConnection.execute(); } dialog.cancel(); } }); AlertDialog alert = builder.create(); alert.show(); } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/activity/ServersInfo.java ================================================ package com.vasilkoff.easyvpnfree.activity; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.DisplayMetrics; import android.view.ViewGroup; import android.widget.TextView; import com.vasilkoff.easyvpnfree.R; import com.vasilkoff.easyvpnfree.database.DBHelper; public class ServersInfo extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_servers_info); DisplayMetrics dm = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(dm); int widthWindow = dm.widthPixels; int heightWindow = dm.heightPixels; if (getResources().getConfiguration().orientation == 1) { getWindow().setLayout((int)(widthWindow * 0.7), (int)(heightWindow * 0.3)); } else { getWindow().setLayout((int)(widthWindow * 0.4), (int)(heightWindow * 0.5)); } DBHelper dbHelper = new DBHelper(this); String basicServers = String.format(getResources().getString(R.string.info_basic), dbHelper.getCountBasic()); ((TextView) findViewById(R.id.infoBasic)).setText(basicServers); String additionalServers = String.format(getResources().getString(R.string.info_additional), dbHelper.getCountAdditional()); ((TextView) findViewById(R.id.infoAdditional)).setText(additionalServers); } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/activity/ServersListActivity.java ================================================ package com.vasilkoff.easyvpnfree.activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; import com.vasilkoff.easyvpnfree.R; import com.vasilkoff.easyvpnfree.adapter.ServerListAdapter; import com.vasilkoff.easyvpnfree.model.Server; import java.util.List; import de.blinkt.openvpn.core.VpnStatus; public class ServersListActivity extends BaseActivity { private ListView listView; private ServerListAdapter serverListAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_servers_list); if (!VpnStatus.isVPNActive()) connectedServer = null; listView = (ListView) findViewById(R.id.list); } @Override protected void onResume() { super.onResume(); invalidateOptionsMenu(); buildList(); } @Override protected void ipInfoResult() { serverListAdapter.notifyDataSetChanged(); } private void buildList() { String country = getIntent().getStringExtra(HomeActivity.EXTRA_COUNTRY); final List serverList = dbHelper.getServersByCountryCode(country); serverListAdapter = new ServerListAdapter(this, serverList); listView.setAdapter(serverListAdapter); listView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView parent, View view, int position, long id) { Server server = serverList.get(position); BaseActivity.sendTouchButton("detailsServer"); Intent intent = new Intent(ServersListActivity.this, ServerActivity.class); intent.putExtra(Server.class.getCanonicalName(), server); ServersListActivity.this.startActivity(intent); } }); getIpInfo(serverList); } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/adapter/BookmarkServerListAdapter.java ================================================ package com.vasilkoff.easyvpnfree.adapter; import android.content.Context; import android.content.Intent; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.Button; import android.widget.ImageView; import android.widget.TextView; import com.vasilkoff.easyvpnfree.R; import com.vasilkoff.easyvpnfree.activity.ServerActivity; import com.vasilkoff.easyvpnfree.database.DBHelper; import com.vasilkoff.easyvpnfree.model.Server; import com.vasilkoff.easyvpnfree.util.ConnectionQuality; import com.vasilkoff.easyvpnfree.util.CountriesNames; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Created by Kusenko on 22.01.2017. */ public class BookmarkServerListAdapter extends RecyclerView.Adapter{ private List serverList = new ArrayList(); private Context context; private Map localeCountries; private DBHelper dbHelper; public BookmarkServerListAdapter(List serverList, Context context, DBHelper dbHelper) { this.serverList = serverList; this.context = context; this.dbHelper = dbHelper; localeCountries = CountriesNames.getCountries(); } @Override public BookmarkHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.bookmark_server_row, parent, false); return new BookmarkHolder(v); } @Override public void onBindViewHolder(BookmarkHolder holder, final int position) { final Server server = serverList.get(position); String code = server.getCountryShort().toLowerCase(); if (code.equals("do")) code = "dom"; holder.flag.setImageResource( context.getResources().getIdentifier(code, "drawable", context.getPackageName())); holder.quality.setImageResource( context.getResources().getIdentifier(ConnectionQuality.getConnectIcon(server.getQuality()), "drawable", context.getPackageName())); holder.host.setText(server.getHostName()); holder.ip.setText(server.getIp()); holder.city.setText(server.getCity()); String localeCountryName = localeCountries.get(server.getCountryShort()) != null ? localeCountries.get(server.getCountryShort()) : server.getCountryLong(); holder.country.setText(localeCountryName); holder.delete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { dbHelper.delBookmark(server); serverList.remove(position); notifyDataSetChanged(); } }); } @Override public int getItemCount() { return serverList.size(); } class BookmarkHolder extends RecyclerView.ViewHolder { ImageView flag; ImageView quality; TextView host; TextView ip; TextView city; TextView country; Button delete; public BookmarkHolder(View v) { super(v); flag = (ImageView) v.findViewById(R.id.bookmarkFlag); quality = (ImageView) v.findViewById(R.id.bookmarkConnect); host = (TextView) v.findViewById(R.id.bookmarkHostName); ip = (TextView) v.findViewById(R.id.bookmarkIP); city = (TextView) v.findViewById(R.id.bookmarkCity); country = (TextView) v.findViewById(R.id.bookmarkCountry); delete = (Button) v.findViewById(R.id.bookmarkDelete); v.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent intent = new Intent(context, ServerActivity.class); intent.putExtra(Server.class.getCanonicalName(), serverList.get(getAdapterPosition())); context.startActivity(intent); } }); } } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/adapter/ServerListAdapter.java ================================================ package com.vasilkoff.easyvpnfree.adapter; import android.content.Context; import android.support.v4.content.ContextCompat; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.ImageView; import android.widget.TextView; import com.vasilkoff.easyvpnfree.R; import com.vasilkoff.easyvpnfree.activity.BaseActivity; import com.vasilkoff.easyvpnfree.model.Server; import com.vasilkoff.easyvpnfree.util.ConnectionQuality; import com.vasilkoff.easyvpnfree.util.CountriesNames; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * Created by Vasilkoff on 27/09/16. */ public class ServerListAdapter extends BaseAdapter { private LayoutInflater inflater; private List serverList = new ArrayList(); private Context context; private Map localeCountries; public ServerListAdapter(Context c, List serverList) { inflater = LayoutInflater.from(c); context = c; this.serverList = serverList; localeCountries = CountriesNames.getCountries(); } @Override public int getCount() { return serverList.size(); } @Override public Server getItem(int position) { return serverList.get(position); } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View v, ViewGroup parent) { v = inflater.inflate(R.layout.layout_server_record_row, parent, false); final Server server = getItem(position); String code = server.getCountryShort().toLowerCase(); if (code.equals("do")) code = "dom"; ((ImageView) v.findViewById(R.id.imageFlag)) .setImageResource( context.getResources().getIdentifier(code, "drawable", context.getPackageName())); ((ImageView) v.findViewById(R.id.imageConnect)) .setImageResource( context.getResources().getIdentifier(ConnectionQuality.getConnectIcon(server.getQuality()), "drawable", context.getPackageName())); ((TextView) v.findViewById(R.id.textHostName)).setText(server.getHostName()); ((TextView) v.findViewById(R.id.textIP)).setText(server.getIp()); ((TextView) v.findViewById(R.id.textCity)).setText(server.getCity()); String localeCountryName = localeCountries.get(server.getCountryShort()) != null ? localeCountries.get(server.getCountryShort()) : server.getCountryLong(); ((TextView) v.findViewById(R.id.textCountry)).setText(localeCountryName); if (BaseActivity.connectedServer != null && BaseActivity.connectedServer.getHostName().equals(server.getHostName())) { v.setBackgroundColor(ContextCompat.getColor(context, R.color.activeServer)); } if (server.getType() == 1) { v.setBackgroundColor(ContextCompat.getColor(context, R.color.additionalServer)); } return v; } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/database/DBHelper.java ================================================ package com.vasilkoff.easyvpnfree.database; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteStatement; import android.util.Log; import com.vasilkoff.easyvpnfree.model.Country; import com.vasilkoff.easyvpnfree.model.Server; import com.vasilkoff.easyvpnfree.util.ConnectionQuality; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.List; import java.util.Random; /** * Created by Kusenko on 29.09.2016. */ public class DBHelper extends SQLiteOpenHelper { private static final int DATABASE_VERSION = 6; private static final String DATABASE_NAME = "Records.db"; private static final String TABLE_SERVERS = "servers"; private static final String TABLE_BOOKMARK_SERVERS = "bookmark_servers"; private static final String TAG = "DBHelper"; private static final String KEY_ID = "_id"; private static final String KEY_HOST_NAME = "hostName"; private static final String KEY_IP = "ip"; private static final String KEY_SCORE = "score"; private static final String KEY_PING = "ping"; private static final String KEY_SPEED = "speed"; private static final String KEY_COUNTRY_LONG = "countryLong"; private static final String KEY_COUNTRY_SHORT = "countryShort"; private static final String KEY_NUM_VPN_SESSIONS = "numVpnSessions"; private static final String KEY_UPTIME = "uptime"; private static final String KEY_TOTAL_USERS = "totalUsers"; private static final String KEY_TOTAL_TRAFFIC = "totalTraffic"; private static final String KEY_LOG_TYPE = "logType"; private static final String KEY_OPERATOR = "operator"; private static final String KEY_MESSAGE = "message"; private static final String KEY_CONFIG_DATA = "configData"; private static final String KEY_TYPE = "type"; private static final String KEY_QUALITY = "quality"; private static final String KEY_CITY = "city"; private static final String KEY_REGION_NAME = "regionName"; private static final String KEY_LAT = "lat"; private static final String KEY_LON = "lon"; public DBHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } @Override public void onCreate(SQLiteDatabase db) { createTable(db, TABLE_SERVERS); createTable(db, TABLE_BOOKMARK_SERVERS); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { db.execSQL("drop table if exists " + TABLE_SERVERS); db.execSQL("drop table if exists " + TABLE_BOOKMARK_SERVERS); onCreate(db); } private void createTable(SQLiteDatabase db, String name) { db.execSQL("create table " + name + "(" + KEY_ID + " integer primary key," + KEY_HOST_NAME + " text," + KEY_IP + " text," + KEY_SCORE + " text," + KEY_PING + " text," + KEY_SPEED + " text," + KEY_COUNTRY_LONG + " text," + KEY_COUNTRY_SHORT + " text," + KEY_NUM_VPN_SESSIONS + " text," + KEY_UPTIME + " text," + KEY_TOTAL_USERS + " text," + KEY_TOTAL_TRAFFIC + " text," + KEY_LOG_TYPE + " text," + KEY_OPERATOR + " text," + KEY_MESSAGE + " text," + KEY_CONFIG_DATA + " text," + KEY_QUALITY + " integer," + KEY_CITY + " text," + KEY_TYPE + " integer," + KEY_REGION_NAME + " text," + KEY_LAT + " real," + KEY_LON + " real," + "UNIQUE (" + KEY_HOST_NAME + ") ON CONFLICT IGNORE" + ")"); } public void setInactive(String ip) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues values = new ContentValues(); values.put(KEY_QUALITY, 0); db.update(TABLE_SERVERS, values, KEY_IP + " = ?", new String[] {ip}); db.close(); } public boolean setIpInfo(JSONArray response, List serverList) { boolean result = false; SQLiteDatabase db = this.getWritableDatabase(); for (int i = 0; i < response.length(); i++) { try { JSONObject ipInfo = new JSONObject(response.get(i).toString()); String city = ipInfo.get(KEY_CITY).toString(); ContentValues values = new ContentValues(); values.put(KEY_CITY, city); values.put(KEY_REGION_NAME, ipInfo.get(KEY_REGION_NAME).toString()); values.put(KEY_LAT, ipInfo.getDouble(KEY_LAT)); values.put(KEY_LON, ipInfo.getDouble(KEY_LON)); db.update(TABLE_SERVERS, values, KEY_IP + " = ?", new String[] {ipInfo.get("query").toString()}); serverList.get(i).setCity(city); result = true; } catch (JSONException e) { result = false; e.printStackTrace(); } } db.close(); return result; } public void clearTable() { SQLiteDatabase db = this.getWritableDatabase(); db.delete(TABLE_SERVERS, null, null); db.close(); } public void setBookmark(Server server) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues contentValues = new ContentValues(); contentValues.put(KEY_HOST_NAME, server.getHostName()); contentValues.put(KEY_IP, server.getIp()); contentValues.put(KEY_SCORE, server.getScore()); contentValues.put(KEY_PING, server.getPing()); contentValues.put(KEY_SPEED, server.getSpeed()); contentValues.put(KEY_COUNTRY_LONG, server.getCountryLong()); contentValues.put(KEY_COUNTRY_SHORT, server.getCountryShort()); contentValues.put(KEY_NUM_VPN_SESSIONS, server.getNumVpnSessions()); contentValues.put(KEY_UPTIME, server.getUptime()); contentValues.put(KEY_TOTAL_USERS, server.getTotalUsers()); contentValues.put(KEY_TOTAL_TRAFFIC, server.getTotalTraffic()); contentValues.put(KEY_LOG_TYPE, server.getLogType()); contentValues.put(KEY_OPERATOR, server.getOperator()); contentValues.put(KEY_MESSAGE, server.getMessage()); contentValues.put(KEY_CONFIG_DATA, server.getConfigData()); contentValues.put(KEY_TYPE, server.getType()); contentValues.put(KEY_QUALITY, server.getQuality()); contentValues.put(KEY_CITY, server.getCity()); db.insert(TABLE_BOOKMARK_SERVERS, null, contentValues); db.close(); } public void delBookmark(Server server) { SQLiteDatabase db = this.getWritableDatabase(); db.delete(TABLE_BOOKMARK_SERVERS, KEY_IP + " = ?", new String[] {server.getIp()}); db.close(); } public List getBookmarks() { List serverList = new ArrayList(); SQLiteDatabase db = this.getWritableDatabase(); Cursor cursor = db.query(TABLE_BOOKMARK_SERVERS, null, null, null, null, null, null); if (cursor.moveToFirst()) { do { serverList.add(parseServer(cursor)); } while (cursor.moveToNext()); } else { Log.d(TAG ,"0 rows"); } cursor.close(); db.close(); return serverList; } public boolean checkBookmark(Server server) { boolean result = false; SQLiteDatabase db = this.getWritableDatabase(); Cursor cursor = db.query(TABLE_BOOKMARK_SERVERS, null, KEY_IP + "=?", new String[]{server.getIp()}, null, null, null); if (cursor.moveToFirst()) { result = true; } else { Log.d(TAG ,"0 rows"); } cursor.close(); db.close(); return result; } public void putLine(String line, int type) { String[] data = line.split(","); if (data.length == 15) { SQLiteDatabase db = this.getWritableDatabase(); ContentValues contentValues = new ContentValues(); contentValues.put(KEY_HOST_NAME, data[0]); contentValues.put(KEY_IP, data[1]); contentValues.put(KEY_SCORE, data[2]); contentValues.put(KEY_PING, data[3]); contentValues.put(KEY_SPEED, data[4]); contentValues.put(KEY_COUNTRY_LONG, data[5]); contentValues.put(KEY_COUNTRY_SHORT, data[6]); contentValues.put(KEY_NUM_VPN_SESSIONS, data[7]); contentValues.put(KEY_UPTIME, data[8]); contentValues.put(KEY_TOTAL_USERS, data[9]); contentValues.put(KEY_TOTAL_TRAFFIC, data[10]); contentValues.put(KEY_LOG_TYPE, data[11]); contentValues.put(KEY_OPERATOR, data[12]); contentValues.put(KEY_MESSAGE, data[13]); contentValues.put(KEY_CONFIG_DATA, data[14]); contentValues.put(KEY_TYPE, type); contentValues.put(KEY_QUALITY, ConnectionQuality.getConnectionQuality(data[4], data[7], data[3])); db.insert(TABLE_SERVERS, null, contentValues); db.close(); } } public long getCount() { SQLiteDatabase db = this.getWritableDatabase(); SQLiteStatement statement = db.compileStatement("SELECT COUNT(*) FROM " + TABLE_SERVERS); long count = statement.simpleQueryForLong(); db.close(); return count; } public long getCountBasic() { SQLiteDatabase db = this.getWritableDatabase(); SQLiteStatement statement = db.compileStatement("SELECT COUNT(*) FROM " + TABLE_SERVERS + " WHERE " + KEY_TYPE + " = 0"); long count = statement.simpleQueryForLong(); db.close(); return count; } public long getCountAdditional() { SQLiteDatabase db = this.getWritableDatabase(); SQLiteStatement statement = db.compileStatement("SELECT COUNT(*) FROM " + TABLE_SERVERS + " WHERE " + KEY_TYPE + " = 1"); long count = statement.simpleQueryForLong(); db.close(); return count; } public List getUniqueCountries() { List countryList = new ArrayList(); SQLiteDatabase db = this.getWritableDatabase(); Cursor cursor = db.query(TABLE_SERVERS, null, null, null, KEY_COUNTRY_LONG, "MAX(" + KEY_QUALITY + ")", null); if (cursor.moveToFirst()) { do { countryList.add(parseServer(cursor)); } while (cursor.moveToNext()); } else { Log.d(TAG ,"0 rows"); } cursor.close(); db.close(); return countryList; } public List getServersWithGPS() { List serverList = new ArrayList(); SQLiteDatabase db = this.getWritableDatabase(); Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_SERVERS + " WHERE " + KEY_LAT + " <> 0", null); if (cursor.moveToFirst()) { do { serverList.add(parseServer(cursor)); } while (cursor.moveToNext()); } else { Log.d(TAG ,"0 rows"); } cursor.close(); db.close(); return serverList; } public List getServersByCountryCode(String country) { List serverList = new ArrayList(); if (country != null) { SQLiteDatabase db = this.getWritableDatabase(); Cursor cursor = db.query(TABLE_SERVERS, null, KEY_COUNTRY_SHORT + "=?", new String[]{country}, null, null, KEY_QUALITY + " DESC"); if (cursor.moveToFirst()) { do { serverList.add(parseServer(cursor)); } while (cursor.moveToNext()); } else { Log.d(TAG ,"0 rows"); } cursor.close(); db.close(); } return serverList; } private Server parseGoodRandomServer(Cursor cursor, SQLiteDatabase db) { List serverListExcellent = new ArrayList(); List serverListGood = new ArrayList(); List serverListBad = new ArrayList(); if (cursor.moveToFirst()) { do { switch (cursor.getInt(16)) { case 1: serverListBad.add(parseServer(cursor)); break; case 2: serverListGood.add(parseServer(cursor)); break; case 3: serverListExcellent.add(parseServer(cursor)); break; } } while (cursor.moveToNext()); } else { Log.d(TAG ,"0 rows"); } cursor.close(); db.close(); Random random = new Random(); if (serverListExcellent.size() > 0) { return serverListExcellent.get(random.nextInt(serverListExcellent.size())); } else if (serverListGood.size() > 0) { return serverListGood.get(random.nextInt(serverListGood.size())); } else if (serverListBad.size() > 0) { return serverListBad.get(random.nextInt(serverListBad.size())); } return null; } public Server getSimilarServer(String country, String ip) { SQLiteDatabase db = this.getWritableDatabase(); Cursor cursor = db.rawQuery("SELECT * FROM " + TABLE_SERVERS + " WHERE " + KEY_QUALITY + " <> 1 AND " + KEY_COUNTRY_LONG + " = ? AND " + KEY_IP + " <> ?", new String[] {country, ip}); return parseGoodRandomServer(cursor, db); } public Server getGoodRandomServer(String country) { SQLiteDatabase db = this.getWritableDatabase(); Cursor cursor; if (country != null) { cursor = db.rawQuery("SELECT * FROM " + TABLE_SERVERS + " WHERE " + KEY_QUALITY + " <> 0 AND " + KEY_COUNTRY_LONG + " = ?", new String[] {country}); } else { cursor = db.rawQuery("SELECT * FROM " + TABLE_SERVERS + " WHERE " + KEY_QUALITY + " <> 0", null); } return parseGoodRandomServer(cursor, db); } private Server parseServer(Cursor cursor) { return new Server( cursor.getString(1), cursor.getString(2), cursor.getString(3), cursor.getString(4), cursor.getString(5), cursor.getString(6), cursor.getString(7), cursor.getString(8), cursor.getString(9), cursor.getString(10), cursor.getString(11), cursor.getString(12), cursor.getString(13), cursor.getString(14), cursor.getString(15), cursor.getInt(16), cursor.getString(17), cursor.getInt(18), cursor.getString(19), cursor.getDouble(20), cursor.getDouble(21) ); } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/model/Country.java ================================================ package com.vasilkoff.easyvpnfree.model; /** * Created by Kusenko on 01.10.2016. */ public class Country { private String CountryName; private String CapitalName; private double CapitalLatitude; private double CapitalLongitude; private String CountryCode; public Country(String countryName, String capitalName, double capitalLatitude, double capitalLongitude, String countryCode) { CountryName = countryName; CapitalName = capitalName; CapitalLatitude = capitalLatitude; CapitalLongitude = capitalLongitude; CountryCode = countryCode; } public String getCountryName() { return CountryName; } public String getCapitalName() { return CapitalName; } public double getCapitalLatitude() { return CapitalLatitude; } public double getCapitalLongitude() { return CapitalLongitude; } public String getCountryCode() { return CountryCode; } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/model/Server.java ================================================ package com.vasilkoff.easyvpnfree.model; import android.os.Parcel; import android.os.Parcelable; /** * Created by Kusenko on 29.09.2016. */ public class Server implements Parcelable { private String hostName; private String ip; private String score; private String ping; private String speed; private String countryLong; private String countryShort; private String numVpnSessions; private String uptime; private String totalUsers; private String totalTraffic; private String logType; private String operator; private String message; private String configData; private int quality; private String city; private int type; private String regionName; private double lat; private double lon; public Server(String hostName, String ip, String score, String ping, String speed, String countryLong, String countryShort, String numVpnSessions, String uptime, String totalUsers, String totalTraffic, String logType, String operator, String message, String configData, int quality, String city, int type, String regionName, double lat, double lon) { this.hostName = hostName; this.ip = ip; this.score = score; this.ping = ping; this.speed = speed; this.countryLong = countryLong; this.countryShort = countryShort; this.numVpnSessions = numVpnSessions; this.uptime = uptime; this.totalUsers = totalUsers; this.totalTraffic = totalTraffic; this.logType = logType; this.operator = operator; this.message = message; this.configData = configData; this.quality = quality; this.city = city; this.type = type; this.regionName = regionName; this.lat = lat; this.lon = lon; } protected Server(Parcel in) { hostName = in.readString(); ip = in.readString(); score = in.readString(); ping = in.readString(); speed = in.readString(); countryLong = in.readString(); countryShort = in.readString(); numVpnSessions = in.readString(); uptime = in.readString(); totalUsers = in.readString(); totalTraffic = in.readString(); logType = in.readString(); operator = in.readString(); message = in.readString(); configData = in.readString(); quality = in.readInt(); city = in.readString(); type = in.readInt(); regionName = in.readString(); lat = in.readDouble(); lon = in.readDouble(); } public static final Creator CREATOR = new Creator() { @Override public Server createFromParcel(Parcel in) { return new Server(in); } @Override public Server[] newArray(int size) { return new Server[size]; } }; public String getHostName() { return hostName; } public void setHostName(String hostName) { this.hostName = hostName; } public String getIp() { return ip; } public void setIp(String ip) { this.ip = ip; } public String getScore() { return score; } public void setScore(String score) { this.score = score; } public String getPing() { return ping; } public void setPing(String ping) { this.ping = ping; } public String getSpeed() { return speed; } public void setSpeed(String speed) { this.speed = speed; } public String getCountryLong() { return countryLong; } public void setCountryLong(String countryLong) { this.countryLong = countryLong; } public String getCountryShort() { return countryShort; } public void setCountryShort(String countryShort) { this.countryShort = countryShort; } public String getNumVpnSessions() { return numVpnSessions; } public void setNumVpnSessions(String numVpnSessions) { this.numVpnSessions = numVpnSessions; } public String getUptime() { return uptime; } public void setUptime(String uptime) { this.uptime = uptime; } public String getTotalUsers() { return totalUsers; } public void setTotalUsers(String totalUsers) { this.totalUsers = totalUsers; } public String getTotalTraffic() { return totalTraffic; } public void setTotalTraffic(String totalTraffic) { this.totalTraffic = totalTraffic; } public String getLogType() { return logType; } public void setLogType(String logType) { this.logType = logType; } public String getOperator() { return operator; } public void setOperator(String operator) { this.operator = operator; } public String getMessage() { return message; } public void setMessage(String message) { this.message = message; } public String getConfigData() { return configData; } public void setConfigData(String configData) { this.configData = configData; } public int getQuality() { return quality; } public void setQuality(int quality) { this.quality = quality; } public String getCity() { return city; } public void setCity(String city) { this.city = city; } public int getType() { return type; } public void setType(int type) { this.type = type; } public String getRegionName() { return regionName; } public void setRegionName(String regionName) { this.regionName = regionName; } public double getLat() { return lat; } public void setLat(double lat) { this.lat = lat; } public double getLon() { return lon; } public void setLon(double lon) { this.lon = lon; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(hostName); dest.writeString(ip); dest.writeString(score); dest.writeString(ping); dest.writeString(speed); dest.writeString(countryLong); dest.writeString(countryShort); dest.writeString(numVpnSessions); dest.writeString(uptime); dest.writeString(totalUsers); dest.writeString(totalTraffic); dest.writeString(logType); dest.writeString(operator); dest.writeString(message); dest.writeString(configData); dest.writeInt(quality); dest.writeString(city); dest.writeInt(type); dest.writeString(regionName); dest.writeDouble(lat); dest.writeDouble(lon); } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/BitmapGenerator.java ================================================ package com.vasilkoff.easyvpnfree.util; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; public class BitmapGenerator { private static Paint paint = new Paint(); public static Bitmap getTextAsBitmap(String text, float textSize, int textColor) { String textUp = text.toUpperCase(); paint.setTextSize(textSize); paint.setColor(textColor); paint.setTextAlign(Paint.Align.LEFT); float baseline = -paint.ascent(); // ascent() is negative int width = (int) (paint.measureText(textUp) + 0.5f); // round int height = (int) (baseline + paint.descent() + 25.5f); Bitmap image = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(image); canvas.drawText(textUp, 0, baseline, paint); return image; } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/ConnectionQuality.java ================================================ package com.vasilkoff.easyvpnfree.util; /** * Created by Kusenko on 09.12.2016. */ public class ConnectionQuality { private static final String CONNECT_BAD = "ic_connect_bad"; private static final String CONNECT_GOOD = "ic_connect_good"; private static final String CONNECT_EXCELLENT = "ic_connect_excellent"; private static final String CONNECT_INACTIVE = "ic_connect_inactive"; private static final String POINT_BAD = "ic_point_red"; private static final String POINT_GOOD = "ic_point_yellow"; private static final String POINT_EXCELLENT = "ic_point_green"; private static final String POINT_INACTIVE = "ic_point_grey"; private static final String SIMPLE_POINT_BAD = "ic_simple_point_red"; private static final String SIMPLE_POINT_GOOD = "ic_simple_point_yellow"; private static final String SIMPLE_POINT_EXCELLENT = "ic_simple_point_green"; private static final String SIMPLE_POINT_INACTIVE = "ic_simple_point_grey"; public static String getConnectIcon(int quality) { switch (quality) { case 0: return CONNECT_INACTIVE; case 1: return CONNECT_BAD; case 2: return CONNECT_GOOD; case 3: return CONNECT_EXCELLENT; default: return CONNECT_INACTIVE; } } public static String getPointIcon(int quality) { switch (quality) { case 0: return POINT_INACTIVE; case 1: return POINT_BAD; case 2: return POINT_GOOD; case 3: return POINT_EXCELLENT; default: return POINT_INACTIVE; } } public static String getSimplePointIcon(int quality) { switch (quality) { case 0: return SIMPLE_POINT_INACTIVE; case 1: return SIMPLE_POINT_BAD; case 2: return SIMPLE_POINT_GOOD; case 3: return SIMPLE_POINT_EXCELLENT; default: return SIMPLE_POINT_INACTIVE; } } public static int getConnectionQuality(String speedStr, String sessionsStr, String pingStr) { int speed = Integer.parseInt(speedStr); int sessions = Integer.parseInt(sessionsStr); int ping = 0; if (!(pingStr.equals("-") || pingStr.equals(""))) { ping = Integer.parseInt(pingStr); } if (speed > 10000000 && ping < 30 && (sessions != 0 && sessions < 100)) { return 3; } else if (speed < 1000000 || ping > 100 || (sessions == 0 || sessions > 150)) { return 1; } else { return 2; } } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/CountriesNames.java ================================================ package com.vasilkoff.easyvpnfree.util; import java.util.HashMap; import java.util.Locale; import java.util.Map; /** * Created by Kusenko on 26.12.2016. */ public class CountriesNames { public static Map getCountries() { Map countries = new HashMap(); String[] isoCountries = Locale.getISOCountries(); for (String country : isoCountries) { Locale locale = new Locale("", country); String iso = locale.getISO3Country(); String code = locale.getCountry(); String name = locale.getDisplayCountry(); if (!"".equals(iso) && !"".equals(code) && !"".equals(name)) { countries.put(code, name); } } return countries; } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/LoadData.java ================================================ package com.vasilkoff.easyvpnfree.util; import android.content.Context; import java.io.IOException; import java.io.InputStream; public class LoadData { public static String fromFile(String nameFile, Context context) { String data = null; try { InputStream is = context.getAssets().open(nameFile); int size = is.available(); byte[] buffer = new byte[size]; is.read(buffer); is.close(); data = new String(buffer, "UTF-8"); } catch (IOException ex) { ex.printStackTrace(); return null; } return data; } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/NetworkState.java ================================================ package com.vasilkoff.easyvpnfree.util; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; import com.vasilkoff.easyvpnfree.App; public class NetworkState { public static boolean isOnline() { ConnectivityManager cm = (ConnectivityManager) App.getInstance().getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo netInfo = cm.getActiveNetworkInfo(); if (netInfo != null && netInfo.isConnectedOrConnecting()) { return true; } return false; } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/NumberPickerPreference.java ================================================ package com.vasilkoff.easyvpnfree.util; import android.content.Context; import android.content.res.TypedArray; import android.preference.DialogPreference; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; import android.widget.NumberPicker; /** * Created by Kusenko on 19.12.2016. */ public class NumberPickerPreference extends DialogPreference { // allowed range public static final int MAX_VALUE = 120; public static final int MIN_VALUE = 10; // enable or disable the 'circular behavior' public static final boolean WRAP_SELECTOR_WHEEL = true; private NumberPicker picker; private int value; public NumberPickerPreference(Context context, AttributeSet attrs) { super(context, attrs); } public NumberPickerPreference(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected View onCreateDialogView() { FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); layoutParams.gravity = Gravity.CENTER; picker = new NumberPicker(getContext()); picker.setLayoutParams(layoutParams); FrameLayout dialogView = new FrameLayout(getContext()); dialogView.addView(picker); return dialogView; } @Override protected void onBindDialogView(View view) { super.onBindDialogView(view); picker.setMinValue(MIN_VALUE); picker.setMaxValue(MAX_VALUE); picker.setWrapSelectorWheel(WRAP_SELECTOR_WHEEL); picker.setValue(getValue()); } @Override protected void onDialogClosed(boolean positiveResult) { if (positiveResult) { picker.clearFocus(); int newValue = picker.getValue(); if (callChangeListener(newValue)) { setValue(newValue); } } } @Override protected Object onGetDefaultValue(TypedArray a, int index) { return a.getInt(index, MIN_VALUE); } @Override protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) { setValue(restorePersistedValue ? getPersistedInt(MIN_VALUE) : (Integer) defaultValue); } public void setValue(int value) { this.value = value; persistInt(this.value); } public int getValue() { return this.value; } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/PropertiesService.java ================================================ package com.vasilkoff.easyvpnfree.util; import android.content.Context; import android.content.SharedPreferences; import android.preference.PreferenceManager; import com.vasilkoff.easyvpnfree.App; /** * Created by Kusenko on 16.12.2016. */ public class PropertiesService { private static SharedPreferences prefs; private static final String DOWNLOADED_DATA_KEY = "downloaded_data"; private static final String UPLOADED_DATA_KEY = "uploaded_data"; private static final String AUTOMATIC_SWITCHING = "automaticSwitching"; private static final String COUNTRY_PRIORITY = "countryPriority"; private static final String CONNECT_ON_START = "connectOnStart"; private static final String AUTOMATIC_SWITCHING_SECONDS = "automaticSwitchingSeconds"; private static final String SELECTED_COUNTRY = "selectedCountry"; private static final String SHOW_RATING = "show_rating"; private static final String SHOW_NOTE = "show_note"; private synchronized static SharedPreferences getPrefs(){ if (prefs == null) { prefs = PreferenceManager.getDefaultSharedPreferences(App.getInstance()); } return prefs; } public static long getDownloaded(){ return getPrefs().getLong(DOWNLOADED_DATA_KEY, 0); } public static void setDownloaded(long count){ getPrefs().edit().putLong(DOWNLOADED_DATA_KEY, count).apply(); } public static long getUploaded(){ return getPrefs().getLong(UPLOADED_DATA_KEY, 0); } public static void setUploaded(long count){ getPrefs().edit().putLong(UPLOADED_DATA_KEY, count).apply(); } public static boolean getConnectOnStart(){ return getPrefs().getBoolean(CONNECT_ON_START, false); } public static boolean getAutomaticSwitching(){ return getPrefs().getBoolean(AUTOMATIC_SWITCHING, true); } public static int getAutomaticSwitchingSeconds(){ return getPrefs().getInt(AUTOMATIC_SWITCHING_SECONDS, 40); } public static boolean getCountryPriority(){ return getPrefs().getBoolean(COUNTRY_PRIORITY, false); } public static String getSelectedCountry(){ return getPrefs().getString(SELECTED_COUNTRY, null); } public static boolean getShowRating(){ return getPrefs().getBoolean(SHOW_RATING, true); } public static void setShowRating(boolean showRating){ getPrefs().edit().putBoolean(SHOW_RATING, showRating).apply(); } public static boolean getShowNote(){ return getPrefs().getBoolean(SHOW_NOTE, true); } public static void setShowNote(boolean showNote){ getPrefs().edit().putBoolean(SHOW_NOTE, showNote).apply(); } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/Stopwatch.java ================================================ package com.vasilkoff.easyvpnfree.util; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.TimeZone; public class Stopwatch { private SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); private Calendar calendar = Calendar.getInstance(); private long startTime = System.currentTimeMillis(); public Stopwatch() { sdf.setTimeZone(TimeZone.getTimeZone("UTC")); } public long getDiff() { return System.currentTimeMillis() - startTime; } public String getElapsedTime() { calendar.setTimeInMillis(getDiff()); return sdf.format(calendar.getTime()); } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/TotalTraffic.java ================================================ package com.vasilkoff.easyvpnfree.util; import android.content.Context; import android.content.Intent; import java.util.ArrayList; import java.util.List; import de.blinkt.openvpn.core.OpenVPNService; /** * Created by Kusenko on 16.12.2016. */ public class TotalTraffic { public static final String TRAFFIC_ACTION = "traffic_action"; public static final String DOWNLOAD_ALL = "download_all"; public static final String DOWNLOAD_SESSION = "download_session"; public static final String UPLOAD_ALL = "upload_all"; public static final String UPLOAD_SESSION = "upload_session"; public static long inTotal; public static long outTotal; public static void calcTraffic(Context context, long in, long out, long diffIn, long diffOut) { List totalTraffic = getTotalTraffic(diffIn, diffOut); Intent traffic = new Intent(); traffic.setAction(TRAFFIC_ACTION); traffic.putExtra(DOWNLOAD_ALL, totalTraffic.get(0)); traffic.putExtra(DOWNLOAD_SESSION, OpenVPNService.humanReadableByteCount(in, false)); traffic.putExtra(UPLOAD_ALL, totalTraffic.get(1)); traffic.putExtra(UPLOAD_SESSION, OpenVPNService.humanReadableByteCount(out, false)); context.sendBroadcast(traffic); } public static List getTotalTraffic() { return getTotalTraffic(0, 0); } public static List getTotalTraffic(long in, long out) { List totalTraffic = new ArrayList(); if (inTotal == 0) inTotal = PropertiesService.getDownloaded(); if (outTotal == 0) outTotal = PropertiesService.getUploaded(); inTotal = inTotal + in; outTotal = outTotal + out; totalTraffic.add(OpenVPNService.humanReadableByteCount(inTotal, false)); totalTraffic.add(OpenVPNService.humanReadableByteCount(outTotal, false)); return totalTraffic; } public static void saveTotal() { if (inTotal != 0) PropertiesService.setDownloaded(inTotal); if (outTotal != 0) PropertiesService.setUploaded(outTotal); } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/iap/Base64.java ================================================ // Portions copyright 2002, Google, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.vasilkoff.easyvpnfree.util.iap; // This code was converted from code at http://iharder.sourceforge.net/base64/ // Lots of extraneous features were removed. /* The original code said: *

* I am placing this code in the Public Domain. Do with it as you will. * This software comes with no guarantees or warranties but with * plenty of well-wishing instead! * Please visit * http://iharder.net/xmlizable * periodically to check for updates or to contribute improvements. *

* * @author Robert Harder * @author rharder@usa.net * @version 1.3 */ /** * Base64 converter class. This code is not a complete MIME encoder; * it simply converts binary data to base64 data and back. * *

Note {@link CharBase64} is a GWT-compatible implementation of this * class. */ public class Base64 { /** Specify encoding (value is {@code true}). */ public final static boolean ENCODE = true; /** Specify decoding (value is {@code false}). */ public final static boolean DECODE = false; /** The equals sign (=) as a byte. */ private final static byte EQUALS_SIGN = (byte) '='; /** The new line character (\n) as a byte. */ private final static byte NEW_LINE = (byte) '\n'; /** * The 64 valid Base64 values. */ private final static byte[] ALPHABET = {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '+', (byte) '/'}; /** * The 64 valid web safe Base64 values. */ private final static byte[] WEBSAFE_ALPHABET = {(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P', (byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j', (byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't', (byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) '-', (byte) '_'}; /** * Translates a Base64 value to either its 6-bit reconstruction value * or a negative number indicating some other meaning. **/ private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 -5, -5, // Whitespace: Tab and Linefeed -9, -9, // Decimal 11 - 12 -5, // Whitespace: Carriage Return -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 -9, -9, -9, -9, -9, // Decimal 27 - 31 -5, // Whitespace: Space -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42 62, // Plus sign at decimal 43 -9, -9, -9, // Decimal 44 - 46 63, // Slash at decimal 47 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine -9, -9, -9, // Decimal 58 - 60 -1, // Equals sign at decimal 61 -9, -9, -9, // Decimal 62 - 64 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' -9, -9, -9, -9, -9, -9, // Decimal 91 - 96 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' -9, -9, -9, -9, -9 // Decimal 123 - 127 /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ }; /** The web safe decodabet */ private final static byte[] WEBSAFE_DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8 -5, -5, // Whitespace: Tab and Linefeed -9, -9, // Decimal 11 - 12 -5, // Whitespace: Carriage Return -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26 -9, -9, -9, -9, -9, // Decimal 27 - 31 -5, // Whitespace: Space -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44 62, // Dash '-' sign at decimal 45 -9, -9, // Decimal 46-47 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine -9, -9, -9, // Decimal 58 - 60 -1, // Equals sign at decimal 61 -9, -9, -9, // Decimal 62 - 64 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N' 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z' -9, -9, -9, -9, // Decimal 91-94 63, // Underscore '_' at decimal 95 -9, // Decimal 96 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm' 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z' -9, -9, -9, -9, -9 // Decimal 123 - 127 /* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243 -9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */ }; // Indicates white space in encoding private final static byte WHITE_SPACE_ENC = -5; // Indicates equals sign in encoding private final static byte EQUALS_SIGN_ENC = -1; /** Defeats instantiation. */ private Base64() { } /* ******** E N C O D I N G M E T H O D S ******** */ /** * Encodes up to three bytes of the array source * and writes the resulting four Base64 bytes to destination. * The source and destination arrays can be manipulated * anywhere along their length by specifying * srcOffset and destOffset. * This method does not check to make sure your arrays * are large enough to accommodate srcOffset + 3 for * the source array or destOffset + 4 for * the destination array. * The actual number of significant bytes in your array is * given by numSigBytes. * * @param source the array to convert * @param srcOffset the index where conversion begins * @param numSigBytes the number of significant bytes in your array * @param destination the array to hold the conversion * @param destOffset the index where output will be put * @param alphabet is the encoding alphabet * @return the destination array * @since 1.3 */ private static byte[] encode3to4(byte[] source, int srcOffset, int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) { // 1 2 3 // 01234567890123456789012345678901 Bit position // --------000000001111111122222222 Array position from threeBytes // --------| || || || | Six bit groups to index alphabet // >>18 >>12 >> 6 >> 0 Right shift necessary // 0x3f 0x3f 0x3f Additional AND // Create buffer with zero-padding if there are only one or two // significant bytes passed in the array. // We have to shift left 24 in order to flush out the 1's that appear // when Java treats a value as negative that is cast from a byte to an int. int inBuff = (numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0) | (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0) | (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0); switch (numSigBytes) { case 3: destination[destOffset] = alphabet[(inBuff >>> 18)]; destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f]; destination[destOffset + 3] = alphabet[(inBuff) & 0x3f]; return destination; case 2: destination[destOffset] = alphabet[(inBuff >>> 18)]; destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f]; destination[destOffset + 3] = EQUALS_SIGN; return destination; case 1: destination[destOffset] = alphabet[(inBuff >>> 18)]; destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f]; destination[destOffset + 2] = EQUALS_SIGN; destination[destOffset + 3] = EQUALS_SIGN; return destination; default: return destination; } // end switch } // end encode3to4 /** * Encodes a byte array into Base64 notation. * Equivalent to calling * {@code encodeBytes(source, 0, source.length)} * * @param source The data to convert * @since 1.4 */ public static String encode(byte[] source) { return encode(source, 0, source.length, ALPHABET, true); } /** * Encodes a byte array into web safe Base64 notation. * * @param source The data to convert * @param doPadding is {@code true} to pad result with '=' chars * if it does not fall on 3 byte boundaries */ public static String encodeWebSafe(byte[] source, boolean doPadding) { return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding); } /** * Encodes a byte array into Base64 notation. * * @param source the data to convert * @param off offset in array where conversion should begin * @param len length of data to convert * @param alphabet the encoding alphabet * @param doPadding is {@code true} to pad result with '=' chars * if it does not fall on 3 byte boundaries * @since 1.4 */ public static String encode(byte[] source, int off, int len, byte[] alphabet, boolean doPadding) { byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE); int outLen = outBuff.length; // If doPadding is false, set length to truncate '=' // padding characters while (doPadding == false && outLen > 0) { if (outBuff[outLen - 1] != '=') { break; } outLen -= 1; } return new String(outBuff, 0, outLen); } /** * Encodes a byte array into Base64 notation. * * @param source the data to convert * @param off offset in array where conversion should begin * @param len length of data to convert * @param alphabet is the encoding alphabet * @param maxLineLength maximum length of one line. * @return the BASE64-encoded byte array */ public static byte[] encode(byte[] source, int off, int len, byte[] alphabet, int maxLineLength) { int lenDiv3 = (len + 2) / 3; // ceil(len / 3) int len43 = lenDiv3 * 4; byte[] outBuff = new byte[len43 // Main 4:3 + (len43 / maxLineLength)]; // New lines int d = 0; int e = 0; int len2 = len - 2; int lineLength = 0; for (; d < len2; d += 3, e += 4) { // The following block of code is the same as // encode3to4( source, d + off, 3, outBuff, e, alphabet ); // but inlined for faster encoding (~20% improvement) int inBuff = ((source[d + off] << 24) >>> 8) | ((source[d + 1 + off] << 24) >>> 16) | ((source[d + 2 + off] << 24) >>> 24); outBuff[e] = alphabet[(inBuff >>> 18)]; outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f]; outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f]; outBuff[e + 3] = alphabet[(inBuff) & 0x3f]; lineLength += 4; if (lineLength == maxLineLength) { outBuff[e + 4] = NEW_LINE; e++; lineLength = 0; } // end if: end of line } // end for: each piece of array if (d < len) { encode3to4(source, d + off, len - d, outBuff, e, alphabet); lineLength += 4; if (lineLength == maxLineLength) { // Add a last newline outBuff[e + 4] = NEW_LINE; e++; } e += 4; } assert (e == outBuff.length); return outBuff; } /* ******** D E C O D I N G M E T H O D S ******** */ /** * Decodes four bytes from array source * and writes the resulting bytes (up to three of them) * to destination. * The source and destination arrays can be manipulated * anywhere along their length by specifying * srcOffset and destOffset. * This method does not check to make sure your arrays * are large enough to accommodate srcOffset + 4 for * the source array or destOffset + 3 for * the destination array. * This method returns the actual number of bytes that * were converted from the Base64 encoding. * * * @param source the array to convert * @param srcOffset the index where conversion begins * @param destination the array to hold the conversion * @param destOffset the index where output will be put * @param decodabet the decodabet for decoding Base64 content * @return the number of decoded bytes converted * @since 1.3 */ private static int decode4to3(byte[] source, int srcOffset, byte[] destination, int destOffset, byte[] decodabet) { // Example: Dk== if (source[srcOffset + 2] == EQUALS_SIGN) { int outBuff = ((decodabet[source[srcOffset]] << 24) >>> 6) | ((decodabet[source[srcOffset + 1]] << 24) >>> 12); destination[destOffset] = (byte) (outBuff >>> 16); return 1; } else if (source[srcOffset + 3] == EQUALS_SIGN) { // Example: DkL= int outBuff = ((decodabet[source[srcOffset]] << 24) >>> 6) | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) | ((decodabet[source[srcOffset + 2]] << 24) >>> 18); destination[destOffset] = (byte) (outBuff >>> 16); destination[destOffset + 1] = (byte) (outBuff >>> 8); return 2; } else { // Example: DkLE int outBuff = ((decodabet[source[srcOffset]] << 24) >>> 6) | ((decodabet[source[srcOffset + 1]] << 24) >>> 12) | ((decodabet[source[srcOffset + 2]] << 24) >>> 18) | ((decodabet[source[srcOffset + 3]] << 24) >>> 24); destination[destOffset] = (byte) (outBuff >> 16); destination[destOffset + 1] = (byte) (outBuff >> 8); destination[destOffset + 2] = (byte) (outBuff); return 3; } } // end decodeToBytes /** * Decodes data from Base64 notation. * * @param s the string to decode (decoded in default encoding) * @return the decoded data * @since 1.4 */ public static byte[] decode(String s) throws Base64DecoderException { byte[] bytes = s.getBytes(); return decode(bytes, 0, bytes.length); } /** * Decodes data from web safe Base64 notation. * Web safe encoding uses '-' instead of '+', '_' instead of '/' * * @param s the string to decode (decoded in default encoding) * @return the decoded data */ public static byte[] decodeWebSafe(String s) throws Base64DecoderException { byte[] bytes = s.getBytes(); return decodeWebSafe(bytes, 0, bytes.length); } /** * Decodes Base64 content in byte array format and returns * the decoded byte array. * * @param source The Base64 encoded data * @return decoded data * @since 1.3 * @throws Base64DecoderException */ public static byte[] decode(byte[] source) throws Base64DecoderException { return decode(source, 0, source.length); } /** * Decodes web safe Base64 content in byte array format and returns * the decoded data. * Web safe encoding uses '-' instead of '+', '_' instead of '/' * * @param source the string to decode (decoded in default encoding) * @return the decoded data */ public static byte[] decodeWebSafe(byte[] source) throws Base64DecoderException { return decodeWebSafe(source, 0, source.length); } /** * Decodes Base64 content in byte array format and returns * the decoded byte array. * * @param source the Base64 encoded data * @param off the offset of where to begin decoding * @param len the length of characters to decode * @return decoded data * @since 1.3 * @throws Base64DecoderException */ public static byte[] decode(byte[] source, int off, int len) throws Base64DecoderException { return decode(source, off, len, DECODABET); } /** * Decodes web safe Base64 content in byte array format and returns * the decoded byte array. * Web safe encoding uses '-' instead of '+', '_' instead of '/' * * @param source the Base64 encoded data * @param off the offset of where to begin decoding * @param len the length of characters to decode * @return decoded data */ public static byte[] decodeWebSafe(byte[] source, int off, int len) throws Base64DecoderException { return decode(source, off, len, WEBSAFE_DECODABET); } /** * Decodes Base64 content using the supplied decodabet and returns * the decoded byte array. * * @param source the Base64 encoded data * @param off the offset of where to begin decoding * @param len the length of characters to decode * @param decodabet the decodabet for decoding Base64 content * @return decoded data */ public static byte[] decode(byte[] source, int off, int len, byte[] decodabet) throws Base64DecoderException { int len34 = len * 3 / 4; byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output int outBuffPosn = 0; byte[] b4 = new byte[4]; int b4Posn = 0; int i = 0; byte sbiCrop = 0; byte sbiDecode = 0; for (i = 0; i < len; i++) { sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits sbiDecode = decodabet[sbiCrop]; if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better if (sbiDecode >= EQUALS_SIGN_ENC) { // An equals sign (for padding) must not occur at position 0 or 1 // and must be the last byte[s] in the encoded value if (sbiCrop == EQUALS_SIGN) { int bytesLeft = len - i; byte lastByte = (byte) (source[len - 1 + off] & 0x7f); if (b4Posn == 0 || b4Posn == 1) { throw new Base64DecoderException( "invalid padding byte '=' at byte offset " + i); } else if ((b4Posn == 3 && bytesLeft > 2) || (b4Posn == 4 && bytesLeft > 1)) { throw new Base64DecoderException( "padding byte '=' falsely signals end of encoded value " + "at offset " + i); } else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) { throw new Base64DecoderException( "encoded value has invalid trailing byte"); } break; } b4[b4Posn++] = sbiCrop; if (b4Posn == 4) { outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); b4Posn = 0; } } } else { throw new Base64DecoderException("Bad Base64 input character at " + i + ": " + source[i + off] + "(decimal)"); } } // Because web safe encoding allows non padding base64 encodes, we // need to pad the rest of the b4 buffer with equal signs when // b4Posn != 0. There can be at most 2 equal signs at the end of // four characters, so the b4 buffer must have two or three // characters. This also catches the case where the input is // padded with EQUALS_SIGN if (b4Posn != 0) { if (b4Posn == 1) { throw new Base64DecoderException("single trailing character at offset " + (len - 1)); } b4[b4Posn++] = EQUALS_SIGN; outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet); } byte[] out = new byte[outBuffPosn]; System.arraycopy(outBuff, 0, out, 0, outBuffPosn); return out; } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/iap/Base64DecoderException.java ================================================ // Copyright 2002, Google, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package com.vasilkoff.easyvpnfree.util.iap; /** * Exception thrown when encountering an invalid Base64 input character. * * @author nelson */ public class Base64DecoderException extends Exception { public Base64DecoderException() { super(); } public Base64DecoderException(String s) { super(s); } private static final long serialVersionUID = 1L; } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/iap/IabException.java ================================================ /* Copyright (c) 2012 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.vasilkoff.easyvpnfree.util.iap; /** * Exception thrown when something went wrong with in-app billing. * An IabException has an associated IabResult (an error). * To get the IAB result that caused this exception to be thrown, * call {@link #getResult()}. */ public class IabException extends Exception { IabResult mResult; public IabException(IabResult r) { this(r, null); } public IabException(int response, String message) { this(new IabResult(response, message)); } public IabException(IabResult r, Exception cause) { super(r.getMessage(), cause); mResult = r; } public IabException(int response, String message, Exception cause) { this(new IabResult(response, message), cause); } /** Returns the IAB result (error) that this exception signals. */ public IabResult getResult() { return mResult; } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/iap/IabHelper.java ================================================ /* Copyright (c) 2012 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.vasilkoff.easyvpnfree.util.iap; import android.app.Activity; import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentSender.SendIntentException; import android.content.ServiceConnection; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.RemoteException; import android.text.TextUtils; import android.util.Log; import com.android.vending.billing.IInAppBillingService; import org.json.JSONException; import java.util.ArrayList; import java.util.List; /** * Provides convenience methods for in-app billing. You can create one instance of this * class for your application and use it to process in-app billing operations. * It provides synchronous (blocking) and asynchronous (non-blocking) methods for * many common in-app billing operations, as well as automatic signature * verification. * * After instantiating, you must perform setup in order to start using the object. * To perform setup, call the {@link #startSetup} method and provide a listener; * that listener will be notified when setup is complete, after which (and not before) * you may call other methods. * * After setup is complete, you will typically want to request an inventory of owned * items and subscriptions. See {@link #queryInventory}, {@link #queryInventoryAsync} * and related methods. * * When you are done with this object, don't forget to call {@link #dispose} * to ensure proper cleanup. This object holds a binding to the in-app billing * service, which will leak unless you dispose of it correctly. If you created * the object on an Activity's onCreate method, then the recommended * place to dispose of it is the Activity's onDestroy method. * * A note about threading: When using this object from a background thread, you may * call the blocking versions of methods; when using from a UI thread, call * only the asynchronous versions and handle the results via callbacks. * Also, notice that you can only call one asynchronous operation at a time; * attempting to start a second asynchronous operation while the first one * has not yet completed will result in an exception being thrown. * * @author Bruno Oliveira (Google) * */ public class IabHelper { // Is debug logging enabled? boolean mDebugLog = false; String mDebugTag = "IabHelper"; // Is setup done? boolean mSetupDone = false; // Has this object been disposed of? (If so, we should ignore callbacks, etc) boolean mDisposed = false; // Are subscriptions supported? boolean mSubscriptionsSupported = false; // Is an asynchronous operation in progress? // (only one at a time can be in progress) boolean mAsyncInProgress = false; // (for logging/debugging) // if mAsyncInProgress == true, what asynchronous operation is in progress? String mAsyncOperation = ""; // Context we were passed during initialization Context mContext; // Connection to the service IInAppBillingService mService; ServiceConnection mServiceConn; // The request code used to launch purchase flow int mRequestCode; // The item type of the current purchase flow String mPurchasingItemType; // Public key for verifying signature, in base64 encoding String mSignatureBase64 = null; // Billing response codes public static final int BILLING_RESPONSE_RESULT_OK = 0; public static final int BILLING_RESPONSE_RESULT_USER_CANCELED = 1; public static final int BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE = 3; public static final int BILLING_RESPONSE_RESULT_ITEM_UNAVAILABLE = 4; public static final int BILLING_RESPONSE_RESULT_DEVELOPER_ERROR = 5; public static final int BILLING_RESPONSE_RESULT_ERROR = 6; public static final int BILLING_RESPONSE_RESULT_ITEM_ALREADY_OWNED = 7; public static final int BILLING_RESPONSE_RESULT_ITEM_NOT_OWNED = 8; // IAB Helper error codes public static final int IABHELPER_ERROR_BASE = -1000; public static final int IABHELPER_REMOTE_EXCEPTION = -1001; public static final int IABHELPER_BAD_RESPONSE = -1002; public static final int IABHELPER_VERIFICATION_FAILED = -1003; public static final int IABHELPER_SEND_INTENT_FAILED = -1004; public static final int IABHELPER_USER_CANCELLED = -1005; public static final int IABHELPER_UNKNOWN_PURCHASE_RESPONSE = -1006; public static final int IABHELPER_MISSING_TOKEN = -1007; public static final int IABHELPER_UNKNOWN_ERROR = -1008; public static final int IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE = -1009; public static final int IABHELPER_INVALID_CONSUMPTION = -1010; // Keys for the responses from InAppBillingService public static final String RESPONSE_CODE = "RESPONSE_CODE"; public static final String RESPONSE_GET_SKU_DETAILS_LIST = "DETAILS_LIST"; public static final String RESPONSE_BUY_INTENT = "BUY_INTENT"; public static final String RESPONSE_INAPP_PURCHASE_DATA = "INAPP_PURCHASE_DATA"; public static final String RESPONSE_INAPP_SIGNATURE = "INAPP_DATA_SIGNATURE"; public static final String RESPONSE_INAPP_ITEM_LIST = "INAPP_PURCHASE_ITEM_LIST"; public static final String RESPONSE_INAPP_PURCHASE_DATA_LIST = "INAPP_PURCHASE_DATA_LIST"; public static final String RESPONSE_INAPP_SIGNATURE_LIST = "INAPP_DATA_SIGNATURE_LIST"; public static final String INAPP_CONTINUATION_TOKEN = "INAPP_CONTINUATION_TOKEN"; // Item types public static final String ITEM_TYPE_INAPP = "inapp"; public static final String ITEM_TYPE_SUBS = "subs"; // some fields on the getSkuDetails response bundle public static final String GET_SKU_DETAILS_ITEM_LIST = "ITEM_ID_LIST"; public static final String GET_SKU_DETAILS_ITEM_TYPE_LIST = "ITEM_TYPE_LIST"; /** * Creates an instance. After creation, it will not yet be ready to use. You must perform * setup by calling {@link #startSetup} and wait for setup to complete. This constructor does not * block and is safe to call from a UI thread. * * @param ctx Your application or Activity context. Needed to bind to the in-app billing service. * @param base64PublicKey Your application's public key, encoded in base64. * This is used for verification of purchase signatures. You can find your app's base64-encoded * public key in your application's page on Google Play Developer Console. Note that this * is NOT your "developer public key". */ public IabHelper(Context ctx, String base64PublicKey) { mContext = ctx.getApplicationContext(); mSignatureBase64 = base64PublicKey; logDebug("IAB helper created."); } /** * Enables or disable debug logging through LogCat. */ public void enableDebugLogging(boolean enable, String tag) { checkNotDisposed(); mDebugLog = enable; mDebugTag = tag; } public void enableDebugLogging(boolean enable) { checkNotDisposed(); mDebugLog = enable; } /** * Callback for setup process. This listener's {@link #onIabSetupFinished} method is called * when the setup process is complete. */ public interface OnIabSetupFinishedListener { /** * Called to notify that setup is complete. * * @param result The result of the setup process. */ public void onIabSetupFinished(IabResult result); } /** * Starts the setup process. This will start up the setup process asynchronously. * You will be notified through the listener when the setup process is complete. * This method is safe to call from a UI thread. * * @param listener The listener to notify when the setup process is complete. */ public void startSetup(final OnIabSetupFinishedListener listener) { // If already set up, can't do it again. checkNotDisposed(); if (mSetupDone) throw new IllegalStateException("IAB helper is already set up."); // Connection to IAB service logDebug("Starting in-app billing setup."); mServiceConn = new ServiceConnection() { @Override public void onServiceDisconnected(ComponentName name) { logDebug("Billing service disconnected."); mService = null; } @Override public void onServiceConnected(ComponentName name, IBinder service) { if (mDisposed) return; logDebug("Billing service connected."); mService = IInAppBillingService.Stub.asInterface(service); String packageName = mContext.getPackageName(); try { logDebug("Checking for in-app billing 3 support."); // check for in-app billing v3 support int response = mService.isBillingSupported(3, packageName, ITEM_TYPE_INAPP); if (response != BILLING_RESPONSE_RESULT_OK) { if (listener != null) listener.onIabSetupFinished(new IabResult(response, "Error checking for billing v3 support.")); // if in-app purchases aren't supported, neither are subscriptions. mSubscriptionsSupported = false; return; } logDebug("In-app billing version 3 supported for " + packageName); // check for v3 subscriptions support response = mService.isBillingSupported(3, packageName, ITEM_TYPE_SUBS); if (response == BILLING_RESPONSE_RESULT_OK) { logDebug("Subscriptions AVAILABLE."); mSubscriptionsSupported = true; } else { logDebug("Subscriptions NOT AVAILABLE. Response: " + response); } mSetupDone = true; } catch (RemoteException e) { if (listener != null) { listener.onIabSetupFinished(new IabResult(IABHELPER_REMOTE_EXCEPTION, "RemoteException while setting up in-app billing.")); } e.printStackTrace(); return; } if (listener != null) { listener.onIabSetupFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Setup successful.")); } } }; Intent serviceIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND"); serviceIntent.setPackage("com.android.vending"); boolean successfullyBound = mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE); if (!successfullyBound) { // no service available to handle that Intent if (listener != null) { listener.onIabSetupFinished( new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE, "Billing service unavailable on device.")); } } /* if (!mContext.getPackageManager().queryIntentServices(serviceIntent, 0).isEmpty()) { // service available to handle that Intent mContext.bindService(serviceIntent, mServiceConn, Context.BIND_AUTO_CREATE); } else { // no service available to handle that Intent if (listener != null) { listener.onIabSetupFinished( new IabResult(BILLING_RESPONSE_RESULT_BILLING_UNAVAILABLE, "Billing service unavailable on device.")); } }*/ } /** * Dispose of object, releasing resources. It's very important to call this * method when you are done with this object. It will release any resources * used by it such as service connections. Naturally, once the object is * disposed of, it can't be used again. */ public void dispose() { logDebug("Disposing."); mSetupDone = false; if (mServiceConn != null) { logDebug("Unbinding from service."); if (mContext != null) mContext.unbindService(mServiceConn); } mDisposed = true; mContext = null; mServiceConn = null; mService = null; mPurchaseListener = null; } private void checkNotDisposed() { if (mDisposed) throw new IllegalStateException("IabHelper was disposed of, so it cannot be used."); } /** Returns whether subscriptions are supported. */ public boolean subscriptionsSupported() { checkNotDisposed(); return mSubscriptionsSupported; } /** * Callback that notifies when a purchase is finished. */ public interface OnIabPurchaseFinishedListener { /** * Called to notify that an in-app purchase finished. If the purchase was successful, * then the sku parameter specifies which item was purchased. If the purchase failed, * the sku and extraData parameters may or may not be null, depending on how far the purchase * process went. * * @param result The result of the purchase. * @param info The purchase information (null if purchase failed) */ public void onIabPurchaseFinished(IabResult result, Purchase info); } // The listener registered on launchPurchaseFlow, which we have to call back when // the purchase finishes OnIabPurchaseFinishedListener mPurchaseListener; public void launchPurchaseFlow(Activity act, String sku, int requestCode, OnIabPurchaseFinishedListener listener) { launchPurchaseFlow(act, sku, requestCode, listener, ""); } public void launchPurchaseFlow(Activity act, String sku, int requestCode, OnIabPurchaseFinishedListener listener, String extraData) { launchPurchaseFlow(act, sku, ITEM_TYPE_INAPP, requestCode, listener, extraData); } public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode, OnIabPurchaseFinishedListener listener) { launchSubscriptionPurchaseFlow(act, sku, requestCode, listener, ""); } public void launchSubscriptionPurchaseFlow(Activity act, String sku, int requestCode, OnIabPurchaseFinishedListener listener, String extraData) { launchPurchaseFlow(act, sku, ITEM_TYPE_SUBS, requestCode, listener, extraData); } /** * Initiate the UI flow for an in-app purchase. Call this method to initiate an in-app purchase, * which will involve bringing up the Google Play screen. The calling activity will be paused while * the user interacts with Google Play, and the result will be delivered via the activity's * {@link Activity#onActivityResult} method, at which point you must call * this object's {@link #handleActivityResult} method to continue the purchase flow. This method * MUST be called from the UI thread of the Activity. * * @param act The calling activity. * @param sku The sku of the item to purchase. * @param itemType indicates if it's a product or a subscription (ITEM_TYPE_INAPP or ITEM_TYPE_SUBS) * @param requestCode A request code (to differentiate from other responses -- * as in {@link Activity#startActivityForResult}). * @param listener The listener to notify when the purchase process finishes * @param extraData Extra data (developer payload), which will be returned with the purchase data * when the purchase completes. This extra data will be permanently bound to that purchase * and will always be returned when the purchase is queried. */ public void launchPurchaseFlow(Activity act, String sku, String itemType, int requestCode, OnIabPurchaseFinishedListener listener, String extraData) { checkNotDisposed(); checkSetupDone("launchPurchaseFlow"); flagStartAsync("launchPurchaseFlow"); IabResult result; if (itemType.equals(ITEM_TYPE_SUBS) && !mSubscriptionsSupported) { IabResult r = new IabResult(IABHELPER_SUBSCRIPTIONS_NOT_AVAILABLE, "Subscriptions are not available."); flagEndAsync(); if (listener != null) listener.onIabPurchaseFinished(r, null); return; } try { logDebug("Constructing buy intent for " + sku + ", item type: " + itemType); Bundle buyIntentBundle = mService.getBuyIntent(3, mContext.getPackageName(), sku, itemType, extraData); int response = getResponseCodeFromBundle(buyIntentBundle); if (response != BILLING_RESPONSE_RESULT_OK) { logError("Unable to buy item, Error response: " + getResponseDesc(response)); flagEndAsync(); result = new IabResult(response, "Unable to buy item"); if (listener != null) listener.onIabPurchaseFinished(result, null); return; } PendingIntent pendingIntent = buyIntentBundle.getParcelable(RESPONSE_BUY_INTENT); logDebug("Launching buy intent for " + sku + ". Request code: " + requestCode); mRequestCode = requestCode; mPurchaseListener = listener; mPurchasingItemType = itemType; act.startIntentSenderForResult(pendingIntent.getIntentSender(), requestCode, new Intent(), Integer.valueOf(0), Integer.valueOf(0), Integer.valueOf(0)); } catch (SendIntentException e) { logError("SendIntentException while launching purchase flow for sku " + sku); e.printStackTrace(); flagEndAsync(); result = new IabResult(IABHELPER_SEND_INTENT_FAILED, "Failed to send intent."); if (listener != null) listener.onIabPurchaseFinished(result, null); } catch (RemoteException e) { logError("RemoteException while launching purchase flow for sku " + sku); e.printStackTrace(); flagEndAsync(); result = new IabResult(IABHELPER_REMOTE_EXCEPTION, "Remote exception while starting purchase flow"); if (listener != null) listener.onIabPurchaseFinished(result, null); } } /** * Handles an activity result that's part of the purchase flow in in-app billing. If you * are calling {@link #launchPurchaseFlow}, then you must call this method from your * Activity's {@link Activity@onActivityResult} method. This method * MUST be called from the UI thread of the Activity. * * @param requestCode The requestCode as you received it. * @param resultCode The resultCode as you received it. * @param data The data (Intent) as you received it. * @return Returns true if the result was related to a purchase flow and was handled; * false if the result was not related to a purchase, in which case you should * handle it normally. */ public boolean handleActivityResult(int requestCode, int resultCode, Intent data) { IabResult result; if (requestCode != mRequestCode) return false; checkNotDisposed(); checkSetupDone("handleActivityResult"); // end of async purchase operation that started on launchPurchaseFlow flagEndAsync(); if (data == null) { logError("Null data in IAB activity result."); result = new IabResult(IABHELPER_BAD_RESPONSE, "Null data in IAB result"); if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null); return true; } int responseCode = getResponseCodeFromIntent(data); String purchaseData = data.getStringExtra(RESPONSE_INAPP_PURCHASE_DATA); String dataSignature = data.getStringExtra(RESPONSE_INAPP_SIGNATURE); if (resultCode == Activity.RESULT_OK && responseCode == BILLING_RESPONSE_RESULT_OK) { logDebug("Successful resultcode from purchase activity."); logDebug("Purchase data: " + purchaseData); logDebug("Data signature: " + dataSignature); logDebug("Extras: " + data.getExtras()); logDebug("Expected item type: " + mPurchasingItemType); if (purchaseData == null || dataSignature == null) { logError("BUG: either purchaseData or dataSignature is null."); logDebug("Extras: " + data.getExtras().toString()); result = new IabResult(IABHELPER_UNKNOWN_ERROR, "IAB returned null purchaseData or dataSignature"); if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null); return true; } Purchase purchase = null; try { purchase = new Purchase(mPurchasingItemType, purchaseData, dataSignature); String sku = purchase.getSku(); // Verify signature if (!Security.verifyPurchase(mSignatureBase64, purchaseData, dataSignature)) { logError("Purchase signature verification FAILED for sku " + sku); result = new IabResult(IABHELPER_VERIFICATION_FAILED, "Signature verification failed for sku " + sku); if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, purchase); return true; } logDebug("Purchase signature successfully verified."); } catch (JSONException e) { logError("Failed to parse purchase data."); e.printStackTrace(); result = new IabResult(IABHELPER_BAD_RESPONSE, "Failed to parse purchase data."); if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null); return true; } if (mPurchaseListener != null) { mPurchaseListener.onIabPurchaseFinished(new IabResult(BILLING_RESPONSE_RESULT_OK, "Success"), purchase); } } else if (resultCode == Activity.RESULT_OK) { // result code was OK, but in-app billing response was not OK. logDebug("Result code was OK but in-app billing response was not OK: " + getResponseDesc(responseCode)); if (mPurchaseListener != null) { result = new IabResult(responseCode, "Problem purchashing item."); mPurchaseListener.onIabPurchaseFinished(result, null); } } else if (resultCode == Activity.RESULT_CANCELED) { logDebug("Purchase canceled - Response: " + getResponseDesc(responseCode)); result = new IabResult(IABHELPER_USER_CANCELLED, "User canceled."); if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null); } else { logError("Purchase failed. Result code: " + Integer.toString(resultCode) + ". Response: " + getResponseDesc(responseCode)); result = new IabResult(IABHELPER_UNKNOWN_PURCHASE_RESPONSE, "Unknown purchase response."); if (mPurchaseListener != null) mPurchaseListener.onIabPurchaseFinished(result, null); } return true; } public Inventory queryInventory(boolean querySkuDetails, List moreSkus) throws IabException { return queryInventory(querySkuDetails, moreSkus, null); } /** * Queries the inventory. This will query all owned items from the server, as well as * information on additional skus, if specified. This method may block or take long to execute. * Do not call from a UI thread. For that, use the non-blocking version {@link #refreshInventoryAsync}. * * @param querySkuDetails if true, SKU details (price, description, etc) will be queried as well * as purchase information. * @param moreItemSkus additional PRODUCT skus to query information on, regardless of ownership. * Ignored if null or if querySkuDetails is false. * @param moreSubsSkus additional SUBSCRIPTIONS skus to query information on, regardless of ownership. * Ignored if null or if querySkuDetails is false. * @throws IabException if a problem occurs while refreshing the inventory. */ public Inventory queryInventory(boolean querySkuDetails, List moreItemSkus, List moreSubsSkus) throws IabException { checkNotDisposed(); checkSetupDone("queryInventory"); try { Inventory inv = new Inventory(); int r = queryPurchases(inv, ITEM_TYPE_INAPP); if (r != BILLING_RESPONSE_RESULT_OK) { throw new IabException(r, "Error refreshing inventory (querying owned items)."); } if (querySkuDetails) { r = querySkuDetails(ITEM_TYPE_INAPP, inv, moreItemSkus); if (r != BILLING_RESPONSE_RESULT_OK) { throw new IabException(r, "Error refreshing inventory (querying prices of items)."); } } // if subscriptions are supported, then also query for subscriptions if (mSubscriptionsSupported) { r = queryPurchases(inv, ITEM_TYPE_SUBS); if (r != BILLING_RESPONSE_RESULT_OK) { throw new IabException(r, "Error refreshing inventory (querying owned subscriptions)."); } if (querySkuDetails) { r = querySkuDetails(ITEM_TYPE_SUBS, inv, moreItemSkus); if (r != BILLING_RESPONSE_RESULT_OK) { throw new IabException(r, "Error refreshing inventory (querying prices of subscriptions)."); } } } return inv; } catch (RemoteException e) { throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while refreshing inventory.", e); } catch (JSONException e) { throw new IabException(IABHELPER_BAD_RESPONSE, "Error parsing JSON response while refreshing inventory.", e); } } /** * Listener that notifies when an inventory query operation completes. */ public interface QueryInventoryFinishedListener { /** * Called to notify that an inventory query operation completed. * * @param result The result of the operation. * @param inv The inventory. */ public void onQueryInventoryFinished(IabResult result, Inventory inv); } /** * Asynchronous wrapper for inventory query. This will perform an inventory * query as described in {@link #queryInventory}, but will do so asynchronously * and call back the specified listener upon completion. This method is safe to * call from a UI thread. * * @param querySkuDetails as in {@link #queryInventory} * @param moreSkus as in {@link #queryInventory} * @param listener The listener to notify when the refresh operation completes. */ public void queryInventoryAsync(final boolean querySkuDetails, final List moreSkus, final QueryInventoryFinishedListener listener) { final Handler handler = new Handler(); checkNotDisposed(); checkSetupDone("queryInventory"); flagStartAsync("refresh inventory"); (new Thread(new Runnable() { public void run() { IabResult result = new IabResult(BILLING_RESPONSE_RESULT_OK, "Inventory refresh successful."); Inventory inv = null; try { inv = queryInventory(querySkuDetails, moreSkus); } catch (IabException ex) { result = ex.getResult(); } flagEndAsync(); final IabResult result_f = result; final Inventory inv_f = inv; if (!mDisposed && listener != null) { handler.post(new Runnable() { public void run() { listener.onQueryInventoryFinished(result_f, inv_f); } }); } } })).start(); } public void queryInventoryAsync(QueryInventoryFinishedListener listener) { queryInventoryAsync(true, null, listener); } public void queryInventoryAsync(boolean querySkuDetails, QueryInventoryFinishedListener listener) { queryInventoryAsync(querySkuDetails, null, listener); } /** * Consumes a given in-app product. Consuming can only be done on an item * that's owned, and as a result of consumption, the user will no longer own it. * This method may block or take long to return. Do not call from the UI thread. * For that, see {@link #consumeAsync}. * * @param itemInfo The PurchaseInfo that represents the item to consume. * @throws IabException if there is a problem during consumption. */ void consume(Purchase itemInfo) throws IabException { checkNotDisposed(); checkSetupDone("consume"); if (!itemInfo.mItemType.equals(ITEM_TYPE_INAPP)) { throw new IabException(IABHELPER_INVALID_CONSUMPTION, "Items of type '" + itemInfo.mItemType + "' can't be consumed."); } try { String token = itemInfo.getToken(); String sku = itemInfo.getSku(); if (token == null || token.equals("")) { logError("Can't consume "+ sku + ". No token."); throw new IabException(IABHELPER_MISSING_TOKEN, "PurchaseInfo is missing token for sku: " + sku + " " + itemInfo); } logDebug("Consuming sku: " + sku + ", token: " + token); int response = mService.consumePurchase(3, mContext.getPackageName(), token); if (response == BILLING_RESPONSE_RESULT_OK) { logDebug("Successfully consumed sku: " + sku); } else { logDebug("Error consuming consuming sku " + sku + ". " + getResponseDesc(response)); throw new IabException(response, "Error consuming sku " + sku); } } catch (RemoteException e) { throw new IabException(IABHELPER_REMOTE_EXCEPTION, "Remote exception while consuming. PurchaseInfo: " + itemInfo, e); } } /** * Callback that notifies when a consumption operation finishes. */ public interface OnConsumeFinishedListener { /** * Called to notify that a consumption has finished. * * @param purchase The purchase that was (or was to be) consumed. * @param result The result of the consumption operation. */ public void onConsumeFinished(Purchase purchase, IabResult result); } /** * Callback that notifies when a multi-item consumption operation finishes. */ public interface OnConsumeMultiFinishedListener { /** * Called to notify that a consumption of multiple items has finished. * * @param purchases The purchases that were (or were to be) consumed. * @param results The results of each consumption operation, corresponding to each * sku. */ public void onConsumeMultiFinished(List purchases, List results); } /** * Asynchronous wrapper to item consumption. Works like {@link #consume}, but * performs the consumption in the background and notifies completion through * the provided listener. This method is safe to call from a UI thread. * * @param purchase The purchase to be consumed. * @param listener The listener to notify when the consumption operation finishes. */ public void consumeAsync(Purchase purchase, OnConsumeFinishedListener listener) { checkNotDisposed(); checkSetupDone("consume"); List purchases = new ArrayList(); purchases.add(purchase); consumeAsyncInternal(purchases, listener, null); } /** * Same as {@link consumeAsync}, but for multiple items at once. * @param purchases The list of PurchaseInfo objects representing the purchases to consume. * @param listener The listener to notify when the consumption operation finishes. */ public void consumeAsync(List purchases, OnConsumeMultiFinishedListener listener) { checkNotDisposed(); checkSetupDone("consume"); consumeAsyncInternal(purchases, null, listener); } /** * Returns a human-readable description for the given response code. * * @param code The response code * @return A human-readable string explaining the result code. * It also includes the result code numerically. */ public static String getResponseDesc(int code) { String[] iab_msgs = ("0:OK/1:User Canceled/2:Unknown/" + "3:Billing Unavailable/4:Item unavailable/" + "5:Developer Error/6:Error/7:Item Already Owned/" + "8:Item not owned").split("/"); String[] iabhelper_msgs = ("0:OK/-1001:Remote exception during initialization/" + "-1002:Bad response received/" + "-1003:Purchase signature verification failed/" + "-1004:Send intent failed/" + "-1005:User cancelled/" + "-1006:Unknown purchase response/" + "-1007:Missing token/" + "-1008:Unknown error/" + "-1009:Subscriptions not available/" + "-1010:Invalid consumption attempt").split("/"); if (code <= IABHELPER_ERROR_BASE) { int index = IABHELPER_ERROR_BASE - code; if (index >= 0 && index < iabhelper_msgs.length) return iabhelper_msgs[index]; else return String.valueOf(code) + ":Unknown IAB Helper Error"; } else if (code < 0 || code >= iab_msgs.length) return String.valueOf(code) + ":Unknown"; else return iab_msgs[code]; } // Checks that setup was done; if not, throws an exception. void checkSetupDone(String operation) { if (!mSetupDone) { logError("Illegal state for operation (" + operation + "): IAB helper is not set up."); throw new IllegalStateException("IAB helper is not set up. Can't perform operation: " + operation); } } // Workaround to bug where sometimes response codes come as Long instead of Integer int getResponseCodeFromBundle(Bundle b) { Object o = b.get(RESPONSE_CODE); if (o == null) { logDebug("Bundle with null response code, assuming OK (known issue)"); return BILLING_RESPONSE_RESULT_OK; } else if (o instanceof Integer) return ((Integer)o).intValue(); else if (o instanceof Long) return (int)((Long)o).longValue(); else { logError("Unexpected type for bundle response code."); logError(o.getClass().getName()); throw new RuntimeException("Unexpected type for bundle response code: " + o.getClass().getName()); } } // Workaround to bug where sometimes response codes come as Long instead of Integer int getResponseCodeFromIntent(Intent i) { Object o = i.getExtras().get(RESPONSE_CODE); if (o == null) { logError("Intent with no response code, assuming OK (known issue)"); return BILLING_RESPONSE_RESULT_OK; } else if (o instanceof Integer) return ((Integer)o).intValue(); else if (o instanceof Long) return (int)((Long)o).longValue(); else { logError("Unexpected type for intent response code."); logError(o.getClass().getName()); throw new RuntimeException("Unexpected type for intent response code: " + o.getClass().getName()); } } void flagStartAsync(String operation) { if (mAsyncInProgress) throw new IllegalStateException("Can't start async operation (" + operation + ") because another async operation(" + mAsyncOperation + ") is in progress."); mAsyncOperation = operation; mAsyncInProgress = true; logDebug("Starting async operation: " + operation); } public void flagEndAsync() { logDebug("Ending async operation: " + mAsyncOperation); mAsyncOperation = ""; mAsyncInProgress = false; } int queryPurchases(Inventory inv, String itemType) throws JSONException, RemoteException { // Query purchases if (mService == null || mContext == null) { logError("Our service and/or our context are null. Exiting."); return IABHELPER_UNKNOWN_ERROR; } logDebug("Querying owned items, item type: " + itemType); logDebug("Package name: " + mContext.getPackageName()); boolean verificationFailed = false; String continueToken = null; do { logDebug("Calling getPurchases with continuation token: " + continueToken); Bundle ownedItems = mService.getPurchases(3, mContext.getPackageName(), itemType, continueToken); int response = getResponseCodeFromBundle(ownedItems); logDebug("Owned items response: " + String.valueOf(response)); if (response != BILLING_RESPONSE_RESULT_OK) { logDebug("getPurchases() failed: " + getResponseDesc(response)); return response; } if (!ownedItems.containsKey(RESPONSE_INAPP_ITEM_LIST) || !ownedItems.containsKey(RESPONSE_INAPP_PURCHASE_DATA_LIST) || !ownedItems.containsKey(RESPONSE_INAPP_SIGNATURE_LIST)) { logError("Bundle returned from getPurchases() doesn't contain required fields."); return IABHELPER_BAD_RESPONSE; } ArrayList ownedSkus = ownedItems.getStringArrayList( RESPONSE_INAPP_ITEM_LIST); ArrayList purchaseDataList = ownedItems.getStringArrayList( RESPONSE_INAPP_PURCHASE_DATA_LIST); ArrayList signatureList = ownedItems.getStringArrayList( RESPONSE_INAPP_SIGNATURE_LIST); for (int i = 0; i < purchaseDataList.size(); ++i) { String purchaseData = purchaseDataList.get(i); String signature = signatureList.get(i); String sku = ownedSkus.get(i); if (Security.verifyPurchase(mSignatureBase64, purchaseData, signature)) { logDebug("Sku is owned: " + sku); Purchase purchase = new Purchase(itemType, purchaseData, signature); if (TextUtils.isEmpty(purchase.getToken())) { logWarn("BUG: empty/null token!"); logDebug("Purchase data: " + purchaseData); } // Record ownership and token inv.addPurchase(purchase); } else { logWarn("Purchase signature verification **FAILED**. Not adding item."); logDebug(" Purchase data: " + purchaseData); logDebug(" Signature: " + signature); verificationFailed = true; } } continueToken = ownedItems.getString(INAPP_CONTINUATION_TOKEN); logDebug("Continuation token: " + continueToken); } while (!TextUtils.isEmpty(continueToken)); return verificationFailed ? IABHELPER_VERIFICATION_FAILED : BILLING_RESPONSE_RESULT_OK; } int querySkuDetails(String itemType, Inventory inv, List moreSkus) throws RemoteException, JSONException { logDebug("Querying SKU details."); ArrayList skuList = new ArrayList(); skuList.addAll(inv.getAllOwnedSkus(itemType)); if (moreSkus != null) { for (String sku : moreSkus) { if (!skuList.contains(sku)) { skuList.add(sku); } } } if (skuList.size() == 0) { logDebug("queryPrices: nothing to do because there are no SKUs."); return BILLING_RESPONSE_RESULT_OK; } Bundle querySkus = new Bundle(); querySkus.putStringArrayList(GET_SKU_DETAILS_ITEM_LIST, skuList); Bundle skuDetails = mService.getSkuDetails(3, mContext.getPackageName(), itemType, querySkus); if (!skuDetails.containsKey(RESPONSE_GET_SKU_DETAILS_LIST)) { int response = getResponseCodeFromBundle(skuDetails); if (response != BILLING_RESPONSE_RESULT_OK) { logDebug("getSkuDetails() failed: " + getResponseDesc(response)); return response; } else { logError("getSkuDetails() returned a bundle with neither an error nor a detail list."); return IABHELPER_BAD_RESPONSE; } } ArrayList responseList = skuDetails.getStringArrayList( RESPONSE_GET_SKU_DETAILS_LIST); for (String thisResponse : responseList) { SkuDetails d = new SkuDetails(itemType, thisResponse); logDebug("Got sku details: " + d); inv.addSkuDetails(d); } return BILLING_RESPONSE_RESULT_OK; } void consumeAsyncInternal(final List purchases, final OnConsumeFinishedListener singleListener, final OnConsumeMultiFinishedListener multiListener) { final Handler handler = new Handler(); flagStartAsync("consume"); (new Thread(new Runnable() { public void run() { final List results = new ArrayList(); for (Purchase purchase : purchases) { try { consume(purchase); results.add(new IabResult(BILLING_RESPONSE_RESULT_OK, "Successful consume of sku " + purchase.getSku())); } catch (IabException ex) { results.add(ex.getResult()); } } flagEndAsync(); if (!mDisposed && singleListener != null) { handler.post(new Runnable() { public void run() { singleListener.onConsumeFinished(purchases.get(0), results.get(0)); } }); } if (!mDisposed && multiListener != null) { handler.post(new Runnable() { public void run() { multiListener.onConsumeMultiFinished(purchases, results); } }); } } })).start(); } void logDebug(String msg) { if (mDebugLog) Log.d(mDebugTag, msg); } void logError(String msg) { Log.e(mDebugTag, "In-app billing error: " + msg); } void logWarn(String msg) { Log.w(mDebugTag, "In-app billing warning: " + msg); } public boolean isAsyncInProgress(){ return mAsyncInProgress; } public boolean isSetupDone (){ return mSetupDone; } public boolean isDisposed (){ return mDisposed; } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/iap/IabResult.java ================================================ /* Copyright (c) 2012 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.vasilkoff.easyvpnfree.util.iap; /** * Represents the result of an in-app billing operation. * A result is composed of a response code (an integer) and possibly a * message (String). You can get those by calling * {@link #getResponse} and {@link #getMessage()}, respectively. You * can also inquire whether a result is a success or a failure by * calling {@link #isSuccess()} and {@link #isFailure()}. */ public class IabResult { int mResponse; String mMessage; public IabResult(int response, String message) { mResponse = response; if (message == null || message.trim().length() == 0) { mMessage = IabHelper.getResponseDesc(response); } else { mMessage = message + " (response: " + IabHelper.getResponseDesc(response) + ")"; } } public int getResponse() { return mResponse; } public String getMessage() { return mMessage; } public boolean isSuccess() { return mResponse == IabHelper.BILLING_RESPONSE_RESULT_OK; } public boolean isFailure() { return !isSuccess(); } public String toString() { return "IabResult: " + getMessage(); } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/iap/Inventory.java ================================================ /* Copyright (c) 2012 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.vasilkoff.easyvpnfree.util.iap; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; /** * Represents a block of information about in-app items. * An Inventory is returned by such methods as {@link IabHelper#queryInventory}. */ public class Inventory { Map mSkuMap = new HashMap(); Map mPurchaseMap = new HashMap(); Inventory() { } /** Returns the listing details for an in-app product. */ public SkuDetails getSkuDetails(String sku) { return mSkuMap.get(sku); } /** Returns purchase information for a given product, or null if there is no purchase. */ public Purchase getPurchase(String sku) { return mPurchaseMap.get(sku); } /** Returns whether or not there exists a purchase of the given product. */ public boolean hasPurchase(String sku) { return mPurchaseMap.containsKey(sku); } /** Return whether or not details about the given product are available. */ public boolean hasDetails(String sku) { return mSkuMap.containsKey(sku); } /** * Erase a purchase (locally) from the inventory, given its product ID. This just * modifies the Inventory object locally and has no effect on the server! This is * useful when you have an existing Inventory object which you know to be up to date, * and you have just consumed an item successfully, which means that erasing its * purchase data from the Inventory you already have is quicker than querying for * a new Inventory. */ public void erasePurchase(String sku) { if (mPurchaseMap.containsKey(sku)) mPurchaseMap.remove(sku); } /** Returns a list of all owned product IDs. */ List getAllOwnedSkus() { return new ArrayList(mPurchaseMap.keySet()); } /** Returns a list of all owned product IDs of a given type */ List getAllOwnedSkus(String itemType) { List result = new ArrayList(); for (Purchase p : mPurchaseMap.values()) { if (p.getItemType().equals(itemType)) result.add(p.getSku()); } return result; } /** Returns a list of all purchases. */ List getAllPurchases() { return new ArrayList(mPurchaseMap.values()); } void addSkuDetails(SkuDetails d) { mSkuMap.put(d.getSku(), d); } void addPurchase(Purchase p) { mPurchaseMap.put(p.getSku(), p); } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/iap/Purchase.java ================================================ /* Copyright (c) 2012 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.vasilkoff.easyvpnfree.util.iap; import org.json.JSONException; import org.json.JSONObject; /** * Represents an in-app billing purchase. */ public class Purchase { String mItemType; // ITEM_TYPE_INAPP or ITEM_TYPE_SUBS String mOrderId; String mPackageName; String mSku; long mPurchaseTime; int mPurchaseState; String mDeveloperPayload; String mToken; String mOriginalJson; String mSignature; public Purchase(String itemType, String jsonPurchaseInfo, String signature) throws JSONException { mItemType = itemType; mOriginalJson = jsonPurchaseInfo; JSONObject o = new JSONObject(mOriginalJson); mOrderId = o.optString("orderId"); mPackageName = o.optString("packageName"); mSku = o.optString("productId"); mPurchaseTime = o.optLong("purchaseTime"); mPurchaseState = o.optInt("purchaseState"); mDeveloperPayload = o.optString("developerPayload"); mToken = o.optString("token", o.optString("purchaseToken")); mSignature = signature; } public String getItemType() { return mItemType; } public String getOrderId() { return mOrderId; } public String getPackageName() { return mPackageName; } public String getSku() { return mSku; } public long getPurchaseTime() { return mPurchaseTime; } public int getPurchaseState() { return mPurchaseState; } public String getDeveloperPayload() { return mDeveloperPayload; } public String getToken() { return mToken; } public String getOriginalJson() { return mOriginalJson; } public String getSignature() { return mSignature; } @Override public String toString() { return "PurchaseInfo(type:" + mItemType + "):" + mOriginalJson; } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/iap/Security.java ================================================ /* Copyright (c) 2012 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.vasilkoff.easyvpnfree.util.iap; import android.text.TextUtils; import android.util.Log; import com.vasilkoff.easyvpnfree.BuildConfig; import org.json.JSONException; import org.json.JSONObject; import java.security.InvalidKeyException; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; import java.security.spec.InvalidKeySpecException; import java.security.spec.X509EncodedKeySpec; /** * Security-related methods. For a secure implementation, all of this code * should be implemented on a server that communicates with the * application on the device. For the sake of simplicity and clarity of this * example, this code is included here and is executed on the device. If you * must verify the purchases on the phone, you should obfuscate this code to * make it harder for an attacker to replace the code with stubs that treat all * purchases as verified. */ public class Security { private static final String TAG = "IABUtil/Security"; private static final String KEY_FACTORY_ALGORITHM = "RSA"; private static final String SIGNATURE_ALGORITHM = "SHA1withRSA"; /** * Verifies that the data was signed with the given signature, and returns * the verified purchase. The data is in JSON format and signed * with a private key. The data also contains the {@link PurchaseState} * and product ID of the purchase. * @param base64PublicKey the base64-encoded public key to use for verifying. * @param signedData the signed JSON string (signed, not encrypted) * @param signature the signature for the data, signed with the private key */ public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) { if (TextUtils.isEmpty(signedData) || TextUtils.isEmpty(base64PublicKey) || TextUtils.isEmpty(signature)) { Log.e(TAG, "Purchase verification failed: missing data."); if (BuildConfig.DEBUG) return true; return false; } PublicKey key = Security.generatePublicKey(base64PublicKey); return Security.verify(key, signedData, signature); } /** * Generates a PublicKey instance from a string containing the * Base64-encoded public key. * * @param encodedPublicKey Base64-encoded public key * @throws IllegalArgumentException if encodedPublicKey is invalid */ public static PublicKey generatePublicKey(String encodedPublicKey) { try { byte[] decodedKey = Base64.decode(encodedPublicKey); KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM); return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey)); } catch (NoSuchAlgorithmException e) { throw new RuntimeException(e); } catch (InvalidKeySpecException e) { Log.e(TAG, "Invalid key specification."); throw new IllegalArgumentException(e); } catch (Base64DecoderException e) { Log.e(TAG, "Base64 decoding failed."); throw new IllegalArgumentException(e); } } /** * Verifies that the signature from the server matches the computed * signature on the data. Returns true if the data is correctly signed. * * @param publicKey public key associated with the developer account * @param signedData signed data from server * @param signature server signature * @return true if the data and signature match */ public static boolean verify(PublicKey publicKey, String signedData, String signature) { Signature sig; try { sig = Signature.getInstance(SIGNATURE_ALGORITHM); sig.initVerify(publicKey); sig.update(signedData.getBytes()); if (!sig.verify(Base64.decode(signature))) { Log.e(TAG, "Signature verification failed."); return false; } return true; } catch (NoSuchAlgorithmException e) { Log.e(TAG, "NoSuchAlgorithmException."); } catch (InvalidKeyException e) { Log.e(TAG, "Invalid key specification."); } catch (SignatureException e) { Log.e(TAG, "Signature exception."); } catch (Base64DecoderException e) { Log.e(TAG, "Base64 decoding failed."); } return false; } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/iap/SkuDetails.java ================================================ /* Copyright (c) 2012 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.vasilkoff.easyvpnfree.util.iap; import org.json.JSONException; import org.json.JSONObject; /** * Represents an in-app product's listing details. */ public class SkuDetails { String mItemType; String mSku; String mType; String mPrice; String mTitle; String mDescription; String mJson; public SkuDetails(String jsonSkuDetails) throws JSONException { this(IabHelper.ITEM_TYPE_INAPP, jsonSkuDetails); } public SkuDetails(String itemType, String jsonSkuDetails) throws JSONException { mItemType = itemType; mJson = jsonSkuDetails; JSONObject o = new JSONObject(mJson); mSku = o.optString("productId"); mType = o.optString("type"); mPrice = o.optString("price"); mTitle = o.optString("title"); mDescription = o.optString("description"); } public String getSku() { return mSku; } public String getType() { return mType; } public String getPrice() { return mPrice; } public String getTitle() { return mTitle; } public String getDescription() { return mDescription; } @Override public String toString() { return "SkuDetails:" + mJson; } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/map/MapCreator.java ================================================ package com.vasilkoff.easyvpnfree.util.map; import android.content.Context; import android.support.v4.content.ContextCompat; import com.vasilkoff.easyvpnfree.R; import com.vasilkoff.easyvpnfree.util.LoadData; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.mapsforge.core.graphics.Color; import org.mapsforge.core.graphics.Paint; import org.mapsforge.core.graphics.Style; import org.mapsforge.core.model.LatLong; import org.mapsforge.map.android.graphics.AndroidGraphicFactory; import org.mapsforge.map.layer.Layers; import org.mapsforge.map.layer.overlay.Polygon; import java.io.IOException; import java.io.InputStream; import java.util.List; /** * Created by Kusenko on 04.11.2016. */ public class MapCreator { private Paint paintFill; private Paint paintStroke; private Layers layers; private Context context; public MapCreator(Context context, Layers layers) { this.context = context; this.layers = layers; paintFill = AndroidGraphicFactory.INSTANCE.createPaint(); paintFill.setColor(ContextCompat.getColor(context,R.color.mapFill)); paintFill.setStyle(Style.FILL); paintStroke = AndroidGraphicFactory.INSTANCE.createPaint(); paintStroke.setStrokeWidth(1); paintStroke.setColor(ContextCompat.getColor(context,R.color.mapStroke)); paintStroke.setStyle(Style.STROKE); } public void parseGeoJson(String nameFile) { String jsonString = null; jsonString = LoadData.fromFile(nameFile, context); if (jsonString != null) parseData(jsonString); } private void parseData(String jsonString) { try { JSONObject dataJsonObj = new JSONObject(jsonString); JSONArray features = dataJsonObj.getJSONArray("features"); for (int i = 0; i < features.length(); i++) { JSONObject feature = new JSONObject(features.get(i).toString()); JSONObject geometry = feature.getJSONObject("geometry"); JSONArray coordinates = geometry.getJSONArray("coordinates"); if (geometry.get("type").equals("Polygon")) { createPolygons(new JSONArray(coordinates.get(0).toString())); } else { for (int j = 0; j < coordinates.length(); j++) { JSONArray coordinate = new JSONArray(coordinates.get(j).toString()); createPolygons(new JSONArray(coordinate.get(0).toString())); } } } } catch (JSONException e) { e.printStackTrace(); } } private void createPolygons(JSONArray coordinates) { Polygon polygon = new Polygon(paintFill, paintStroke, AndroidGraphicFactory.INSTANCE); List polygonList = polygon.getLatLongs(); for (int j = 0; j < coordinates.length(); j++) { try { JSONArray arrLatLong = new JSONArray(coordinates.get(j).toString()); polygonList.add(new LatLong(arrLatLong.getDouble(1), arrLatLong.getDouble(0))); } catch (JSONException e) { e.printStackTrace(); } } layers.add(polygon); } } ================================================ FILE: Android-code/app/src/main/java/com/vasilkoff/easyvpnfree/util/map/MyMarker.java ================================================ package com.vasilkoff.easyvpnfree.util.map; import org.mapsforge.core.graphics.Bitmap; import org.mapsforge.core.model.LatLong; import org.mapsforge.map.layer.overlay.Marker; /** * Created by Kusenko on 04.11.2016. */ public class MyMarker extends Marker { private Object relationObject; /** * @param latLong the initial geographical coordinates of this marker (may be null). * @param bitmap the initial {@code Bitmap} of this marker (may be null). * @param horizontalOffset the horizontal marker offset. * @param verticalOffset the vertical marker offset. */ public MyMarker(LatLong latLong, Bitmap bitmap, int horizontalOffset, int verticalOffset, Object relationObject) { super(latLong, bitmap, horizontalOffset, verticalOffset); this.relationObject = relationObject; } public Object getRelationObject() { return relationObject; } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/VpnProfile.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn; import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Build; import android.preference.PreferenceManager; import android.security.KeyChain; import android.security.KeyChainException; import android.support.annotation.NonNull; import android.text.TextUtils; import android.util.Base64; import com.vasilkoff.easyvpnfree.R; import org.spongycastle.util.io.pem.PemObject; import org.spongycastle.util.io.pem.PemWriter; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.Serializable; import java.io.StringWriter; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.HashSet; import java.util.Locale; import java.util.UUID; import java.util.Vector; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; import javax.crypto.IllegalBlockSizeException; import javax.crypto.NoSuchPaddingException; import de.blinkt.openvpn.core.Connection; import de.blinkt.openvpn.core.NativeUtils; import de.blinkt.openvpn.core.OpenVPNService; import de.blinkt.openvpn.core.VPNLaunchHelper; import de.blinkt.openvpn.core.VpnStatus; import de.blinkt.openvpn.core.X509Utils; public class VpnProfile implements Serializable, Cloneable { // Note that this class cannot be moved to core where it belongs since // the profile loading depends on it being here // The Serializable documentation mentions that class name change are possible // but the how is unclear // transient public static final long MAX_EMBED_FILE_SIZE = 2048 * 1024; // 2048kB // Don't change this, not all parts of the program use this constant public static final String EXTRA_PROFILEUUID = "de.blinkt.openvpn.profileUUID"; public static final String INLINE_TAG = "[[INLINE]]"; public static final String DISPLAYNAME_TAG = "[[NAME]]"; private static final long serialVersionUID = 7085688938959334563L; public static final int MAXLOGLEVEL = 4; public static final int CURRENT_PROFILE_VERSION = 6; public static final int DEFAULT_MSSFIX_SIZE = 1280; public static String DEFAULT_DNS1 = "8.8.8.8"; public static String DEFAULT_DNS2 = "8.8.4.4"; public transient String mTransientPW = null; public transient String mTransientPCKS12PW = null; public static final int TYPE_CERTIFICATES = 0; public static final int TYPE_PKCS12 = 1; public static final int TYPE_KEYSTORE = 2; public static final int TYPE_USERPASS = 3; public static final int TYPE_STATICKEYS = 4; public static final int TYPE_USERPASS_CERTIFICATES = 5; public static final int TYPE_USERPASS_PKCS12 = 6; public static final int TYPE_USERPASS_KEYSTORE = 7; public static final int X509_VERIFY_TLSREMOTE = 0; public static final int X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING = 1; public static final int X509_VERIFY_TLSREMOTE_DN = 2; public static final int X509_VERIFY_TLSREMOTE_RDN = 3; public static final int X509_VERIFY_TLSREMOTE_RDN_PREFIX = 4; // variable named wrong and should haven beeen transient // but needs to keep wrong name to guarante loading of old // profiles public transient boolean profileDeleted = false; public int mAuthenticationType = TYPE_KEYSTORE; public String mName; public String mAlias; public String mClientCertFilename; public String mTLSAuthDirection = ""; public String mTLSAuthFilename; public String mClientKeyFilename; public String mCaFilename; public boolean mUseLzo = true; public String mPKCS12Filename; public String mPKCS12Password; public boolean mUseTLSAuth = false; public String mDNS1 = DEFAULT_DNS1; public String mDNS2 = DEFAULT_DNS2; public String mIPv4Address; public String mIPv6Address; public boolean mOverrideDNS = false; public String mSearchDomain = "blinkt.de"; public boolean mUseDefaultRoute = true; public boolean mUsePull = true; public String mCustomRoutes; public boolean mCheckRemoteCN = true; public boolean mExpectTLSCert = false; public String mRemoteCN = ""; public String mPassword = ""; public String mUsername = ""; public boolean mRoutenopull = false; public boolean mUseRandomHostname = false; public boolean mUseFloat = false; public boolean mUseCustomConfig = false; public String mCustomConfigOptions = ""; public String mVerb = "1"; //ignored public String mCipher = ""; public boolean mNobind = false; public boolean mUseDefaultRoutev6 = true; public String mCustomRoutesv6 = ""; public String mKeyPassword = ""; public boolean mPersistTun = false; public String mConnectRetryMax = "-1"; public String mConnectRetry = "2"; public String mConnectRetryMaxTime = "300"; public boolean mUserEditable = true; public String mAuth = ""; public int mX509AuthType = X509_VERIFY_TLSREMOTE_RDN; public String mx509UsernameField = null; private transient PrivateKey mPrivateKey; // Public attributes, since I got mad with getter/setter // set members to default values private UUID mUuid; public boolean mAllowLocalLAN; private int mProfileVersion; public String mExcludedRoutes; public String mExcludedRoutesv6; public int mMssFix = 0; // -1 is default, public Connection[] mConnections = new Connection[0]; public boolean mRemoteRandom = false; public HashSet mAllowedAppsVpn = new HashSet<>(); public boolean mAllowedAppsVpnAreDisallowed = true; public String mCrlFilename; public String mProfileCreator; public boolean mPushPeerInfo = false; public static final boolean mIsOpenVPN22 = false; /* Options no longer used in new profiles */ public String mServerName = "openvpn.blinkt.de"; public String mServerPort = "1194"; public boolean mUseUdp = true; public VpnProfile(String name) { mUuid = UUID.randomUUID(); mName = name; mProfileVersion = CURRENT_PROFILE_VERSION; mConnections = new Connection[1]; mConnections[0] = new Connection(); } public static String openVpnEscape(String unescaped) { if (unescaped == null) return null; String escapedString = unescaped.replace("\\", "\\\\"); escapedString = escapedString.replace("\"", "\\\""); escapedString = escapedString.replace("\n", "\\n"); if (escapedString.equals(unescaped) && !escapedString.contains(" ") && !escapedString.contains("#") && !escapedString.contains(";") && !escapedString.equals("")) return unescaped; else return '"' + escapedString + '"'; } public void clearDefaults() { mServerName = "unknown"; mUsePull = false; mUseLzo = false; mUseDefaultRoute = false; mUseDefaultRoutev6 = false; mExpectTLSCert = false; mCheckRemoteCN = false; mPersistTun = false; mAllowLocalLAN = true; mPushPeerInfo = false; mMssFix = 0; } public UUID getUUID() { return mUuid; } public String getName() { if (mName == null) return "No profile name"; return mName; } public void upgradeProfile() { if (mProfileVersion < 2) { /* default to the behaviour the OS used */ mAllowLocalLAN = Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT; } if (mProfileVersion < 4) { moveOptionsToConnection(); mAllowedAppsVpnAreDisallowed = true; } if (mAllowedAppsVpn == null) mAllowedAppsVpn = new HashSet<>(); if (mConnections == null) mConnections = new Connection[0]; if (mProfileVersion < 6) { if (TextUtils.isEmpty(mProfileCreator)) mUserEditable = true; } mProfileVersion = CURRENT_PROFILE_VERSION; } private void moveOptionsToConnection() { mConnections = new Connection[1]; Connection conn = new Connection(); conn.mServerName = mServerName; conn.mServerPort = mServerPort; conn.mUseUdp = mUseUdp; conn.mCustomConfiguration = ""; mConnections[0] = conn; } public String getConfigFile(Context context, boolean configForOvpn3) { File cacheDir = context.getCacheDir(); String cfg = ""; // Enable management interface cfg += "# Enables connection to GUI\n"; cfg += "management "; cfg += cacheDir.getAbsolutePath() + "/" + "mgmtsocket"; cfg += " unix\n"; cfg += "management-client\n"; // Not needed, see updated man page in 2.3 //cfg += "management-signal\n"; cfg += "management-query-passwords\n"; cfg += "management-hold\n\n"; if (!configForOvpn3) { cfg += String.format("setenv IV_GUI_VER %s \n", openVpnEscape(getVersionEnvString(context))); String versionString = String.format(Locale.US, "%d %s %s %s %s %s", Build.VERSION.SDK_INT, Build.VERSION.RELEASE, NativeUtils.getNativeAPI(), Build.BRAND, Build.BOARD, Build.MODEL); cfg += String.format("setenv IV_PLAT_VER %s\n", openVpnEscape(versionString)); } cfg += "machine-readable-output\n"; // Users are confused by warnings that are misleading... cfg += "ifconfig-nowarn\n"; boolean useTLSClient = (mAuthenticationType != TYPE_STATICKEYS); if (useTLSClient && mUsePull) cfg += "client\n"; else if (mUsePull) cfg += "pull\n"; else if (useTLSClient) cfg += "tls-client\n"; //cfg += "verb " + mVerb + "\n"; cfg += "verb " + MAXLOGLEVEL + "\n"; if (mConnectRetryMax == null) { mConnectRetryMax = "-1"; } if (!mConnectRetryMax.equals("-1")) cfg += "connect-retry-max " + mConnectRetryMax + "\n"; if (TextUtils.isEmpty(mConnectRetry)) mConnectRetry = "2"; if (TextUtils.isEmpty(mConnectRetryMaxTime)) mConnectRetryMaxTime="300"; if (!mIsOpenVPN22) cfg += "connect-retry " + mConnectRetry + " " + mConnectRetryMaxTime + "\n"; else if (mIsOpenVPN22 && mUseUdp) cfg += "connect-retry " + mConnectRetry + "\n"; cfg += "resolv-retry 60\n"; // We cannot use anything else than tun cfg += "dev tun\n"; boolean canUsePlainRemotes = true; if (mConnections.length == 1) { cfg += mConnections[0].getConnectionBlock(); } else { for (Connection conn : mConnections) { canUsePlainRemotes = canUsePlainRemotes && conn.isOnlyRemote(); } if (mRemoteRandom) cfg += "remote-random\n"; if (canUsePlainRemotes) { for (Connection conn : mConnections) { if (conn.mEnabled) { cfg += conn.getConnectionBlock(); } } } } switch (mAuthenticationType) { case VpnProfile.TYPE_USERPASS_CERTIFICATES: cfg += "auth-user-pass\n"; case VpnProfile.TYPE_CERTIFICATES: // Ca cfg += insertFileData("ca", mCaFilename); // Client Cert + Key cfg += insertFileData("key", mClientKeyFilename); cfg += insertFileData("cert", mClientCertFilename); break; case VpnProfile.TYPE_USERPASS_PKCS12: cfg += "auth-user-pass\n"; case VpnProfile.TYPE_PKCS12: cfg += insertFileData("pkcs12", mPKCS12Filename); break; case VpnProfile.TYPE_USERPASS_KEYSTORE: cfg += "auth-user-pass\n"; case VpnProfile.TYPE_KEYSTORE: if (!configForOvpn3) { String[] ks = getKeyStoreCertificates(context); cfg += "### From Keystore ####\n"; if (ks != null) { cfg += "\n" + ks[0] + "\n\n"; if (ks[1] != null) cfg += "\n" + ks[1] + "\n\n"; cfg += "\n" + ks[2] + "\n\n"; cfg += "management-external-key\n"; } else { cfg += context.getString(R.string.keychain_access) + "\n"; if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) if (!mAlias.matches("^[a-zA-Z0-9]$")) cfg += context.getString(R.string.jelly_keystore_alphanumeric_bug) + "\n"; } } break; case VpnProfile.TYPE_USERPASS: cfg += "auth-user-pass\n"; cfg += insertFileData("ca", mCaFilename); } if (!TextUtils.isEmpty(mCrlFilename)) cfg += insertFileData("crl-verify", mCrlFilename); if (mUseLzo) { cfg += "comp-lzo\n"; } if (mUseTLSAuth) { if (mAuthenticationType == TYPE_STATICKEYS) cfg += insertFileData("secret", mTLSAuthFilename); else cfg += insertFileData("tls-auth", mTLSAuthFilename); if (!TextUtils.isEmpty(mTLSAuthDirection)) { cfg += "key-direction "; cfg += mTLSAuthDirection; cfg += "\n"; } } if (!mUsePull) { if (!TextUtils.isEmpty(mIPv4Address)) cfg += "ifconfig " + cidrToIPAndNetmask(mIPv4Address) + "\n"; if (!TextUtils.isEmpty(mIPv6Address)) cfg += "ifconfig-ipv6 " + mIPv6Address + "\n"; } if (mUsePull && mRoutenopull) cfg += "route-nopull\n"; String routes = ""; if (mUseDefaultRoute) routes += "route 0.0.0.0 0.0.0.0 vpn_gateway\n"; else { for (String route : getCustomRoutes(mCustomRoutes)) { routes += "route " + route + " vpn_gateway\n"; } for (String route : getCustomRoutes(mExcludedRoutes)) { routes += "route " + route + " net_gateway\n"; } } if (mUseDefaultRoutev6) cfg += "route-ipv6 ::/0\n"; else for (String route : getCustomRoutesv6(mCustomRoutesv6)) { routes += "route-ipv6 " + route + "\n"; } cfg += routes; if (mOverrideDNS || !mUsePull) { if (!TextUtils.isEmpty(mDNS1)) cfg += "dhcp-option DNS " + mDNS1 + "\n"; if (!TextUtils.isEmpty(mDNS2)) cfg += "dhcp-option DNS " + mDNS2 + "\n"; if (!TextUtils.isEmpty(mSearchDomain)) cfg += "dhcp-option DOMAIN " + mSearchDomain + "\n"; } if (mMssFix != 0) { if (mMssFix != 1450) { cfg += String.format(Locale.US, "mssfix %d\n", mMssFix); } else cfg += "mssfix\n"; } if (mNobind) cfg += "nobind\n"; // Authentication if (mAuthenticationType != TYPE_STATICKEYS) { if (mCheckRemoteCN) { if (mRemoteCN == null || mRemoteCN.equals("")) cfg += "verify-x509-name " + openVpnEscape(mConnections[0].mServerName) + " name\n"; else switch (mX509AuthType) { // 2.2 style x509 checks case X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING: cfg += "compat-names no-remapping\n"; case X509_VERIFY_TLSREMOTE: cfg += "tls-remote " + openVpnEscape(mRemoteCN) + "\n"; break; case X509_VERIFY_TLSREMOTE_RDN: cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name\n"; break; case X509_VERIFY_TLSREMOTE_RDN_PREFIX: cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + " name-prefix\n"; break; case X509_VERIFY_TLSREMOTE_DN: cfg += "verify-x509-name " + openVpnEscape(mRemoteCN) + "\n"; break; } if (!TextUtils.isEmpty(mx509UsernameField)) cfg+= "x509-username-field " + openVpnEscape(mx509UsernameField) +"\n"; } if (mExpectTLSCert) cfg += "remote-cert-tls server\n"; } if (!TextUtils.isEmpty(mCipher)) { cfg += "cipher " + mCipher + "\n"; } if (!TextUtils.isEmpty(mAuth)) { cfg += "auth " + mAuth + "\n"; } // Obscure Settings dialog if (mUseRandomHostname) cfg += "#my favorite options :)\nremote-random-hostname\n"; if (mUseFloat) cfg += "float\n"; if (mPersistTun) { cfg += "persist-tun\n"; cfg += "# persist-tun also enables pre resolving to avoid DNS resolve problem\n"; cfg += "preresolve\n"; } if (mPushPeerInfo) cfg += "push-peer-info\n"; SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); boolean usesystemproxy = prefs.getBoolean("usesystemproxy", true); if (usesystemproxy && !mIsOpenVPN22) { cfg += "# Use system proxy setting\n"; cfg += "management-query-proxy\n"; } if (mUseCustomConfig) { cfg += "# Custom configuration options\n"; cfg += "# You are on your on own here :)\n"; cfg += mCustomConfigOptions; cfg += "\n"; } if (!canUsePlainRemotes) { cfg += "# Connection Options are at the end to allow global options (and global custom options) to influence connection blocks\n"; for (Connection conn : mConnections) { if (conn.mEnabled) { cfg += "\n"; cfg += conn.getConnectionBlock(); cfg += "\n"; } } } return cfg; } public String getVersionEnvString(Context c) { String version = "unknown"; try { PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0); version = packageinfo.versionName; } catch (PackageManager.NameNotFoundException e) { VpnStatus.logException(e); } return String.format(Locale.US, "%s %s", c.getPackageName(), version); } //! Put inline data inline and other data as normal escaped filename public static String insertFileData(String cfgentry, String filedata) { if (filedata == null) { return String.format("%s %s\n", cfgentry, "file missing in config profile"); } else if (isEmbedded(filedata)) { String dataWithOutHeader = getEmbeddedContent(filedata); return String.format(Locale.ENGLISH, "<%s>\n%s\n\n", cfgentry, dataWithOutHeader, cfgentry); } else { return String.format(Locale.ENGLISH, "%s %s\n", cfgentry, openVpnEscape(filedata)); } } @NonNull private Collection getCustomRoutes(String routes) { Vector cidrRoutes = new Vector<>(); if (routes == null) { // No routes set, return empty vector return cidrRoutes; } for (String route : routes.split("[\n \t]")) { if (!route.equals("")) { String cidrroute = cidrToIPAndNetmask(route); if (cidrroute == null) return cidrRoutes; cidrRoutes.add(cidrroute); } } return cidrRoutes; } private Collection getCustomRoutesv6(String routes) { Vector cidrRoutes = new Vector<>(); if (routes == null) { // No routes set, return empty vector return cidrRoutes; } for (String route : routes.split("[\n \t]")) { if (!route.equals("")) { cidrRoutes.add(route); } } return cidrRoutes; } private String cidrToIPAndNetmask(String route) { String[] parts = route.split("/"); // No /xx, assume /32 as netmask if (parts.length == 1) parts = (route + "/32").split("/"); if (parts.length != 2) return null; int len; try { len = Integer.parseInt(parts[1]); } catch (NumberFormatException ne) { return null; } if (len < 0 || len > 32) return null; long nm = 0xffffffffL; nm = (nm << (32 - len)) & 0xffffffffL; String netmask = String.format(Locale.ENGLISH, "%d.%d.%d.%d", (nm & 0xff000000) >> 24, (nm & 0xff0000) >> 16, (nm & 0xff00) >> 8, nm & 0xff); return parts[0] + " " + netmask; } public Intent prepareStartService(Context context) { Intent intent = getStartServiceIntent(context); // TODO: Handle this?! // if (mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) { // if (getKeyStoreCertificates(context) == null) // return null; // } return intent; } public void writeConfigFile(Context context) throws IOException { FileWriter cfg = new FileWriter(VPNLaunchHelper.getConfigFilePath(context)); cfg.write(getConfigFile(context, false)); cfg.flush(); cfg.close(); } public Intent getStartServiceIntent(Context context) { String prefix = context.getPackageName(); Intent intent = new Intent(context, OpenVPNService.class); intent.putExtra(prefix + ".profileUUID", mUuid.toString()); return intent; } public String[] getKeyStoreCertificates(Context context) { return getKeyStoreCertificates(context, 5); } public static String getDisplayName(String embeddedFile) { int start = DISPLAYNAME_TAG.length(); int end = embeddedFile.indexOf(INLINE_TAG); return embeddedFile.substring(start, end); } public static String getEmbeddedContent(String data) { if (!data.contains(INLINE_TAG)) return data; int start = data.indexOf(INLINE_TAG) + INLINE_TAG.length(); return data.substring(start); } public static boolean isEmbedded(String data) { if (data == null) return false; if (data.startsWith(INLINE_TAG) || data.startsWith(DISPLAYNAME_TAG)) return true; else return false; } public void checkForRestart(final Context context) { /* This method is called when OpenVPNService is restarted */ if ((mAuthenticationType == VpnProfile.TYPE_KEYSTORE || mAuthenticationType == VpnProfile.TYPE_USERPASS_KEYSTORE) && mPrivateKey == null) { new Thread(new Runnable() { @Override public void run() { getKeyStoreCertificates(context); } }).start(); } } @Override protected VpnProfile clone() throws CloneNotSupportedException { VpnProfile copy = (VpnProfile) super.clone(); copy.mUuid = UUID.randomUUID(); copy.mConnections = new Connection[mConnections.length]; int i = 0; for (Connection conn : mConnections) { copy.mConnections[i++] = conn.clone(); } copy.mAllowedAppsVpn = (HashSet) mAllowedAppsVpn.clone(); return copy; } public VpnProfile copy(String name) { try { VpnProfile copy = clone(); copy.mName = name; return copy; } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } } class NoCertReturnedException extends Exception { public NoCertReturnedException(String msg) { super(msg); } } synchronized String[] getKeyStoreCertificates(Context context, int tries) { // Force application context- KeyChain methods will block long enough that by the time they // are finished and try to unbind, the original activity context might have been destroyed. context = context.getApplicationContext(); try { PrivateKey privateKey = KeyChain.getPrivateKey(context, mAlias); mPrivateKey = privateKey; String keystoreChain = null; X509Certificate[] caChain = KeyChain.getCertificateChain(context, mAlias); if (caChain == null) throw new NoCertReturnedException("No certificate returned from Keystore"); if (caChain.length <= 1 && TextUtils.isEmpty(mCaFilename)) { VpnStatus.logMessage(VpnStatus.LogLevel.ERROR, "", context.getString(R.string.keychain_nocacert)); } else { StringWriter ksStringWriter = new StringWriter(); PemWriter pw = new PemWriter(ksStringWriter); for (int i = 1; i < caChain.length; i++) { X509Certificate cert = caChain[i]; pw.writeObject(new PemObject("CERTIFICATE", cert.getEncoded())); } pw.close(); keystoreChain = ksStringWriter.toString(); } String caout = null; if (!TextUtils.isEmpty(mCaFilename)) { try { Certificate[] cacerts = X509Utils.getCertificatesFromFile(mCaFilename); StringWriter caoutWriter = new StringWriter(); PemWriter pw = new PemWriter(caoutWriter); for (Certificate cert : cacerts) pw.writeObject(new PemObject("CERTIFICATE", cert.getEncoded())); pw.close(); caout = caoutWriter.toString(); } catch (Exception e) { VpnStatus.logError("Could not read CA certificate" + e.getLocalizedMessage()); } } StringWriter certout = new StringWriter(); if (caChain.length >= 1) { X509Certificate usercert = caChain[0]; PemWriter upw = new PemWriter(certout); upw.writeObject(new PemObject("CERTIFICATE", usercert.getEncoded())); upw.close(); } String user = certout.toString(); String ca, extra; if (caout == null) { ca = keystoreChain; extra = null; } else { ca = caout; extra = keystoreChain; } return new String[]{ca, extra, user}; } catch (InterruptedException | IOException | KeyChainException | NoCertReturnedException | IllegalArgumentException | CertificateException e) { e.printStackTrace(); VpnStatus.logError(R.string.keyChainAccessError, e.getLocalizedMessage()); VpnStatus.logError(R.string.keychain_access); if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) { if (!mAlias.matches("^[a-zA-Z0-9]$")) { VpnStatus.logError(R.string.jelly_keystore_alphanumeric_bug); } } return null; } catch (AssertionError e) { if (tries == 0) return null; VpnStatus.logError(String.format("Failure getting Keystore Keys (%s), retrying", e.getLocalizedMessage())); try { Thread.sleep(3000); } catch (InterruptedException e1) { VpnStatus.logException(e1); } return getKeyStoreCertificates(context, tries - 1); } } //! Return an error if something is wrong public int checkProfile(Context context) { if (mAuthenticationType == TYPE_KEYSTORE || mAuthenticationType == TYPE_USERPASS_KEYSTORE) { if (mAlias == null) return R.string.no_keystore_cert_selected; } if (!mUsePull || mAuthenticationType == TYPE_STATICKEYS) { if (mIPv4Address == null || cidrToIPAndNetmask(mIPv4Address) == null) return R.string.ipv4_format_error; } if (!mUseDefaultRoute) { if (!TextUtils.isEmpty(mCustomRoutes) && getCustomRoutes(mCustomRoutes).size() == 0) return R.string.custom_route_format_error; if (!TextUtils.isEmpty(mExcludedRoutes) && getCustomRoutes(mExcludedRoutes).size() == 0) return R.string.custom_route_format_error; } if (mUseTLSAuth && TextUtils.isEmpty(mTLSAuthFilename)) return R.string.missing_tlsauth; if ((mAuthenticationType == TYPE_USERPASS_CERTIFICATES || mAuthenticationType == TYPE_CERTIFICATES) && (TextUtils.isEmpty(mClientCertFilename) || TextUtils.isEmpty(mClientKeyFilename))) return R.string.missing_certificates; if ((mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) && TextUtils.isEmpty(mCaFilename)) return R.string.missing_ca_certificate; boolean noRemoteEnabled = true; for (Connection c : mConnections) if (c.mEnabled) noRemoteEnabled = false; if (noRemoteEnabled) return R.string.remote_no_server_selected; // Everything okay return R.string.no_error_found; } //! Openvpn asks for a "Private Key", this should be pkcs12 key // public String getPasswordPrivateKey() { if (mTransientPCKS12PW != null) { String pwcopy = mTransientPCKS12PW; mTransientPCKS12PW = null; return pwcopy; } switch (mAuthenticationType) { case TYPE_PKCS12: case TYPE_USERPASS_PKCS12: return mPKCS12Password; case TYPE_CERTIFICATES: case TYPE_USERPASS_CERTIFICATES: return mKeyPassword; case TYPE_USERPASS: case TYPE_STATICKEYS: default: return null; } } public boolean isUserPWAuth() { switch (mAuthenticationType) { case TYPE_USERPASS: case TYPE_USERPASS_CERTIFICATES: case TYPE_USERPASS_KEYSTORE: case TYPE_USERPASS_PKCS12: return true; default: return false; } } public boolean requireTLSKeyPassword() { if (TextUtils.isEmpty(mClientKeyFilename)) return false; String data = ""; if (isEmbedded(mClientKeyFilename)) data = mClientKeyFilename; else { char[] buf = new char[2048]; FileReader fr; try { fr = new FileReader(mClientKeyFilename); int len = fr.read(buf); while (len > 0) { data += new String(buf, 0, len); len = fr.read(buf); } fr.close(); } catch (FileNotFoundException e) { return false; } catch (IOException e) { return false; } } if (data.contains("Proc-Type: 4,ENCRYPTED")) return true; else if (data.contains("-----BEGIN ENCRYPTED PRIVATE KEY-----")) return true; else return false; } public int needUserPWInput(boolean ignoreTransient) { if ((mAuthenticationType == TYPE_PKCS12 || mAuthenticationType == TYPE_USERPASS_PKCS12) && (mPKCS12Password == null || mPKCS12Password.equals(""))) { if (ignoreTransient || mTransientPCKS12PW == null) return R.string.pkcs12_file_encryption_key; } if (mAuthenticationType == TYPE_CERTIFICATES || mAuthenticationType == TYPE_USERPASS_CERTIFICATES) { if (requireTLSKeyPassword() && TextUtils.isEmpty(mKeyPassword)) if (ignoreTransient || mTransientPCKS12PW == null) { return R.string.private_key_password; } } if (isUserPWAuth() && (TextUtils.isEmpty(mUsername) || (TextUtils.isEmpty(mPassword) && (mTransientPW == null || ignoreTransient)))) { return R.string.password; } return 0; } public String getPasswordAuth() { if (mTransientPW != null) { String pwcopy = mTransientPW; mTransientPW = null; return pwcopy; } else { return mPassword; } } // Used by the Array Adapter @Override public String toString() { return mName; } public String getUUIDString() { return mUuid.toString(); } public PrivateKey getKeystoreKey() { return mPrivateKey; } public String getSignedData(String b64data) { PrivateKey privkey = getKeystoreKey(); byte[] data = Base64.decode(b64data, Base64.DEFAULT); // The Jelly Bean *evil* Hack // 4.2 implements the RSA/ECB/PKCS1PADDING in the OpenSSLprovider if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) { return processSignJellyBeans(privkey, data); } try { /* ECB is perfectly fine in this special case, since we are using it for the public/private part in the TLS exchange */ @SuppressLint("GetInstance") Cipher rsaSigner = Cipher.getInstance("RSA/ECB/PKCS1PADDING"); rsaSigner.init(Cipher.ENCRYPT_MODE, privkey); byte[] signed_bytes = rsaSigner.doFinal(data); return Base64.encodeToString(signed_bytes, Base64.NO_WRAP); } catch (NoSuchAlgorithmException | InvalidKeyException | IllegalBlockSizeException | BadPaddingException | NoSuchPaddingException e) { VpnStatus.logError(R.string.error_rsa_sign, e.getClass().toString(), e.getLocalizedMessage()); return null; } } private String processSignJellyBeans(PrivateKey privkey, byte[] data) { try { Method getKey = privkey.getClass().getSuperclass().getDeclaredMethod("getOpenSSLKey"); getKey.setAccessible(true); // Real object type is OpenSSLKey Object opensslkey = getKey.invoke(privkey); getKey.setAccessible(false); Method getPkeyContext = opensslkey.getClass().getDeclaredMethod("getPkeyContext"); // integer pointer to EVP_pkey getPkeyContext.setAccessible(true); int pkey = (Integer) getPkeyContext.invoke(opensslkey); getPkeyContext.setAccessible(false); // 112 with TLS 1.2 (172 back with 4.3), 36 with TLS 1.0 byte[] signed_bytes = NativeUtils.rsasign(data, pkey); return Base64.encodeToString(signed_bytes, Base64.NO_WRAP); } catch (NoSuchMethodException | InvalidKeyException | InvocationTargetException | IllegalAccessException | IllegalArgumentException e) { VpnStatus.logError(R.string.error_rsa_sign, e.getClass().toString(), e.getLocalizedMessage()); return null; } } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/CIDRIP.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import java.util.Locale; class CIDRIP { String mIp; int len; public CIDRIP(String ip, String mask) { mIp = ip; long netmask = getInt(mask); // Add 33. bit to ensure the loop terminates netmask += 1l << 32; int lenZeros = 0; while ((netmask & 0x1) == 0) { lenZeros++; netmask = netmask >> 1; } // Check if rest of netmask is only 1s if (netmask != (0x1ffffffffl >> lenZeros)) { // Asume no CIDR, set /32 len = 32; } else { len = 32 - lenZeros; } } public CIDRIP(String address, int prefix_length) { len = prefix_length; mIp = address; } @Override public String toString() { return String.format(Locale.ENGLISH, "%s/%d", mIp, len); } public boolean normalise() { long ip = getInt(mIp); long newip = ip & (0xffffffffl << (32 - len)); if (newip != ip) { mIp = String.format("%d.%d.%d.%d", (newip & 0xff000000) >> 24, (newip & 0xff0000) >> 16, (newip & 0xff00) >> 8, newip & 0xff); return true; } else { return false; } } static long getInt(String ipaddr) { String[] ipt = ipaddr.split("\\."); long ip = 0; ip += Long.parseLong(ipt[0]) << 24; ip += Integer.parseInt(ipt[1]) << 16; ip += Integer.parseInt(ipt[2]) << 8; ip += Integer.parseInt(ipt[3]); return ip; } public long getInt() { return getInt(mIp); } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.text.TextUtils; import android.util.Pair; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.Collections; import java.util.HashMap; import java.util.Locale; import java.util.Vector; import de.blinkt.openvpn.VpnProfile; //! Openvpn Config FIle Parser, probably not 100% accurate but close enough // And remember, this is valid :) // -- // bar // public class ConfigParser { public static final String CONVERTED_PROFILE = "converted Profile"; private HashMap>> options = new HashMap>>(); private HashMap> meta = new HashMap>(); private String auth_user_pass_file; public void parseConfig(Reader reader) throws IOException, ConfigParseError { HashMap optionAliases = new HashMap<>(); optionAliases.put("server-poll-timeout", "timeout-connect"); BufferedReader br = new BufferedReader(reader); int lineno = 0; try { while (true) { String line = br.readLine(); lineno++; if (line == null) break; if (lineno == 1) { if ((line.startsWith("PK\003\004") || (line.startsWith("PK\007\008")))) { throw new ConfigParseError("Input looks like a ZIP Archive. Import is only possible for OpenVPN config files (.ovpn/.conf)"); } if (line.startsWith("\uFEFF")) { line = line.substring(1); } } // Check for OpenVPN Access Server Meta information if (line.startsWith("# OVPN_ACCESS_SERVER_")) { Vector metaarg = parsemeta(line); meta.put(metaarg.get(0), metaarg); continue; } Vector args = parseline(line); if (args.size() == 0) continue; if (args.get(0).startsWith("--")) args.set(0, args.get(0).substring(2)); checkinlinefile(args, br); String optionname = args.get(0); if (optionAliases.get(optionname)!=null) optionname = optionAliases.get(optionname); if (!options.containsKey(optionname)) { options.put(optionname, new Vector>()); } options.get(optionname).add(args); } } catch (java.lang.OutOfMemoryError memoryError) { throw new ConfigParseError("File too large to parse: " + memoryError.getLocalizedMessage()); } } private Vector parsemeta(String line) { String meta = line.split("#\\sOVPN_ACCESS_SERVER_", 2)[1]; String[] parts = meta.split("=", 2); Vector rval = new Vector(); Collections.addAll(rval, parts); return rval; } private void checkinlinefile(Vector args, BufferedReader br) throws IOException, ConfigParseError { String arg0 = args.get(0).trim(); // CHeck for if (arg0.startsWith("<") && arg0.endsWith(">")) { String argname = arg0.substring(1, arg0.length() - 1); String inlinefile = VpnProfile.INLINE_TAG; String endtag = String.format("", argname); do { String line = br.readLine(); if (line == null) { throw new ConfigParseError(String.format("No endtag for starttag <%s> found", argname, argname)); } if (line.trim().equals(endtag)) break; else { inlinefile += line; inlinefile += "\n"; } } while (true); args.clear(); args.add(argname); args.add(inlinefile); } } public String getAuthUserPassFile() { return auth_user_pass_file; } enum linestate { initial, readin_single_quote, reading_quoted, reading_unquoted, done } private boolean space(char c) { // I really hope nobody is using zero bytes inside his/her config file // to sperate parameter but here we go: return Character.isWhitespace(c) || c == '\0'; } public static class ConfigParseError extends Exception { private static final long serialVersionUID = -60L; public ConfigParseError(String msg) { super(msg); } } // adapted openvpn's parse function to java private Vector parseline(String line) throws ConfigParseError { Vector parameters = new Vector(); if (line.length() == 0) return parameters; linestate state = linestate.initial; boolean backslash = false; char out = 0; int pos = 0; String currentarg = ""; do { // Emulate the c parsing ... char in; if (pos < line.length()) in = line.charAt(pos); else in = '\0'; if (!backslash && in == '\\' && state != linestate.readin_single_quote) { backslash = true; } else { if (state == linestate.initial) { if (!space(in)) { if (in == ';' || in == '#') /* comment */ break; if (!backslash && in == '\"') state = linestate.reading_quoted; else if (!backslash && in == '\'') state = linestate.readin_single_quote; else { out = in; state = linestate.reading_unquoted; } } } else if (state == linestate.reading_unquoted) { if (!backslash && space(in)) state = linestate.done; else out = in; } else if (state == linestate.reading_quoted) { if (!backslash && in == '\"') state = linestate.done; else out = in; } else if (state == linestate.readin_single_quote) { if (in == '\'') state = linestate.done; else out = in; } if (state == linestate.done) { /* ASSERT (parm_len > 0); */ state = linestate.initial; parameters.add(currentarg); currentarg = ""; out = 0; } if (backslash && out != 0) { if (!(out == '\\' || out == '\"' || space(out))) { throw new ConfigParseError("Options warning: Bad backslash ('\\') usage"); } } backslash = false; } /* store parameter character */ if (out != 0) { currentarg += out; } } while (pos++ < line.length()); return parameters; } final String[] unsupportedOptions = {"config", "tls-server" }; // Ignore all scripts // in most cases these won't work and user who wish to execute scripts will // figure out themselves final String[] ignoreOptions = {"tls-client", "askpass", "auth-nocache", "up", "down", "route-up", "ipchange", "route-up", "route-pre-down", "auth-user-pass-verify", "block-outside-dns", "dhcp-release", "dhcp-renew", "dh", "group", "ip-win32", "management-hold", "management", "management-client", "management-query-remote", "management-query-passwords", "management-query-proxy", "management-external-key", "management-forget-disconnect", "management-signal", "management-log-cache", "management-up-down", "management-client-user", "management-client-group", "pause-exit", "plugin", "machine-readable-output", "persist-key", "push", "register-dns", "route-delay", "route-gateway", "route-metric", "route-method", "status", "script-security", "show-net-up", "suppress-timestamps", "tmp-dir", "tun-ipv6", "topology", "user", "win-sys", }; final String[][] ignoreOptionsWithArg = { {"setenv", "IV_GUI_VER"}, {"setenv", "IV_OPENVPN_GUI_VERSION"}, {"engine", "dynamic"}, {"setenv", "CLIENT_CERT"} }; final String[] connectionOptions = { "local", "remote", "float", "port", "connect-retry", "connect-timeout", "connect-retry-max", "link-mtu", "tun-mtu", "tun-mtu-extra", "fragment", "mtu-disc", "local-port", "remote-port", "bind", "nobind", "proto", "http-proxy", "http-proxy-retry", "http-proxy-timeout", "http-proxy-option", "socks-proxy", "socks-proxy-retry", "explicit-exit-notify", "mssfix" }; // This method is far too long @SuppressWarnings("ConstantConditions") public VpnProfile convertProfile() throws ConfigParseError, IOException { boolean noauthtypeset = true; VpnProfile np = new VpnProfile(CONVERTED_PROFILE); // Pull, client, tls-client np.clearDefaults(); if (options.containsKey("client") || options.containsKey("pull")) { np.mUsePull = true; options.remove("pull"); options.remove("client"); } Vector secret = getOption("secret", 1, 2); if (secret != null) { np.mAuthenticationType = VpnProfile.TYPE_STATICKEYS; noauthtypeset = false; np.mUseTLSAuth = true; np.mTLSAuthFilename = secret.get(1); if (secret.size() == 3) np.mTLSAuthDirection = secret.get(2); } Vector> routes = getAllOption("route", 1, 4); if (routes != null) { String routeopt = ""; String routeExcluded = ""; for (Vector route : routes) { String netmask = "255.255.255.255"; String gateway = "vpn_gateway"; if (route.size() >= 3) netmask = route.get(2); if (route.size() >= 4) gateway = route.get(3); String net = route.get(1); try { CIDRIP cidr = new CIDRIP(net, netmask); if (gateway.equals("net_gateway")) routeExcluded += cidr.toString() + " "; else routeopt += cidr.toString() + " "; } catch (ArrayIndexOutOfBoundsException aioob) { throw new ConfigParseError("Could not parse netmask of route " + netmask); } catch (NumberFormatException ne) { throw new ConfigParseError("Could not parse netmask of route " + netmask); } } np.mCustomRoutes = routeopt; np.mExcludedRoutes = routeExcluded; } Vector> routesV6 = getAllOption("route-ipv6", 1, 4); if (routesV6 != null) { String customIPv6Routes = ""; for (Vector route : routesV6) { customIPv6Routes += route.get(1) + " "; } np.mCustomRoutesv6 = customIPv6Routes; } Vector routeNoPull = getOption("route-nopull", 1, 1); if (routeNoPull!=null) np.mRoutenopull=true; // Also recognize tls-auth [inline] direction ... Vector> tlsauthoptions = getAllOption("tls-auth", 1, 2); if (tlsauthoptions != null) { for (Vector tlsauth : tlsauthoptions) { if (tlsauth != null) { if (!tlsauth.get(1).equals("[inline]")) { np.mTLSAuthFilename = tlsauth.get(1); np.mUseTLSAuth = true; } if (tlsauth.size() == 3) np.mTLSAuthDirection = tlsauth.get(2); } } } Vector direction = getOption("key-direction", 1, 1); if (direction != null) np.mTLSAuthDirection = direction.get(1); Vector> defgw = getAllOption("redirect-gateway", 0, 5); if (defgw != null) { np.mUseDefaultRoute = true; checkRedirectParameters(np, defgw); } Vector> redirectPrivate = getAllOption("redirect-private", 0, 5); if (redirectPrivate != null) { checkRedirectParameters(np, redirectPrivate); } Vector dev = getOption("dev", 1, 1); Vector devtype = getOption("dev-type", 1, 1); if ((devtype != null && devtype.get(1).equals("tun")) || (dev != null && dev.get(1).startsWith("tun")) || (devtype == null && dev == null)) { //everything okay } else { throw new ConfigParseError("Sorry. Only tun mode is supported. See the FAQ for more detail"); } Vector mssfix = getOption("mssfix", 0, 1); if (mssfix != null) { if (mssfix.size() >= 2) { try { np.mMssFix = Integer.parseInt(mssfix.get(1)); } catch (NumberFormatException e) { throw new ConfigParseError("Argument to --mssfix has to be an integer"); } } else { np.mMssFix = 1450; // OpenVPN default size } } Vector mode = getOption("mode", 1, 1); if (mode != null) { if (!mode.get(1).equals("p2p")) throw new ConfigParseError("Invalid mode for --mode specified, need p2p"); } Vector> dhcpoptions = getAllOption("dhcp-option", 2, 2); if (dhcpoptions != null) { for (Vector dhcpoption : dhcpoptions) { String type = dhcpoption.get(1); String arg = dhcpoption.get(2); if (type.equals("DOMAIN")) { np.mSearchDomain = dhcpoption.get(2); } else if (type.equals("DNS")) { np.mOverrideDNS = true; if (np.mDNS1.equals(VpnProfile.DEFAULT_DNS1)) np.mDNS1 = arg; else np.mDNS2 = arg; } } } Vector ifconfig = getOption("ifconfig", 2, 2); if (ifconfig != null) { try { CIDRIP cidr = new CIDRIP(ifconfig.get(1), ifconfig.get(2)); np.mIPv4Address = cidr.toString(); } catch (NumberFormatException nfe) { throw new ConfigParseError("Could not pase ifconfig IP address: " + nfe.getLocalizedMessage()); } } if (getOption("remote-random-hostname", 0, 0) != null) np.mUseRandomHostname = true; if (getOption("float", 0, 0) != null) np.mUseFloat = true; if (getOption("comp-lzo", 0, 1) != null) np.mUseLzo = true; Vector cipher = getOption("cipher", 1, 1); if (cipher != null) np.mCipher = cipher.get(1); Vector auth = getOption("auth", 1, 1); if (auth != null) np.mAuth = auth.get(1); Vector ca = getOption("ca", 1, 1); if (ca != null) { np.mCaFilename = ca.get(1); } Vector cert = getOption("cert", 1, 1); if (cert != null) { np.mClientCertFilename = cert.get(1); np.mAuthenticationType = VpnProfile.TYPE_CERTIFICATES; noauthtypeset = false; } Vector key = getOption("key", 1, 1); if (key != null) np.mClientKeyFilename = key.get(1); Vector pkcs12 = getOption("pkcs12", 1, 1); if (pkcs12 != null) { np.mPKCS12Filename = pkcs12.get(1); np.mAuthenticationType = VpnProfile.TYPE_KEYSTORE; noauthtypeset = false; } Vector cryptoapicert = getOption("cryptoapicert", 1, 1); if (cryptoapicert != null) { np.mAuthenticationType = VpnProfile.TYPE_KEYSTORE; noauthtypeset = false; } Vector compatnames = getOption("compat-names", 1, 2); Vector nonameremapping = getOption("no-name-remapping", 1, 1); Vector tlsremote = getOption("tls-remote", 1, 1); if (tlsremote != null) { np.mRemoteCN = tlsremote.get(1); np.mCheckRemoteCN = true; np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE; if ((compatnames != null && compatnames.size() > 2) || (nonameremapping != null)) np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_COMPAT_NOREMAPPING; } Vector verifyx509name = getOption("verify-x509-name", 1, 2); if (verifyx509name != null) { np.mRemoteCN = verifyx509name.get(1); np.mCheckRemoteCN = true; if (verifyx509name.size() > 2) { if (verifyx509name.get(2).equals("name")) np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_RDN; else if (verifyx509name.get(2).equals("subject")) np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_DN; else if (verifyx509name.get(2).equals("name-prefix")) np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_RDN_PREFIX; else throw new ConfigParseError("Unknown parameter to verify-x509-name: " + verifyx509name.get(2)); } else { np.mX509AuthType = VpnProfile.X509_VERIFY_TLSREMOTE_DN; } } Vector x509usernamefield = getOption("x509-username-field", 1,1); if (x509usernamefield!=null) { np.mx509UsernameField = x509usernamefield.get(1); } Vector verb = getOption("verb", 1, 1); if (verb != null) { np.mVerb = verb.get(1); } if (getOption("nobind", 0, 0) != null) np.mNobind = true; if (getOption("persist-tun", 0, 0) != null) np.mPersistTun = true; if (getOption("push-peer-info", 0, 0) != null) np.mPushPeerInfo = true; Vector connectretry = getOption("connect-retry", 1, 2); if (connectretry != null) { np.mConnectRetry = connectretry.get(1); if (connectretry.size() > 2) np.mConnectRetryMaxTime = connectretry.get(2); } Vector connectretrymax = getOption("connect-retry-max", 1, 1); if (connectretrymax != null) np.mConnectRetryMax = connectretrymax.get(1); Vector> remotetls = getAllOption("remote-cert-tls", 1, 1); if (remotetls != null) if (remotetls.get(0).get(1).equals("server")) np.mExpectTLSCert = true; else options.put("remotetls", remotetls); Vector authuser = getOption("auth-user-pass", 0, 1); if (authuser != null) { if (noauthtypeset) { np.mAuthenticationType = VpnProfile.TYPE_USERPASS; } else if (np.mAuthenticationType == VpnProfile.TYPE_CERTIFICATES) { np.mAuthenticationType = VpnProfile.TYPE_USERPASS_CERTIFICATES; } else if (np.mAuthenticationType == VpnProfile.TYPE_KEYSTORE) { np.mAuthenticationType = VpnProfile.TYPE_USERPASS_KEYSTORE; } if (authuser.size() > 1) { if (!authuser.get(1).startsWith(VpnProfile.INLINE_TAG)) auth_user_pass_file = authuser.get(1); np.mUsername = null; useEmbbedUserAuth(np, authuser.get(1)); } } Vector crlfile = getOption("crl-verify", 1, 2); if (crlfile != null) { // If the 'dir' parameter is present just add it as custom option .. if (crlfile.size() == 3 && crlfile.get(2).equals("dir")) np.mCustomConfigOptions += TextUtils.join(" ", crlfile) + "\n"; else // Save the filename for the config converter to add later np.mCrlFilename = crlfile.get(1); } Pair conns = parseConnectionOptions(null); np.mConnections = conns.second; Vector> connectionBlocks = getAllOption("connection", 1, 1); if (np.mConnections.length > 0 && connectionBlocks != null) { throw new ConfigParseError("Using a block and --remote is not allowed."); } if (connectionBlocks != null) { np.mConnections = new Connection[connectionBlocks.size()]; int connIndex = 0; for (Vector conn : connectionBlocks) { Pair connectionBlockConnection = parseConnection(conn.get(1), conns.first); if (connectionBlockConnection.second.length != 1) throw new ConfigParseError("A block must have exactly one remote"); np.mConnections[connIndex] = connectionBlockConnection.second[0]; connIndex++; } } if (getOption("remote-random", 0, 0) != null) np.mRemoteRandom = true; Vector protoforce = getOption("proto-force", 1, 1); if (protoforce != null) { boolean disableUDP; String protoToDisable = protoforce.get(1); if (protoToDisable.equals("udp")) disableUDP = true; else if (protoToDisable.equals("tcp")) disableUDP = false; else throw new ConfigParseError(String.format("Unknown protocol %s in proto-force", protoToDisable)); for (Connection conn : np.mConnections) if (conn.mUseUdp == disableUDP) conn.mEnabled = false; } // Parse OpenVPN Access Server extra Vector friendlyname = meta.get("FRIENDLY_NAME"); if (friendlyname != null && friendlyname.size() > 1) np.mName = friendlyname.get(1); Vector ocusername = meta.get("USERNAME"); if (ocusername != null && ocusername.size() > 1) np.mUsername = ocusername.get(1); checkIgnoreAndInvalidOptions(np); fixup(np); return np; } private Pair parseConnection(String connection, Connection defaultValues) throws IOException, ConfigParseError { // Parse a connection Block as a new configuration file ConfigParser connectionParser = new ConfigParser(); StringReader reader = new StringReader(connection.substring(VpnProfile.INLINE_TAG.length())); connectionParser.parseConfig(reader); Pair conn = connectionParser.parseConnectionOptions(defaultValues); return conn; } private Pair parseConnectionOptions(Connection connDefault) throws ConfigParseError { Connection conn; if (connDefault != null) try { conn = connDefault.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); return null; } else conn = new Connection(); Vector port = getOption("port", 1, 1); if (port != null) { conn.mServerPort = port.get(1); } Vector rport = getOption("rport", 1, 1); if (rport != null) { conn.mServerPort = rport.get(1); } Vector proto = getOption("proto", 1, 1); if (proto != null) { conn.mUseUdp = isUdpProto(proto.get(1)); } Vector connectTimeout = getOption("connect-timeout", 1, 1); if (connectTimeout != null) { try { conn.mConnectTimeout = Integer.parseInt(connectTimeout.get(1)); } catch (NumberFormatException nfe) { throw new ConfigParseError(String.format("Argument to connect-timeout (%s) must to be an integer: %s", connectTimeout.get(1), nfe.getLocalizedMessage())); } } // Parse remote config Vector> remotes = getAllOption("remote", 1, 3); // Assume that we need custom options if connectionDefault are set if (connDefault != null) { for (Vector> option : options.values()) { conn.mCustomConfiguration += getOptionStrings(option); } if (!TextUtils.isEmpty(conn.mCustomConfiguration)) conn.mUseCustomConfig = true; } // Make remotes empty to simplify code if (remotes == null) remotes = new Vector>(); Connection[] connections = new Connection[remotes.size()]; int i = 0; for (Vector remote : remotes) { try { connections[i] = conn.clone(); } catch (CloneNotSupportedException e) { e.printStackTrace(); } switch (remote.size()) { case 4: connections[i].mUseUdp = isUdpProto(remote.get(3)); case 3: connections[i].mServerPort = remote.get(2); case 2: connections[i].mServerName = remote.get(1); } i++; } return Pair.create(conn, connections); } private void checkRedirectParameters(VpnProfile np, Vector> defgw) { for (Vector redirect : defgw) for (int i = 1; i < redirect.size(); i++) { if (redirect.get(i).equals("block-local")) np.mAllowLocalLAN = false; else if (redirect.get(i).equals("unblock-local")) np.mAllowLocalLAN = true; } } private boolean isUdpProto(String proto) throws ConfigParseError { boolean isudp; if (proto.equals("udp") || proto.equals("udp6")) isudp = true; else if (proto.equals("tcp-client") || proto.equals("tcp") || proto.equals("tcp6") || proto.endsWith("tcp6-client")) isudp = false; else throw new ConfigParseError("Unsupported option to --proto " + proto); return isudp; } static public void useEmbbedUserAuth(VpnProfile np, String inlinedata) { String data = VpnProfile.getEmbeddedContent(inlinedata); String[] parts = data.split("\n"); if (parts.length >= 2) { np.mUsername = parts[0]; np.mPassword = parts[1]; } } private void checkIgnoreAndInvalidOptions(VpnProfile np) throws ConfigParseError { for (String option : unsupportedOptions) if (options.containsKey(option)) throw new ConfigParseError(String.format("Unsupported Option %s encountered in config file. Aborting", option)); for (String option : ignoreOptions) // removing an item which is not in the map is no error options.remove(option); if (options.size() > 0) { np.mCustomConfigOptions = "# These options found in the config file do not map to config settings:\n" + np.mCustomConfigOptions; for (Vector> option : options.values()) { np.mCustomConfigOptions += getOptionStrings(option); } np.mUseCustomConfig = true; } } boolean ignoreThisOption(Vector option) { for (String[] ignoreOption : ignoreOptionsWithArg) { if (option.size() < ignoreOption.length) continue; boolean ignore = true; for (int i = 0; i < ignoreOption.length; i++) { if (!ignoreOption[i].equals(option.get(i))) ignore = false; } if (ignore) return true; } return false; } //! Generate options for custom options private String getOptionStrings(Vector> option) { String custom = ""; for (Vector optionsline : option) { if (!ignoreThisOption(optionsline)) { // Check if option had been inlined and inline again if (optionsline.size() == 2 && "extra-certs".equals(optionsline.get(0)) ) { custom += VpnProfile.insertFileData(optionsline.get(0), optionsline.get(1)); } else { for (String arg : optionsline) custom += VpnProfile.openVpnEscape(arg) + " "; custom += "\n"; } } } return custom; } private void fixup(VpnProfile np) { if (np.mRemoteCN.equals(np.mServerName)) { np.mRemoteCN = ""; } } private Vector getOption(String option, int minarg, int maxarg) throws ConfigParseError { Vector> alloptions = getAllOption(option, minarg, maxarg); if (alloptions == null) return null; else return alloptions.lastElement(); } private Vector> getAllOption(String option, int minarg, int maxarg) throws ConfigParseError { Vector> args = options.get(option); if (args == null) return null; for (Vector optionline : args) if (optionline.size() < (minarg + 1) || optionline.size() > maxarg + 1) { String err = String.format(Locale.getDefault(), "Option %s has %d parameters, expected between %d and %d", option, optionline.size() - 1, minarg, maxarg); throw new ConfigParseError(err); } options.remove(option); return args; } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/Connection.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.text.TextUtils; import java.io.Serializable; public class Connection implements Serializable, Cloneable { public String mServerName = "openvpn.blinkt.de"; public String mServerPort = "1194"; public boolean mUseUdp = true; public String mCustomConfiguration = ""; public boolean mUseCustomConfig = false; public boolean mEnabled = true; public int mConnectTimeout = 0; public static final int CONNECTION_DEFAULT_TIMEOUT = 120; private static final long serialVersionUID = 92031902903829089L; public String getConnectionBlock() { String cfg = ""; // Server Address cfg += "remote "; cfg += mServerName; cfg += " "; cfg += mServerPort; if (mUseUdp) cfg += " udp\n"; else cfg += " tcp-client\n"; if (mConnectTimeout != 0) cfg += String.format(" connect-timeout %d\n", mConnectTimeout); if (!TextUtils.isEmpty(mCustomConfiguration) && mUseCustomConfig) { cfg += mCustomConfiguration; cfg += "\n"; } return cfg; } @Override public Connection clone() throws CloneNotSupportedException { return (Connection) super.clone(); } public boolean isOnlyRemote() { return TextUtils.isEmpty(mCustomConfiguration) || !mUseCustomConfig; } public int getTimeout() { if (mConnectTimeout <= 0) return CONNECTION_DEFAULT_TIMEOUT; else return mConnectTimeout; } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/DeviceStateReceiver.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.NetworkInfo.State; import android.os.Handler; import android.preference.PreferenceManager; import com.vasilkoff.easyvpnfree.R; import de.blinkt.openvpn.core.VpnStatus.ByteCountListener; import java.util.LinkedList; import java.util.Objects; import static de.blinkt.openvpn.core.OpenVPNManagement.pauseReason; public class DeviceStateReceiver extends BroadcastReceiver implements ByteCountListener, OpenVPNManagement.PausedStateCallback { private final Handler mDisconnectHandler; private int lastNetwork = -1; private OpenVPNManagement mManagement; // Window time in s private final int TRAFFIC_WINDOW = 60; // Data traffic limit in bytes private final long TRAFFIC_LIMIT = 64 * 1024; // Time to wait after network disconnect to pause the VPN private final int DISCONNECT_WAIT = 20; connectState network = connectState.DISCONNECTED; connectState screen = connectState.SHOULDBECONNECTED; connectState userpause = connectState.SHOULDBECONNECTED; private String lastStateMsg = null; private java.lang.Runnable mDelayDisconnectRunnable = new Runnable() { @Override public void run() { if (!(network == connectState.PENDINGDISCONNECT)) return; network = connectState.DISCONNECTED; // Set screen state to be disconnected if disconnect pending if (screen == connectState.PENDINGDISCONNECT) screen = connectState.DISCONNECTED; mManagement.pause(getPauseReason()); } }; private NetworkInfo lastConnectedNetwork; @Override public boolean shouldBeRunning() { return shouldBeConnected(); } enum connectState { SHOULDBECONNECTED, PENDINGDISCONNECT, DISCONNECTED } static class Datapoint { private Datapoint(long t, long d) { timestamp = t; data = d; } long timestamp; long data; } LinkedList trafficdata = new LinkedList(); @Override public void updateByteCount(long in, long out, long diffIn, long diffOut) { if (screen != connectState.PENDINGDISCONNECT) return; long total = diffIn + diffOut; trafficdata.add(new Datapoint(System.currentTimeMillis(), total)); while (trafficdata.getFirst().timestamp <= (System.currentTimeMillis() - TRAFFIC_WINDOW * 1000)) { trafficdata.removeFirst(); } long windowtraffic = 0; for (Datapoint dp : trafficdata) windowtraffic += dp.data; if (windowtraffic < TRAFFIC_LIMIT) { screen = connectState.DISCONNECTED; VpnStatus.logInfo(R.string.screenoff_pause, OpenVPNService.humanReadableByteCount(TRAFFIC_LIMIT, false), TRAFFIC_WINDOW); mManagement.pause(getPauseReason()); } } public void userPause(boolean pause) { if (pause) { userpause = connectState.DISCONNECTED; // Check if we should disconnect mManagement.pause(getPauseReason()); } else { boolean wereConnected = shouldBeConnected(); userpause = connectState.SHOULDBECONNECTED; if (shouldBeConnected() && !wereConnected) mManagement.resume(); else // Update the reason why we currently paused mManagement.pause(getPauseReason()); } } public DeviceStateReceiver(OpenVPNManagement magnagement) { super(); mManagement = magnagement; mManagement.setPauseCallback(this); mDisconnectHandler = new Handler(); } @Override public void onReceive(Context context, Intent intent) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); if (ConnectivityManager.CONNECTIVITY_ACTION.equals(intent.getAction())) { networkStateChange(context); } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) { boolean screenOffPause = prefs.getBoolean("screenoff", false); if (screenOffPause) { if (ProfileManager.getLastConnectedVpn() != null && !ProfileManager.getLastConnectedVpn().mPersistTun) VpnStatus.logError(R.string.screen_nopersistenttun); screen = connectState.PENDINGDISCONNECT; fillTrafficData(); if (network == connectState.DISCONNECTED || userpause == connectState.DISCONNECTED) screen = connectState.DISCONNECTED; } } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) { // Network was disabled because screen off boolean connected = shouldBeConnected(); screen = connectState.SHOULDBECONNECTED; /* We should connect now, cancel any outstanding disconnect timer */ mDisconnectHandler.removeCallbacks(mDelayDisconnectRunnable); /* should be connected has changed because the screen is on now, connect the VPN */ if (shouldBeConnected() != connected) mManagement.resume(); else if (!shouldBeConnected()) /*Update the reason why we are still paused */ mManagement.pause(getPauseReason()); } } private void fillTrafficData() { trafficdata.add(new Datapoint(System.currentTimeMillis(), TRAFFIC_LIMIT)); } public static boolean equalsObj(Object a, Object b) { return (a == null) ? (b == null) : a.equals(b); } public void networkStateChange(Context context) { NetworkInfo networkInfo = getCurrentNetworkInfo(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); boolean sendusr1 = prefs.getBoolean("netchangereconnect", true); String netstatestring; if (networkInfo == null) { netstatestring = "not connected"; } else { String subtype = networkInfo.getSubtypeName(); if (subtype == null) subtype = ""; String extrainfo = networkInfo.getExtraInfo(); if (extrainfo == null) extrainfo = ""; /* if(networkInfo.getType()==android.net.ConnectivityManager.TYPE_WIFI) { WifiManager wifiMgr = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); WifiInfo wifiinfo = wifiMgr.getConnectionInfo(); extrainfo+=wifiinfo.getBSSID(); subtype += wifiinfo.getNetworkId(); }*/ netstatestring = String.format("%2$s %4$s to %1$s %3$s", networkInfo.getTypeName(), networkInfo.getDetailedState(), extrainfo, subtype); } if (networkInfo != null && networkInfo.getState() == State.CONNECTED) { int newnet = networkInfo.getType(); boolean pendingDisconnect = (network == connectState.PENDINGDISCONNECT); network = connectState.SHOULDBECONNECTED; boolean sameNetwork; if (lastConnectedNetwork == null || lastConnectedNetwork.getType() != networkInfo.getType() || !equalsObj(lastConnectedNetwork.getExtraInfo(), networkInfo.getExtraInfo()) ) sameNetwork = false; else sameNetwork = true; /* Same network, connection still 'established' */ if (pendingDisconnect && sameNetwork) { mDisconnectHandler.removeCallbacks(mDelayDisconnectRunnable); // Reprotect the sockets just be sure mManagement.networkChange(true); } else { /* Different network or connection not established anymore */ if (screen == connectState.PENDINGDISCONNECT) screen = connectState.DISCONNECTED; if (shouldBeConnected()) { mDisconnectHandler.removeCallbacks(mDelayDisconnectRunnable); if (pendingDisconnect || !sameNetwork) mManagement.networkChange(sameNetwork); else mManagement.resume(); } lastNetwork = newnet; lastConnectedNetwork = networkInfo; } } else if (networkInfo == null) { // Not connected, stop openvpn, set last connected network to no network lastNetwork = -1; if (sendusr1) { network = connectState.PENDINGDISCONNECT; mDisconnectHandler.postDelayed(mDelayDisconnectRunnable, DISCONNECT_WAIT * 1000); } } if (!netstatestring.equals(lastStateMsg)) VpnStatus.logInfo(R.string.netstatus, netstatestring); lastStateMsg = netstatestring; } public boolean isUserPaused() { return userpause == connectState.DISCONNECTED; } private boolean shouldBeConnected() { return (screen == connectState.SHOULDBECONNECTED && userpause == connectState.SHOULDBECONNECTED && network == connectState.SHOULDBECONNECTED); } private pauseReason getPauseReason() { if (userpause == connectState.DISCONNECTED) return pauseReason.userPause; if (screen == connectState.DISCONNECTED) return pauseReason.screenOff; if (network == connectState.DISCONNECTED) return pauseReason.noNetwork; return pauseReason.userPause; } private NetworkInfo getCurrentNetworkInfo(Context context) { ConnectivityManager conn = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); return conn.getActiveNetworkInfo(); } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/ICSOpenVPNApplication.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.app.Application; /* import org.acra.ACRA; import org.acra.ReportingInteractionMode; import org.acra.annotation.ReportsCrashes; */ import com.vasilkoff.easyvpnfree.BuildConfig; import de.blinkt.openvpn.core.PRNGFixes; /* @ReportsCrashes( formKey = "", formUri = "http://reports.blinkt.de/report-icsopenvpn", reportType = org.acra.sender.HttpSender.Type.JSON, httpMethod = org.acra.sender.HttpSender.Method.PUT, formUriBasicAuthLogin="report-icsopenvpn", formUriBasicAuthPassword="Tohd4neiF9Ai!!!!111eleven", mode = ReportingInteractionMode.TOAST, resToastText = R.string.crash_toast_text ) */ public class ICSOpenVPNApplication extends Application { @Override public void onCreate() { super.onCreate(); PRNGFixes.apply(); if (BuildConfig.DEBUG) { //ACRA.init(this); } VpnStatus.initLogCache(getApplicationContext().getCacheDir()); } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/LogFileHandler.java ================================================ /* * Copyright (c) 2012-2015 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.os.Handler; import android.os.Looper; import android.os.Message; import com.vasilkoff.easyvpnfree.R; import java.io.BufferedInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.util.Locale; /** * Created by arne on 23.01.16. */ class LogFileHandler extends Handler { static final int TRIM_LOG_FILE = 100; static final int FLUSH_TO_DISK = 101; static final int LOG_INIT = 102; public static final int LOG_MESSAGE = 103; public static final int MAGIC_BYTE = 0x55; protected OutputStream mLogFile; public static final String LOGFILE_NAME = "logcache.dat"; public LogFileHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { try { if (msg.what == LOG_INIT) { if (mLogFile != null) throw new RuntimeException("mLogFile not null"); readLogCache((File) msg.obj); openLogFile((File) msg.obj); } else if (msg.what == LOG_MESSAGE && msg.obj instanceof LogItem) { // Ignore log messages if not yet initialized if (mLogFile == null) return; writeLogItemToDisk((LogItem) msg.obj); } else if (msg.what == TRIM_LOG_FILE) { trimLogFile(); for (LogItem li : VpnStatus.getlogbuffer()) writeLogItemToDisk(li); } else if (msg.what == FLUSH_TO_DISK) { flushToDisk(); } } catch (IOException | BufferOverflowException e) { e.printStackTrace(); VpnStatus.logError("Error during log cache: " + msg.what); VpnStatus.logException(e); } } private void flushToDisk() throws IOException { mLogFile.flush(); } private void trimLogFile() { try { mLogFile.flush(); ((FileOutputStream) mLogFile).getChannel().truncate(0); } catch (IOException e) { e.printStackTrace(); } } private void writeLogItemToDisk(LogItem li) throws IOException { // We do not really care if the log cache breaks between Android upgrades, // write binary format to disc byte[] liBytes = li.getMarschaledBytes(); writeEscapedBytes(liBytes); } public void writeEscapedBytes(byte[] bytes) throws IOException { int magic = 0; for (byte b : bytes) if (b == MAGIC_BYTE || b == MAGIC_BYTE + 1) magic++; byte eBytes[] = new byte[bytes.length + magic]; int i = 0; for (byte b : bytes) { if (b == MAGIC_BYTE || b == MAGIC_BYTE + 1) { eBytes[i++] = MAGIC_BYTE + 1; eBytes[i++] = (byte) (b - MAGIC_BYTE); } else { eBytes[i++] = b; } } byte[] lenBytes = ByteBuffer.allocate(4).putInt(bytes.length).array(); synchronized (mLogFile) { mLogFile.write(MAGIC_BYTE); mLogFile.write(lenBytes); mLogFile.write(eBytes); } } private void openLogFile(File cacheDir) throws FileNotFoundException { File logfile = new File(cacheDir, LOGFILE_NAME); mLogFile = new FileOutputStream(logfile); } private void readLogCache(File cacheDir) { try { File logfile = new File(cacheDir, LOGFILE_NAME); if (!logfile.exists() || !logfile.canRead()) return; readCacheContents(new FileInputStream(logfile)); } catch (java.io.IOException | java.lang.RuntimeException e ) { VpnStatus.logError("Reading cached logfile failed"); VpnStatus.logException(e); e.printStackTrace(); // ignore reading file error } } protected void readCacheContents(InputStream in) throws IOException { BufferedInputStream logFile = new BufferedInputStream(in); byte[] buf = new byte[16384]; int read = logFile.read(buf, 0, 5); int itemsRead = 0; readloop: while (read >= 5) { int skipped = 0; while (buf[skipped] != MAGIC_BYTE) { skipped++; if (!(logFile.read(buf, skipped + 4, 1) == 1) || skipped + 10 > buf.length) { VpnStatus.logDebug(String.format(Locale.US, "Skipped %d bytes and no a magic byte found", skipped)); break readloop; } } if (skipped > 0) VpnStatus.logDebug(String.format(Locale.US, "Skipped %d bytes before finding a magic byte", skipped)); int len = ByteBuffer.wrap(buf, skipped+1, 4).asIntBuffer().get(); // Marshalled LogItem int pos = 0; byte buf2[] = new byte[buf.length]; while (pos < len) { byte b = (byte) logFile.read(); if (b == MAGIC_BYTE) { VpnStatus.logDebug(String.format(Locale.US, "Unexpected magic byte found at pos %d, abort current log item", pos)); read = logFile.read(buf, 1, 4) + 1; continue readloop; } else if (b == MAGIC_BYTE + 1) { b = (byte) logFile.read(); if (b == 0) b = MAGIC_BYTE; else if (b == 1) b = MAGIC_BYTE + 1; else { VpnStatus.logDebug(String.format(Locale.US, "Escaped byte not 0 or 1: %d", b)); read = logFile.read(buf, 1, 4) + 1; continue readloop; } } buf2[pos++] = b; } restoreLogItem(buf2, len); //Next item read = logFile.read(buf, 0, 5); itemsRead++; if (itemsRead > 2 * VpnStatus.MAXLOGENTRIES) { VpnStatus.logError("Too many logentries read from cache, aborting."); read = 0; } } VpnStatus.logDebug(R.string.reread_log, itemsRead); } protected void restoreLogItem(byte[] buf, int len) throws UnsupportedEncodingException { LogItem li = new LogItem(buf, len); if (li.verify()) { VpnStatus.newLogItem(li, true); } else { VpnStatus.logError(String.format(Locale.getDefault(), "Could not read log item from file: %d: %s", len, bytesToHex(buf, Math.max(len, 80)))); } } final protected static char[] hexArray = "0123456789ABCDEF".toCharArray(); public static String bytesToHex(byte[] bytes, int len) { len = Math.min(bytes.length, len); char[] hexChars = new char[len * 2]; for (int j = 0; j < len; j++) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/LogItem.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; import android.util.Log; import com.vasilkoff.easyvpnfree.R; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.UnsupportedEncodingException; import java.nio.BufferOverflowException; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.FormatFlagsConversionMismatchException; import java.util.Locale; import java.util.UnknownFormatConversionException; /** * Created by arne on 24.04.16. */ public class LogItem implements Parcelable { private Object[] mArgs = null; private String mMessage = null; private int mRessourceId; // Default log priority VpnStatus.LogLevel mLevel = VpnStatus.LogLevel.INFO; private long logtime = System.currentTimeMillis(); private int mVerbosityLevel = -1; private LogItem(int ressourceId, Object[] args) { mRessourceId = ressourceId; mArgs = args; } public LogItem(VpnStatus.LogLevel level, int verblevel, String message) { mMessage = message; mLevel = level; mVerbosityLevel = verblevel; } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeArray(mArgs); dest.writeString(mMessage); dest.writeInt(mRessourceId); dest.writeInt(mLevel.getInt()); dest.writeInt(mVerbosityLevel); dest.writeLong(logtime); } @Override public boolean equals(Object obj) { if (!(obj instanceof LogItem)) return obj.equals(this); LogItem other = (LogItem) obj; return Arrays.equals(mArgs, other.mArgs) && ((other.mMessage == null && mMessage == other.mMessage) || mMessage.equals(other.mMessage)) && mRessourceId == other.mRessourceId && ((mLevel == null && other.mLevel == mLevel) || other.mLevel.equals(mLevel)) && mVerbosityLevel == other.mVerbosityLevel && logtime == other.logtime; } public byte[] getMarschaledBytes() throws UnsupportedEncodingException, BufferOverflowException { ByteBuffer bb = ByteBuffer.allocate(16384); bb.put((byte) 0x0); //version bb.putLong(logtime); //8 bb.putInt(mVerbosityLevel); //4 bb.putInt(mLevel.getInt()); bb.putInt(mRessourceId); if (mMessage == null || mMessage.length() == 0) { bb.putInt(0); } else { marschalString(mMessage, bb); } if (mArgs == null || mArgs.length == 0) { bb.putInt(0); } else { bb.putInt(mArgs.length); for (Object o : mArgs) { if (o instanceof String) { bb.putChar('s'); marschalString((String) o, bb); } else if (o instanceof Integer) { bb.putChar('i'); bb.putInt((Integer) o); } else if (o instanceof Float) { bb.putChar('f'); bb.putFloat((Float) o); } else if (o instanceof Double) { bb.putChar('d'); bb.putDouble((Double) o); } else if (o instanceof Long) { bb.putChar('l'); bb.putLong((Long) o); } else if (o == null) { bb.putChar('0'); } else { VpnStatus.logDebug("Unknown object for LogItem marschaling " + o); bb.putChar('s'); marschalString(o.toString(), bb); } } } int pos = bb.position(); bb.rewind(); return Arrays.copyOf(bb.array(), pos); } public LogItem(byte[] in, int length) throws UnsupportedEncodingException { ByteBuffer bb = ByteBuffer.wrap(in, 0, length); bb.get(); // ignore version logtime = bb.getLong(); mVerbosityLevel = bb.getInt(); mLevel = VpnStatus.LogLevel.getEnumByValue(bb.getInt()); mRessourceId = bb.getInt(); int len = bb.getInt(); if (len == 0) { mMessage = null; } else { if (len > bb.remaining()) throw new IndexOutOfBoundsException("String length " + len + " is bigger than remaining bytes " + bb.remaining()); byte[] utf8bytes = new byte[len]; bb.get(utf8bytes); mMessage = new String(utf8bytes, "UTF-8"); } int numArgs = bb.getInt(); if (numArgs > 30) { throw new IndexOutOfBoundsException("Too many arguments for Logitem to unmarschal"); } if (numArgs == 0) { mArgs = null; } else { mArgs = new Object[numArgs]; for (int i = 0; i < numArgs; i++) { char type = bb.getChar(); switch (type) { case 's': mArgs[i] = unmarschalString(bb); break; case 'i': mArgs[i] = bb.getInt(); break; case 'd': mArgs[i] = bb.getDouble(); break; case 'f': mArgs[i] = bb.getFloat(); break; case 'l': mArgs[i] = bb.getLong(); break; case '0': mArgs[i] = null; break; default: throw new UnsupportedEncodingException("Unknown format type: " + type); } } } if (bb.hasRemaining()) throw new UnsupportedEncodingException(bb.remaining() + " bytes left after unmarshaling everything"); } private void marschalString(String str, ByteBuffer bb) throws UnsupportedEncodingException { byte[] utf8bytes = str.getBytes("UTF-8"); bb.putInt(utf8bytes.length); bb.put(utf8bytes); } private String unmarschalString(ByteBuffer bb) throws UnsupportedEncodingException { int len = bb.getInt(); byte[] utf8bytes = new byte[len]; bb.get(utf8bytes); return new String(utf8bytes, "UTF-8"); } public LogItem(Parcel in) { mArgs = in.readArray(Object.class.getClassLoader()); mMessage = in.readString(); mRessourceId = in.readInt(); mLevel = VpnStatus.LogLevel.getEnumByValue(in.readInt()); mVerbosityLevel = in.readInt(); logtime = in.readLong(); } public static final Creator CREATOR = new Creator() { public LogItem createFromParcel(Parcel in) { return new LogItem(in); } public LogItem[] newArray(int size) { return new LogItem[size]; } }; public LogItem(VpnStatus.LogLevel loglevel, int ressourceId, Object... args) { mRessourceId = ressourceId; mArgs = args; mLevel = loglevel; } public LogItem(VpnStatus.LogLevel loglevel, String msg) { mLevel = loglevel; mMessage = msg; } public LogItem(VpnStatus.LogLevel loglevel, int ressourceId) { mRessourceId = ressourceId; mLevel = loglevel; } public String getString(Context c) { try { if (mMessage != null) { return mMessage; } else { if (c != null) { if (mRessourceId == R.string.mobile_info) return getMobileInfoString(c); if (mArgs == null) return c.getString(mRessourceId); else return c.getString(mRessourceId, mArgs); } else { String str = String.format(Locale.ENGLISH, "Log (no context) resid %d", mRessourceId); if (mArgs != null) str += join("|", mArgs); return str; } } } catch (UnknownFormatConversionException e) { if (c != null) throw new UnknownFormatConversionException(e.getLocalizedMessage() + getString(null)); else throw e; } catch (java.util.FormatFlagsConversionMismatchException e) { if (c != null) throw new FormatFlagsConversionMismatchException(e.getLocalizedMessage() + getString(null), e.getConversion()); else throw e; } } // TextUtils.join will cause not macked exeception in tests .... public static String join(CharSequence delimiter, Object[] tokens) { StringBuilder sb = new StringBuilder(); boolean firstTime = true; for (Object token : tokens) { if (firstTime) { firstTime = false; } else { sb.append(delimiter); } sb.append(token); } return sb.toString(); } public VpnStatus.LogLevel getLogLevel() { return mLevel; } @Override public String toString() { return getString(null); } // The lint is wrong here @SuppressLint("StringFormatMatches") private String getMobileInfoString(Context c) { c.getPackageManager(); String apksign = "error getting package signature"; String version = "error getting version"; try { @SuppressLint("PackageManagerGetSignatures") Signature raw = c.getPackageManager().getPackageInfo(c.getPackageName(), PackageManager.GET_SIGNATURES).signatures[0]; CertificateFactory cf = CertificateFactory.getInstance("X.509"); X509Certificate cert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(raw.toByteArray())); MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] der = cert.getEncoded(); md.update(der); byte[] digest = md.digest(); if (Arrays.equals(digest, VpnStatus.officalkey)) apksign = c.getString(R.string.official_build); else if (Arrays.equals(digest, VpnStatus.officaldebugkey)) apksign = c.getString(R.string.debug_build); else if (Arrays.equals(digest, VpnStatus.amazonkey)) apksign = "amazon version"; else if (Arrays.equals(digest, VpnStatus.fdroidkey)) apksign = "F-Droid built and signed version"; else apksign = c.getString(R.string.built_by, cert.getSubjectX500Principal().getName()); PackageInfo packageinfo = c.getPackageManager().getPackageInfo(c.getPackageName(), 0); version = packageinfo.versionName; } catch (PackageManager.NameNotFoundException | CertificateException | NoSuchAlgorithmException ignored) { } Object[] argsext = Arrays.copyOf(mArgs, mArgs.length); argsext[argsext.length - 1] = apksign; argsext[argsext.length - 2] = version; return c.getString(R.string.mobile_info, argsext); } public long getLogtime() { return logtime; } public int getVerbosityLevel() { if (mVerbosityLevel == -1) { // Hack: // For message not from OpenVPN, report the status level as log level return mLevel.getInt(); } return mVerbosityLevel; } public boolean verify() { if (mLevel == null) return false; if (mMessage == null && mRessourceId == 0) return false; return true; } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/LollipopDeviceStateListener.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.annotation.TargetApi; import android.net.ConnectivityManager; import android.net.LinkProperties; import android.net.Network; import android.net.NetworkCapabilities; import android.os.Build; /** * Created by arne on 26.11.14. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class LollipopDeviceStateListener extends ConnectivityManager.NetworkCallback { private String mLastConnectedStatus; private String mLastLinkProperties; private String mLastNetworkCapabilities; @Override public void onAvailable(Network network) { super.onAvailable(network); if (!network.toString().equals(mLastConnectedStatus)) { mLastConnectedStatus = network.toString(); VpnStatus.logDebug("Connected to " + mLastConnectedStatus); } } @Override public void onLinkPropertiesChanged(Network network, LinkProperties linkProperties) { super.onLinkPropertiesChanged(network, linkProperties); if (!linkProperties.toString().equals(mLastLinkProperties)) { mLastLinkProperties = linkProperties.toString(); VpnStatus.logDebug(String.format("Linkproperties of %s: %s", network, linkProperties)); } } @Override public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) { super.onCapabilitiesChanged(network, networkCapabilities); if (!networkCapabilities.toString().equals(mLastNetworkCapabilities)) { mLastNetworkCapabilities = networkCapabilities.toString(); VpnStatus.logDebug(String.format("Network capabilities of %s: %s", network, networkCapabilities)); } } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.os.Build; import java.security.InvalidKeyException; public class NativeUtils { public static native byte[] rsasign(byte[] input, int pkey) throws InvalidKeyException; public static native String[] getIfconfig() throws IllegalArgumentException; static native void jniclose(int fdint); public static native String getNativeAPI(); static { System.loadLibrary("opvpnutil"); if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN) System.loadLibrary("jbcrypto"); } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/NetworkSpace.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.os.Build; import android.support.annotation.NonNull; import android.text.TextUtils; import com.vasilkoff.easyvpnfree.BuildConfig; import junit.framework.Assert; import java.math.BigInteger; import java.net.Inet6Address; import java.util.Collection; import java.util.Locale; import java.util.PriorityQueue; import java.util.TreeSet; import java.util.Vector; public class NetworkSpace { static class ipAddress implements Comparable { private BigInteger netAddress; public int networkMask; private boolean included; private boolean isV4; private BigInteger firstAddress; private BigInteger lastAddress; /** * sorts the networks with following criteria: * 1. compares first 1 of the network * 2. smaller networks are returned as smaller */ @Override public int compareTo(@NonNull ipAddress another) { int comp = getFirstAddress().compareTo(another.getFirstAddress()); if (comp != 0) return comp; if (networkMask > another.networkMask) return -1; else if (another.networkMask == networkMask) return 0; else return 1; } /** * Warning ignores the included integer * * @param o the object to compare this instance with. */ @Override public boolean equals(Object o) { if (!(o instanceof ipAddress)) return super.equals(o); ipAddress on = (ipAddress) o; return (networkMask == on.networkMask) && on.getFirstAddress().equals(getFirstAddress()); } public ipAddress(CIDRIP ip, boolean include) { included = include; netAddress = BigInteger.valueOf(ip.getInt()); networkMask = ip.len; isV4 = true; } public ipAddress(Inet6Address address, int mask, boolean include) { networkMask = mask; included = include; int s = 128; netAddress = BigInteger.ZERO; for (byte b : address.getAddress()) { s -= 8; netAddress = netAddress.add(BigInteger.valueOf((b & 0xFF)).shiftLeft(s)); } } public BigInteger getLastAddress() { if (lastAddress == null) lastAddress = getMaskedAddress(true); return lastAddress; } public BigInteger getFirstAddress() { if (firstAddress == null) firstAddress = getMaskedAddress(false); return firstAddress; } private BigInteger getMaskedAddress(boolean one) { BigInteger numAddress = netAddress; int numBits; if (isV4) { numBits = 32 - networkMask; } else { numBits = 128 - networkMask; } for (int i = 0; i < numBits; i++) { if (one) numAddress = numAddress.setBit(i); else numAddress = numAddress.clearBit(i); } return numAddress; } @Override public String toString() { //String in = included ? "+" : "-"; if (isV4) return String.format(Locale.US, "%s/%d", getIPv4Address(), networkMask); else return String.format(Locale.US, "%s/%d", getIPv6Address(), networkMask); } ipAddress(BigInteger baseAddress, int mask, boolean included, boolean isV4) { this.netAddress = baseAddress; this.networkMask = mask; this.included = included; this.isV4 = isV4; } public ipAddress[] split() { ipAddress firstHalf = new ipAddress(getFirstAddress(), networkMask + 1, included, isV4); ipAddress secondHalf = new ipAddress(firstHalf.getLastAddress().add(BigInteger.ONE), networkMask + 1, included, isV4); if (BuildConfig.DEBUG) Assert.assertTrue(secondHalf.getLastAddress().equals(getLastAddress())); return new ipAddress[]{firstHalf, secondHalf}; } String getIPv4Address() { if (BuildConfig.DEBUG) { Assert.assertTrue(isV4); Assert.assertTrue(netAddress.longValue() <= 0xffffffffl); Assert.assertTrue(netAddress.longValue() >= 0); } long ip = netAddress.longValue(); return String.format(Locale.US, "%d.%d.%d.%d", (ip >> 24) % 256, (ip >> 16) % 256, (ip >> 8) % 256, ip % 256); } String getIPv6Address() { if (BuildConfig.DEBUG) Assert.assertTrue(!isV4); BigInteger r = netAddress; String ipv6str = null; boolean lastPart = true; while (r.compareTo(BigInteger.ZERO) == 1) { long part = r.mod(BigInteger.valueOf(0x10000)).longValue(); if (ipv6str != null || part != 0) { if (ipv6str == null && !lastPart) ipv6str = ":"; if (lastPart) ipv6str = String.format(Locale.US, "%x", part, ipv6str); else ipv6str = String.format(Locale.US, "%x:%s", part, ipv6str); } r = r.shiftRight(16); lastPart = false; } if (ipv6str == null) return "::"; return ipv6str; } public boolean containsNet(ipAddress network) { // this.first >= net.first && this.last <= net.last BigInteger ourFirst = getFirstAddress(); BigInteger ourLast = getLastAddress(); BigInteger netFirst = network.getFirstAddress(); BigInteger netLast = network.getLastAddress(); boolean a = ourFirst.compareTo(netFirst) != 1; boolean b = ourLast.compareTo(netLast) != -1; return a && b; } } TreeSet mIpAddresses = new TreeSet(); public Collection getNetworks(boolean included) { Vector ips = new Vector(); for (ipAddress ip : mIpAddresses) { if (ip.included == included) ips.add(ip); } return ips; } public void clear() { mIpAddresses.clear(); } void addIP(CIDRIP cidrIp, boolean include) { mIpAddresses.add(new ipAddress(cidrIp, include)); } public void addIPSplit(CIDRIP cidrIp, boolean include) { ipAddress newIP = new ipAddress(cidrIp, include); ipAddress[] splitIps = newIP.split(); for (ipAddress split : splitIps) mIpAddresses.add(split); } void addIPv6(Inet6Address address, int mask, boolean included) { mIpAddresses.add(new ipAddress(address, mask, included)); } TreeSet generateIPList() { PriorityQueue networks = new PriorityQueue(mIpAddresses); TreeSet ipsDone = new TreeSet(); ipAddress currentNet = networks.poll(); if (currentNet == null) return ipsDone; while (currentNet != null) { // Check if it and the next of it are compatible ipAddress nextNet = networks.poll(); if (BuildConfig.DEBUG) Assert.assertNotNull(currentNet); if (nextNet == null || currentNet.getLastAddress().compareTo(nextNet.getFirstAddress()) == -1) { // Everything good, no overlapping nothing to do ipsDone.add(currentNet); currentNet = nextNet; } else { // This network is smaller or equal to the next but has the same base address if (currentNet.getFirstAddress().equals(nextNet.getFirstAddress()) && currentNet.networkMask >= nextNet.networkMask) { if (currentNet.included == nextNet.included) { // Included in the next next and same type // Simply forget our current network currentNet = nextNet; } else { // our currentNet is included in next and types differ. Need to split the next network ipAddress[] newNets = nextNet.split(); // TODO: The contains method of the Priority is stupid linear search // First add the second half to keep the order in networks if (!networks.contains(newNets[1])) networks.add(newNets[1]); if (newNets[0].getLastAddress().equals(currentNet.getLastAddress())) { if (BuildConfig.DEBUG) Assert.assertEquals(newNets[0].networkMask, currentNet.networkMask); // Don't add the lower half that would conflict with currentNet } else { if (!networks.contains(newNets[0])) networks.add(newNets[0]); } // Keep currentNet as is } } else { if (BuildConfig.DEBUG) { Assert.assertTrue(currentNet.networkMask < nextNet.networkMask); Assert.assertTrue(nextNet.getFirstAddress().compareTo(currentNet.getFirstAddress()) == 1); Assert.assertTrue(currentNet.getLastAddress().compareTo(nextNet.getLastAddress()) != -1); } // This network is bigger than the next and last ip of current >= next //noinspection StatementWithEmptyBody if (currentNet.included == nextNet.included) { // Next network is in included in our network with the same type, // simply ignore the next and move on } else { // We need to split our network ipAddress[] newNets = currentNet.split(); if (newNets[1].networkMask == nextNet.networkMask) { if (BuildConfig.DEBUG) { Assert.assertTrue(newNets[1].getFirstAddress().equals(nextNet.getFirstAddress())); Assert.assertTrue(newNets[1].getLastAddress().equals(currentNet.getLastAddress())); // split second equal the next network, do not add it } networks.add(nextNet); } else { // Add the smaller network first networks.add(newNets[1]); networks.add(nextNet); } currentNet = newNets[0]; } } } } return ipsDone; } Collection getPositiveIPList() { TreeSet ipsSorted = generateIPList(); Vector ips = new Vector(); for (ipAddress ia : ipsSorted) { if (ia.included) ips.add(ia); } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { // Include postive routes from the original set under < 4.4 since these might overrule the local // network but only if no smaller negative route exists for (ipAddress origIp : mIpAddresses) { if (!origIp.included) continue; // The netspace exists if (ipsSorted.contains(origIp)) continue; boolean skipIp = false; // If there is any smaller net that is excluded we may not add the positive route back for (ipAddress calculatedIp : ipsSorted) { if (!calculatedIp.included && origIp.containsNet(calculatedIp)) { skipIp = true; break; } } if (skipIp) continue; // It is safe to include the IP ips.add(origIp); } } return ips; } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/OpenVPNManagement.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; public interface OpenVPNManagement { interface PausedStateCallback { boolean shouldBeRunning(); } enum pauseReason { noNetwork, userPause, screenOff, } int mBytecountInterval = 2; void reconnect(); void pause(pauseReason reason); void resume(); /** * @param replaceConnection True if the VPN is connected by a new connection. * @return true if there was a process that has been send a stop signal */ boolean stopVPN(boolean replaceConnection); /* * Rebind the interface */ void networkChange(boolean sameNetwork); void setPauseCallback(PausedStateCallback callback); } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.Manifest.permission; import android.annotation.TargetApi; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.UiModeManager; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.net.ConnectivityManager; import android.net.VpnService; import android.os.Binder; import android.os.Build; import android.os.Handler; import android.os.Handler.Callback; import android.os.IBinder; import android.os.Message; import android.os.ParcelFileDescriptor; import android.preference.PreferenceManager; import android.system.OsConstants; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; import com.vasilkoff.easyvpnfree.BuildConfig; import com.vasilkoff.easyvpnfree.R; import com.vasilkoff.easyvpnfree.activity.ServerActivity; import com.vasilkoff.easyvpnfree.util.TotalTraffic; import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.Inet6Address; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.Collection; import java.util.Locale; import java.util.Vector; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.VpnStatus.ByteCountListener; import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; import de.blinkt.openvpn.core.VpnStatus.StateListener; import static de.blinkt.openvpn.core.NetworkSpace.ipAddress; import static de.blinkt.openvpn.core.VpnStatus.ConnectionStatus.LEVEL_CONNECTED; import static de.blinkt.openvpn.core.VpnStatus.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT; public class OpenVPNService extends VpnService implements StateListener, Callback, ByteCountListener { public static final String START_SERVICE = "de.blinkt.openvpn.START_SERVICE"; public static final String START_SERVICE_STICKY = "de.blinkt.openvpn.START_SERVICE_STICKY"; public static final String ALWAYS_SHOW_NOTIFICATION = "de.blinkt.openvpn.NOTIFICATION_ALWAYS_VISIBLE"; public static final String DISCONNECT_VPN = "de.blinkt.openvpn.DISCONNECT_VPN"; private static final String PAUSE_VPN = "de.blinkt.openvpn.PAUSE_VPN"; private static final String RESUME_VPN = "de.blinkt.openvpn.RESUME_VPN"; private static final int OPENVPN_STATUS = 1; private static boolean mNotificationAlwaysVisible = false; private final Vector mDnslist = new Vector<>(); private final NetworkSpace mRoutes = new NetworkSpace(); private final NetworkSpace mRoutesv6 = new NetworkSpace(); private final IBinder mBinder = new LocalBinder(); private Thread mProcessThread = null; private VpnProfile mProfile; private String mDomain = null; private CIDRIP mLocalIP = null; private int mMtu; private String mLocalIPv6 = null; private DeviceStateReceiver mDeviceStateReceiver; private boolean mDisplayBytecount = false; private boolean mStarting = false; private long mConnecttime; private boolean mOvpn3 = false; private OpenVPNManagement mManagement; private String mLastTunCfg; private String mRemoteGW; private final Object mProcessLock = new Object(); private Handler guiHandler; private Toast mlastToast; private Runnable mOpenVPNThread; // From: http://stackoverflow.com/questions/3758606/how-to-convert-byte-size-into-human-readable-format-in-java public static String humanReadableByteCount(long bytes, boolean mbit) { if (mbit) bytes = bytes * 8; int unit = mbit ? 1000 : 1024; if (bytes < unit) return bytes + (mbit ? " bit" : " B"); int exp = (int) (Math.log(bytes) / Math.log(unit)); String pre = (mbit ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (mbit ? "" : ""); if (mbit) return String.format(Locale.getDefault(), "%.1f %sbit", bytes / Math.pow(unit, exp), pre); else return String.format(Locale.getDefault(), "%.1f %sB", bytes / Math.pow(unit, exp), pre); } @Override public IBinder onBind(Intent intent) { String action = intent.getAction(); if (action != null && action.equals(START_SERVICE)) return mBinder; else return super.onBind(intent); } @Override public void onRevoke() { VpnStatus.logError(R.string.permission_revoked); mManagement.stopVPN(false); endVpnService(); } // Similar to revoke but do not try to stop process public void processDied() { endVpnService(); } private void endVpnService() { synchronized (mProcessLock) { mProcessThread = null; } VpnStatus.removeByteCountListener(this); unregisterDeviceStateReceiver(); ProfileManager.setConntectedVpnProfileDisconnected(this); mOpenVPNThread = null; if (!mStarting) { stopForeground(!mNotificationAlwaysVisible); if (!mNotificationAlwaysVisible) { stopSelf(); VpnStatus.removeStateListener(this); } } } private void showNotification(final String msg, String tickerText, boolean lowpriority, long when, ConnectionStatus status) { String ns = Context.NOTIFICATION_SERVICE; NotificationManager mNotificationManager = (NotificationManager) getSystemService(ns); //int icon = getIconByConnectionStatus(status); int icon = R.drawable.ic_app_notif; android.app.Notification.Builder nbuilder = new Notification.Builder(this); if (mProfile != null) nbuilder.setContentTitle(getString(R.string.notification_title, mProfile.mName)); else nbuilder.setContentTitle(getString(R.string.notifcation_title_notconnect)); nbuilder.setContentText(msg); nbuilder.setOnlyAlertOnce(true); nbuilder.setOngoing(true); nbuilder.setContentIntent(getLogPendingIntent()); nbuilder.setSmallIcon(icon); if (when != 0) nbuilder.setWhen(when); // Try to set the priority available since API 16 (Jellybean) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) jbNotificationExtras(lowpriority, nbuilder); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) lpNotificationExtras(nbuilder); if (tickerText != null && !tickerText.equals("")) nbuilder.setTicker(tickerText); @SuppressWarnings("deprecation") Notification notification = nbuilder.getNotification(); mNotificationManager.notify(OPENVPN_STATUS, notification); startForeground(OPENVPN_STATUS, notification); // Check if running on a TV if (runningOnAndroidTV() && !lowpriority) guiHandler.post(new Runnable() { @Override public void run() { if (mlastToast != null) mlastToast.cancel(); String toastText = String.format(Locale.getDefault(), "%s - %s", mProfile.mName, msg); mlastToast = Toast.makeText(getBaseContext(), toastText, Toast.LENGTH_SHORT); mlastToast.show(); } }); } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void lpNotificationExtras(Notification.Builder nbuilder) { nbuilder.setCategory(Notification.CATEGORY_SERVICE); nbuilder.setLocalOnly(true); } private boolean runningOnAndroidTV() { UiModeManager uiModeManager = (UiModeManager) getSystemService(UI_MODE_SERVICE); return uiModeManager.getCurrentModeType() == Configuration.UI_MODE_TYPE_TELEVISION; } /*private int getIconByConnectionStatus(ConnectionStatus level) { switch (level) { case LEVEL_CONNECTED: return R.drawable.ic_stat_vpn; case LEVEL_AUTH_FAILED: case LEVEL_NONETWORK: case LEVEL_NOTCONNECTED: return R.drawable.ic_stat_vpn_offline; case LEVEL_CONNECTING_NO_SERVER_REPLY_YET: case LEVEL_WAITING_FOR_USER_INPUT: return R.drawable.ic_stat_vpn_outline; case LEVEL_CONNECTING_SERVER_REPLIED: return R.drawable.ic_stat_vpn_empty_halo; case LEVEL_VPNPAUSED: return android.R.drawable.ic_media_pause; case UNKNOWN_LEVEL: default: return R.drawable.ic_stat_vpn; } }*/ @TargetApi(Build.VERSION_CODES.JELLY_BEAN) private void jbNotificationExtras(boolean lowpriority, android.app.Notification.Builder nbuilder) { try { if (lowpriority) { Method setpriority = nbuilder.getClass().getMethod("setPriority", int.class); // PRIORITY_MIN == -2 setpriority.invoke(nbuilder, -2); Method setUsesChronometer = nbuilder.getClass().getMethod("setUsesChronometer", boolean.class); setUsesChronometer.invoke(nbuilder, true); } Intent disconnectVPN = new Intent(this, OpenVPNService.class); disconnectVPN.setAction(DISCONNECT_VPN); PendingIntent disconnectPendingIntent = PendingIntent.getService( this, 0, disconnectVPN, 0); nbuilder.addAction(R.drawable.ic_menu_close_clear_cancel, getString(R.string.cancel_connection), disconnectPendingIntent); Intent pauseVPN = new Intent(this, OpenVPNService.class); if (mDeviceStateReceiver == null || !mDeviceStateReceiver.isUserPaused()) { pauseVPN.setAction(PAUSE_VPN); PendingIntent pauseVPNPending = PendingIntent.getService(this, 0, pauseVPN, 0); nbuilder.addAction(R.drawable.ic_menu_pause, getString(R.string.pauseVPN), pauseVPNPending); } else { pauseVPN.setAction(RESUME_VPN); PendingIntent resumeVPNPending = PendingIntent.getService(this, 0, pauseVPN, 0); nbuilder.addAction(R.drawable.ic_menu_play, getString(R.string.resumevpn), resumeVPNPending); } //ignore exception } catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException | IllegalAccessException e) { VpnStatus.logException(e); } } PendingIntent getLogPendingIntent() { // Let the configure Button show the Log Intent intent = new Intent(getBaseContext(), ServerActivity.class); //intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); PendingIntent startLW = PendingIntent.getActivity(this, 0, intent, 0); return startLW; } synchronized void registerDeviceStateReceiver(OpenVPNManagement magnagement) { // Registers BroadcastReceiver to track network connection changes. IntentFilter filter = new IntentFilter(); filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); mDeviceStateReceiver = new DeviceStateReceiver(magnagement); registerReceiver(mDeviceStateReceiver, filter); VpnStatus.addByteCountListener(mDeviceStateReceiver); /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) addLollipopCMListener(); */ } synchronized void unregisterDeviceStateReceiver() { if (mDeviceStateReceiver != null) try { VpnStatus.removeByteCountListener(mDeviceStateReceiver); this.unregisterReceiver(mDeviceStateReceiver); } catch (IllegalArgumentException iae) { // I don't know why this happens: // java.lang.IllegalArgumentException: Receiver not registered: de.blinkt.openvpn.NetworkSateReceiver@41a61a10 // Ignore for now ... iae.printStackTrace(); } mDeviceStateReceiver = null; /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) removeLollipopCMListener();*/ } public void userPause(boolean shouldBePaused) { if (mDeviceStateReceiver != null) mDeviceStateReceiver.userPause(shouldBePaused); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null && intent.getBooleanExtra(ALWAYS_SHOW_NOTIFICATION, false)) mNotificationAlwaysVisible = true; VpnStatus.addStateListener(this); VpnStatus.addByteCountListener(this); guiHandler = new Handler(getMainLooper()); if (intent != null && DISCONNECT_VPN.equals(intent.getAction())) { if (mManagement != null) mManagement.stopVPN(false); return START_NOT_STICKY; } if (intent != null && PAUSE_VPN.equals(intent.getAction())) { if (mDeviceStateReceiver != null) mDeviceStateReceiver.userPause(true); return START_NOT_STICKY; } if (intent != null && RESUME_VPN.equals(intent.getAction())) { if (mDeviceStateReceiver != null) mDeviceStateReceiver.userPause(false); return START_NOT_STICKY; } if (intent != null && START_SERVICE.equals(intent.getAction())) return START_NOT_STICKY; if (intent != null && START_SERVICE_STICKY.equals(intent.getAction())) { return START_REDELIVER_INTENT; } if (intent != null && intent.hasExtra(getPackageName() + ".profileUUID")) { String profileUUID = intent.getStringExtra(getPackageName() + ".profileUUID"); mProfile = ProfileManager.get(this, profileUUID); } else { /* The intent is null when we are set as always-on or the service has been restarted. */ mProfile = ProfileManager.getLastConnectedProfile(this); VpnStatus.logInfo(R.string.service_restarted); /* Got no profile, just stop */ if (mProfile == null) { Log.d("OpenVPN", "Got no last connected profile on null intent. Assuming always on."); mProfile = ProfileManager.getAlwaysOnVPN(this); if (mProfile==null) { stopSelf(startId); return START_NOT_STICKY; } } /* Do the asynchronous keychain certificate stuff */ mProfile.checkForRestart(this); /* Recreate the intent */ intent = mProfile.getStartServiceIntent(this); } /* start the OpenVPN process itself in a background thread */ new Thread(new Runnable() { @Override public void run() { startOpenVPN(); } }).start(); ProfileManager.setConnectedVpnProfile(this, mProfile); /* TODO: At the moment we have no way to handle asynchronous PW input * Fixing will also allow to handle challenge/response authentication */ if (mProfile.needUserPWInput(true) != 0) return START_NOT_STICKY; return START_STICKY; } private void startOpenVPN() { VpnStatus.logInfo(R.string.building_configration); VpnStatus.updateStateString("VPN_GENERATE_CONFIG", "", R.string.building_configration, VpnStatus.ConnectionStatus.LEVEL_START); try { mProfile.writeConfigFile(this); } catch (IOException e) { VpnStatus.logException("Error writing config file", e); endVpnService(); return; } // Extract information from the intent. String prefix = getPackageName(); String nativeLibraryDirectory = getApplicationInfo().nativeLibraryDir; // Also writes OpenVPN binary String[] argv = VPNLaunchHelper.buildOpenvpnArgv(this); // Set a flag that we are starting a new VPN mStarting = true; // Stop the previous session by interrupting the thread. stopOldOpenVPNProcess(); // An old running VPN should now be exited mStarting = false; // Start a new session by creating a new thread. SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); mOvpn3 = prefs.getBoolean("ovpn3", false); if (!"ovpn3".equals(BuildConfig.FLAVOR)) mOvpn3 = false; // Open the Management Interface if (!mOvpn3) { // start a Thread that handles incoming messages of the managment socket OpenVpnManagementThread ovpnManagementThread = new OpenVpnManagementThread(mProfile, this); if (ovpnManagementThread.openManagementInterface(this)) { Thread mSocketManagerThread = new Thread(ovpnManagementThread, "OpenVPNManagementThread"); mSocketManagerThread.start(); mManagement = ovpnManagementThread; VpnStatus.logInfo("started Socket Thread"); } else { endVpnService(); return; } } Runnable processThread; if (mOvpn3) { OpenVPNManagement mOpenVPN3 = instantiateOpenVPN3Core(); processThread = (Runnable) mOpenVPN3; mManagement = mOpenVPN3; } else { processThread = new OpenVPNThread(this, argv, nativeLibraryDirectory); mOpenVPNThread = processThread; } synchronized (mProcessLock) { mProcessThread = new Thread(processThread, "OpenVPNProcessThread"); mProcessThread.start(); } new Handler(getMainLooper()).post(new Runnable() { @Override public void run() { if (mDeviceStateReceiver != null) unregisterDeviceStateReceiver(); registerDeviceStateReceiver(mManagement); } } ); } private void stopOldOpenVPNProcess() { if (mManagement != null) { if (mOpenVPNThread!=null) ((OpenVPNThread) mOpenVPNThread).setReplaceConnection(); if (mManagement.stopVPN(true)) { // an old was asked to exit, wait 1s try { Thread.sleep(1000); } catch (InterruptedException e) { //ignore } } } synchronized (mProcessLock) { if (mProcessThread != null) { mProcessThread.interrupt(); try { Thread.sleep(1000); } catch (InterruptedException e) { //ignore } } } } private OpenVPNManagement instantiateOpenVPN3Core() { try { Class cl = Class.forName("de.blinkt.openvpn.core.OpenVPNThreadv3"); return (OpenVPNManagement) cl.getConstructor(OpenVPNService.class, VpnProfile.class).newInstance(this, mProfile); } catch (IllegalArgumentException | InstantiationException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException | IllegalAccessException e) { e.printStackTrace(); } return null; } @Override public void onDestroy() { synchronized (mProcessLock) { if (mProcessThread != null) { mManagement.stopVPN(true); } } if (mDeviceStateReceiver != null) { this.unregisterReceiver(mDeviceStateReceiver); } // Just in case unregister for state VpnStatus.removeStateListener(this); VpnStatus.flushLog(); } private String getTunConfigString() { // The format of the string is not important, only that // two identical configurations produce the same result String cfg = "TUNCFG UNQIUE STRING ips:"; if (mLocalIP != null) cfg += mLocalIP.toString(); if (mLocalIPv6 != null) cfg += mLocalIPv6; cfg += "routes: " + TextUtils.join("|", mRoutes.getNetworks(true)) + TextUtils.join("|", mRoutesv6.getNetworks(true)); cfg += "excl. routes:" + TextUtils.join("|", mRoutes.getNetworks(false)) + TextUtils.join("|", mRoutesv6.getNetworks(false)); cfg += "dns: " + TextUtils.join("|", mDnslist); cfg += "domain: " + mDomain; cfg += "mtu: " + mMtu; return cfg; } public ParcelFileDescriptor openTun() { //Debug.startMethodTracing(getExternalFilesDir(null).toString() + "/opentun.trace", 40* 1024 * 1024); Builder builder = new Builder(); VpnStatus.logInfo(R.string.last_openvpn_tun_config); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mProfile.mAllowLocalLAN) { allowAllAFFamilies(builder); } if (mLocalIP == null && mLocalIPv6 == null) { VpnStatus.logError(getString(R.string.opentun_no_ipaddr)); return null; } if (mLocalIP != null) { addLocalNetworksToRoutes(); try { builder.addAddress(mLocalIP.mIp, mLocalIP.len); } catch (IllegalArgumentException iae) { VpnStatus.logError(R.string.dns_add_error, mLocalIP, iae.getLocalizedMessage()); return null; } } if (mLocalIPv6 != null) { String[] ipv6parts = mLocalIPv6.split("/"); try { builder.addAddress(ipv6parts[0], Integer.parseInt(ipv6parts[1])); } catch (IllegalArgumentException iae) { VpnStatus.logError(R.string.ip_add_error, mLocalIPv6, iae.getLocalizedMessage()); return null; } } for (String dns : mDnslist) { try { builder.addDnsServer(dns); } catch (IllegalArgumentException iae) { VpnStatus.logError(R.string.dns_add_error, dns, iae.getLocalizedMessage()); } } String release = Build.VERSION.RELEASE; if ((Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT && !release.startsWith("4.4.3") && !release.startsWith("4.4.4") && !release.startsWith("4.4.5") && !release.startsWith("4.4.6")) && mMtu < 1280) { VpnStatus.logInfo(String.format(Locale.US, "Forcing MTU to 1280 instead of %d to workaround Android Bug #70916", mMtu)); builder.setMtu(1280); } else { builder.setMtu(mMtu); } Collection positiveIPv4Routes = mRoutes.getPositiveIPList(); Collection positiveIPv6Routes = mRoutesv6.getPositiveIPList(); if ("samsung".equals(Build.BRAND) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && mDnslist.size() >= 1) { // Check if the first DNS Server is in the VPN range try { ipAddress dnsServer = new ipAddress(new CIDRIP(mDnslist.get(0), 32), true); boolean dnsIncluded = false; for (ipAddress net : positiveIPv4Routes) { if (net.containsNet(dnsServer)) { dnsIncluded = true; } } if (!dnsIncluded) { String samsungwarning = String.format("Warning Samsung Android 5.0+ devices ignore DNS servers outside the VPN range. To enable DNS resolution a route to your DNS Server (%s) has been added.", mDnslist.get(0)); VpnStatus.logWarning(samsungwarning); positiveIPv4Routes.add(dnsServer); } } catch (Exception e) { VpnStatus.logError("Error parsing DNS Server IP: " + mDnslist.get(0)); } } ipAddress multicastRange = new ipAddress(new CIDRIP("224.0.0.0", 3), true); for (NetworkSpace.ipAddress route : positiveIPv4Routes) { try { if (multicastRange.containsNet(route)) VpnStatus.logDebug(R.string.ignore_multicast_route, route.toString()); else builder.addRoute(route.getIPv4Address(), route.networkMask); } catch (IllegalArgumentException ia) { VpnStatus.logError(getString(R.string.route_rejected) + route + " " + ia.getLocalizedMessage()); } } for (NetworkSpace.ipAddress route6 : positiveIPv6Routes) { try { builder.addRoute(route6.getIPv6Address(), route6.networkMask); } catch (IllegalArgumentException ia) { VpnStatus.logError(getString(R.string.route_rejected) + route6 + " " + ia.getLocalizedMessage()); } } if (mDomain != null) builder.addSearchDomain(mDomain); VpnStatus.logInfo(R.string.local_ip_info, mLocalIP.mIp, mLocalIP.len, mLocalIPv6, mMtu); VpnStatus.logInfo(R.string.dns_server_info, TextUtils.join(", ", mDnslist), mDomain); VpnStatus.logInfo(R.string.routes_info_incl, TextUtils.join(", ", mRoutes.getNetworks(true)), TextUtils.join(", ", mRoutesv6.getNetworks(true))); VpnStatus.logInfo(R.string.routes_info_excl, TextUtils.join(", ", mRoutes.getNetworks(false)), TextUtils.join(", ", mRoutesv6.getNetworks(false))); VpnStatus.logDebug(R.string.routes_debug, TextUtils.join(", ", positiveIPv4Routes), TextUtils.join(", ", positiveIPv6Routes)); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { setAllowedVpnPackages(builder); } String session = mProfile.mName; if (mLocalIP != null && mLocalIPv6 != null) session = getString(R.string.session_ipv6string, session, mLocalIP, mLocalIPv6); else if (mLocalIP != null) session = getString(R.string.session_ipv4string, session, mLocalIP); builder.setSession(session); // No DNS Server, log a warning if (mDnslist.size() == 0) VpnStatus.logInfo(R.string.warn_no_dns); mLastTunCfg = getTunConfigString(); // Reset information mDnslist.clear(); mRoutes.clear(); mRoutesv6.clear(); mLocalIP = null; mLocalIPv6 = null; mDomain = null; builder.setConfigureIntent(getLogPendingIntent()); try { //Debug.stopMethodTracing(); ParcelFileDescriptor tun = builder.establish(); if (tun == null) throw new NullPointerException("Android establish() method returned null (Really broken network configuration?)"); return tun; } catch (Exception e) { VpnStatus.logError(R.string.tun_open_error); VpnStatus.logError(getString(R.string.error) + e.getLocalizedMessage()); if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN_MR1) { VpnStatus.logError(R.string.tun_error_helpful); } return null; } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void allowAllAFFamilies(Builder builder) { builder.allowFamily(OsConstants.AF_INET); builder.allowFamily(OsConstants.AF_INET6); } private void addLocalNetworksToRoutes() { // Add local network interfaces String[] localRoutes = NativeUtils.getIfconfig(); // The format of mLocalRoutes is kind of broken because I don't really like JNI for (int i = 0; i < localRoutes.length; i += 3) { String intf = localRoutes[i]; String ipAddr = localRoutes[i + 1]; String netMask = localRoutes[i + 2]; if (intf == null || intf.equals("lo") || intf.startsWith("tun") || intf.startsWith("rmnet")) continue; if (ipAddr == null || netMask == null) { VpnStatus.logError("Local routes are broken?! (Report to author) " + TextUtils.join("|", localRoutes)); continue; } if (ipAddr.equals(mLocalIP.mIp)) continue; if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT && !mProfile.mAllowLocalLAN) { mRoutes.addIPSplit(new CIDRIP(ipAddr, netMask), true); } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && mProfile.mAllowLocalLAN) mRoutes.addIP(new CIDRIP(ipAddr, netMask), false); } } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private void setAllowedVpnPackages(Builder builder) { boolean atLeastOneAllowedApp = false; for (String pkg : mProfile.mAllowedAppsVpn) { try { if (mProfile.mAllowedAppsVpnAreDisallowed) { builder.addDisallowedApplication(pkg); } else { builder.addAllowedApplication(pkg); atLeastOneAllowedApp = true; } } catch (PackageManager.NameNotFoundException e) { mProfile.mAllowedAppsVpn.remove(pkg); VpnStatus.logInfo(R.string.app_no_longer_exists, pkg); } } if (!mProfile.mAllowedAppsVpnAreDisallowed && !atLeastOneAllowedApp) { VpnStatus.logDebug(R.string.no_allowed_app, getPackageName()); try { builder.addAllowedApplication(getPackageName()); } catch (PackageManager.NameNotFoundException e) { VpnStatus.logError("This should not happen: " + e.getLocalizedMessage()); } } if (mProfile.mAllowedAppsVpnAreDisallowed) { VpnStatus.logDebug(R.string.disallowed_vpn_apps_info, TextUtils.join(", ", mProfile.mAllowedAppsVpn)); } else { VpnStatus.logDebug(R.string.allowed_vpn_apps_info, TextUtils.join(", ", mProfile.mAllowedAppsVpn)); } } public void addDNS(String dns) { mDnslist.add(dns); } public void setDomain(String domain) { if (mDomain == null) { mDomain = domain; } } /** * Route that is always included, used by the v3 core */ public void addRoute(CIDRIP route) { mRoutes.addIP(route, true); } public void addRoute(String dest, String mask, String gateway, String device) { CIDRIP route = new CIDRIP(dest, mask); boolean include = isAndroidTunDevice(device); NetworkSpace.ipAddress gatewayIP = new NetworkSpace.ipAddress(new CIDRIP(gateway, 32), false); if (mLocalIP == null) { VpnStatus.logError("Local IP address unset and received. Neither pushed server config nor local config specifies an IP addresses. Opening tun device is most likely going to fail."); return; } NetworkSpace.ipAddress localNet = new NetworkSpace.ipAddress(mLocalIP, true); if (localNet.containsNet(gatewayIP)) include = true; if (gateway != null && (gateway.equals("255.255.255.255") || gateway.equals(mRemoteGW))) include = true; if (route.len == 32 && !mask.equals("255.255.255.255")) { VpnStatus.logWarning(R.string.route_not_cidr, dest, mask); } if (route.normalise()) VpnStatus.logWarning(R.string.route_not_netip, dest, route.len, route.mIp); mRoutes.addIP(route, include); } public void addRoutev6(String network, String device) { String[] v6parts = network.split("/"); boolean included = isAndroidTunDevice(device); // Tun is opened after ROUTE6, no device name may be present try { Inet6Address ip = (Inet6Address) InetAddress.getAllByName(v6parts[0])[0]; int mask = Integer.parseInt(v6parts[1]); mRoutesv6.addIPv6(ip, mask, included); } catch (UnknownHostException e) { VpnStatus.logException(e); } } private boolean isAndroidTunDevice(String device) { return device != null && (device.startsWith("tun") || "(null)".equals(device) || "vpnservice-tun".equals(device)); } public void setMtu(int mtu) { mMtu = mtu; } public void setLocalIP(CIDRIP cdrip) { mLocalIP = cdrip; } public void setLocalIP(String local, String netmask, int mtu, String mode) { mLocalIP = new CIDRIP(local, netmask); mMtu = mtu; mRemoteGW = null; long netMaskAsInt = CIDRIP.getInt(netmask); if (mLocalIP.len == 32 && !netmask.equals("255.255.255.255")) { // get the netmask as IP int masklen; long mask; if ("net30".equals(mode)) { masklen = 30; mask = 0xfffffffc; } else { masklen = 31; mask = 0xfffffffe; } // Netmask is Ip address +/-1, assume net30/p2p with small net if ((netMaskAsInt & mask) == (mLocalIP.getInt() & mask)) { mLocalIP.len = masklen; } else { mLocalIP.len = 32; if (!"p2p".equals(mode)) VpnStatus.logWarning(R.string.ip_not_cidr, local, netmask, mode); } } if (("p2p".equals(mode) && mLocalIP.len < 32) || ("net30".equals(mode) && mLocalIP.len < 30)) { VpnStatus.logWarning(R.string.ip_looks_like_subnet, local, netmask, mode); } /* Workaround for Lollipop, it does not route traffic to the VPNs own network mask */ if (mLocalIP.len <= 31 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { CIDRIP interfaceRoute = new CIDRIP(mLocalIP.mIp, mLocalIP.len); interfaceRoute.normalise(); addRoute(interfaceRoute); } // Configurations are sometimes really broken... mRemoteGW = netmask; } public void setLocalIPv6(String ipv6addr) { mLocalIPv6 = ipv6addr; } @Override public void updateState(String state, String logmessage, int resid, ConnectionStatus level) { // If the process is not running, ignore any state, // Notification should be invisible in this state doSendBroadcast(state, level); if (mProcessThread == null && !mNotificationAlwaysVisible) return; boolean lowpriority = false; // Display byte count only after being connected { if (level == LEVEL_WAITING_FOR_USER_INPUT) { // The user is presented a dialog of some kind, no need to inform the user // with a notifcation return; } else if (level == LEVEL_CONNECTED) { mDisplayBytecount = true; mConnecttime = System.currentTimeMillis(); if (!runningOnAndroidTV()) lowpriority = true; } else { mDisplayBytecount = false; } // Other notifications are shown, // This also mean we are no longer connected, ignore bytecount messages until next // CONNECTED // Does not work :( String msg; if (resid == R.string.state_waitconnectretry) { msg = VpnStatus.getLastCleanLogMessage(this); } else { msg = getString(resid); } showNotification(VpnStatus.getLastCleanLogMessage(this), msg, lowpriority, 0, level); } } private void doSendBroadcast(String state, ConnectionStatus level) { Intent vpnstatus = new Intent(); vpnstatus.setAction("de.blinkt.openvpn.VPN_STATUS"); vpnstatus.putExtra("status", level.toString()); vpnstatus.putExtra("detailstatus", state); sendBroadcast(vpnstatus, permission.ACCESS_NETWORK_STATE); } @Override public void updateByteCount(long in, long out, long diffIn, long diffOut) { TotalTraffic.calcTraffic(this, in, out, diffIn, diffOut); if (mDisplayBytecount) { String netstat = String.format(getString(R.string.statusline_bytecount), humanReadableByteCount(in, false), humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true), humanReadableByteCount(out, false), humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true)); boolean lowpriority = !mNotificationAlwaysVisible; showNotification(netstat, null, lowpriority, mConnecttime, LEVEL_CONNECTED); } } @Override public boolean handleMessage(Message msg) { Runnable r = msg.getCallback(); if (r != null) { r.run(); return true; } else { return false; } } public OpenVPNManagement getManagement() { return mManagement; } public String getTunReopenStatus() { String currentConfiguration = getTunConfigString(); if (currentConfiguration.equals(mLastTunCfg)) { return "NOACTION"; } else { String release = Build.VERSION.RELEASE; if (Build.VERSION.SDK_INT == Build.VERSION_CODES.KITKAT && !release.startsWith("4.4.3") && !release.startsWith("4.4.4") && !release.startsWith("4.4.5") && !release.startsWith("4.4.6")) // There will be probably no 4.4.4 or 4.4.5 version, so don't waste effort to do parsing here return "OPEN_AFTER_CLOSE"; else return "OPEN_BEFORE_CLOSE"; } } public class LocalBinder extends Binder { public OpenVPNService getService() { // Return this instance of LocalService so clients can call public methods return OpenVPNService.this; } } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/OpenVPNThread.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.annotation.SuppressLint; import android.util.Log; import com.vasilkoff.easyvpnfree.R; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.text.SimpleDateFormat; import java.util.Collections; import java.util.Date; import java.util.LinkedList; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; public class OpenVPNThread implements Runnable { private static final String DUMP_PATH_STRING = "Dump path: "; @SuppressLint("SdCardPath") private static final String BROKEN_PIE_SUPPORT = "/data/data/de.blinkt.openvpn/cache/pievpn"; private final static String BROKEN_PIE_SUPPORT2 = "syntax error"; private static final String TAG = "OpenVPN"; public static final int M_FATAL = (1 << 4); public static final int M_NONFATAL = (1 << 5); public static final int M_WARN = (1 << 6); public static final int M_DEBUG = (1 << 7); private String[] mArgv; private Process mProcess; private String mNativeDir; private OpenVPNService mService; private String mDumpPath; private boolean mBrokenPie = false; private boolean mNoProcessExitStatus = false; public OpenVPNThread(OpenVPNService service, String[] argv, String nativelibdir) { mArgv = argv; mNativeDir = nativelibdir; mService = service; } public void stopProcess() { mProcess.destroy(); } void setReplaceConnection() { mNoProcessExitStatus=true; } @Override public void run() { try { Log.i(TAG, "Starting openvpn"); startOpenVPNThreadArgs(mArgv); Log.i(TAG, "OpenVPN process exited"); } catch (Exception e) { VpnStatus.logException("Starting OpenVPN Thread", e); Log.e(TAG, "OpenVPNThread Got " + e.toString()); } finally { int exitvalue = 0; try { if (mProcess != null) exitvalue = mProcess.waitFor(); } catch (IllegalThreadStateException ite) { VpnStatus.logError("Illegal Thread state: " + ite.getLocalizedMessage()); } catch (InterruptedException ie) { VpnStatus.logError("InterruptedException: " + ie.getLocalizedMessage()); } if (exitvalue != 0) { VpnStatus.logError("Process exited with exit value " + exitvalue); if (mBrokenPie) { /* This will probably fail since the NoPIE binary is probably not written */ String[] noPieArgv = VPNLaunchHelper.replacePieWithNoPie(mArgv); // We are already noPIE, nothing to gain if (!noPieArgv.equals(mArgv)) { mArgv = noPieArgv; VpnStatus.logInfo("PIE Version could not be executed. Trying no PIE version"); run(); return; } } } if (!mNoProcessExitStatus) VpnStatus.updateStateString("NOPROCESS", "No process running.", R.string.state_noprocess, ConnectionStatus.LEVEL_NOTCONNECTED); if (mDumpPath != null) { try { BufferedWriter logout = new BufferedWriter(new FileWriter(mDumpPath + ".log")); SimpleDateFormat timeformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.GERMAN); for (LogItem li : VpnStatus.getlogbuffer()) { String time = timeformat.format(new Date(li.getLogtime())); logout.write(time + " " + li.getString(mService) + "\n"); } logout.close(); VpnStatus.logError(R.string.minidump_generated); } catch (IOException e) { VpnStatus.logError("Writing minidump log: " + e.getLocalizedMessage()); } } mService.processDied(); Log.i(TAG, "Exiting"); } } private void startOpenVPNThreadArgs(String[] argv) { LinkedList argvlist = new LinkedList(); Collections.addAll(argvlist, argv); ProcessBuilder pb = new ProcessBuilder(argvlist); // Hack O rama String lbpath = genLibraryPath(argv, pb); pb.environment().put("LD_LIBRARY_PATH", lbpath); pb.redirectErrorStream(true); try { mProcess = pb.start(); // Close the output, since we don't need it mProcess.getOutputStream().close(); InputStream in = mProcess.getInputStream(); BufferedReader br = new BufferedReader(new InputStreamReader(in)); while (true) { String logline = br.readLine(); if (logline == null) return; if (logline.startsWith(DUMP_PATH_STRING)) mDumpPath = logline.substring(DUMP_PATH_STRING.length()); if (logline.startsWith(BROKEN_PIE_SUPPORT) || logline.contains(BROKEN_PIE_SUPPORT2)) mBrokenPie = true; // 1380308330.240114 18000002 Send to HTTP proxy: 'X-Online-Host: bla.blabla.com' Pattern p = Pattern.compile("(\\d+).(\\d+) ([0-9a-f])+ (.*)"); Matcher m = p.matcher(logline); if (m.matches()) { int flags = Integer.parseInt(m.group(3), 16); String msg = m.group(4); int logLevel = flags & 0x0F; VpnStatus.LogLevel logStatus = VpnStatus.LogLevel.INFO; if ((flags & M_FATAL) != 0) logStatus = VpnStatus.LogLevel.ERROR; else if ((flags & M_NONFATAL) != 0) logStatus = VpnStatus.LogLevel.WARNING; else if ((flags & M_WARN) != 0) logStatus = VpnStatus.LogLevel.WARNING; else if ((flags & M_DEBUG) != 0) logStatus = VpnStatus.LogLevel.VERBOSE; if (msg.startsWith("MANAGEMENT: CMD")) logLevel = Math.max(4, logLevel); VpnStatus.logMessageOpenVPN(logStatus, logLevel, msg); } else { VpnStatus.logInfo("P:" + logline); } } } catch (IOException e) { VpnStatus.logException("Error reading from output of OpenVPN process", e); stopProcess(); } } private String genLibraryPath(String[] argv, ProcessBuilder pb) { // Hack until I find a good way to get the real library path String applibpath = argv[0].replaceFirst("/cache/.*$", "/lib"); String lbpath = pb.environment().get("LD_LIBRARY_PATH"); if (lbpath == null) lbpath = applibpath; else lbpath = applibpath + ":" + lbpath; if (!applibpath.equals(mNativeDir)) { lbpath = mNativeDir + ":" + lbpath; } return lbpath; } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.content.Context; import android.net.LocalServerSocket; import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.os.Handler; import android.os.ParcelFileDescriptor; import android.support.annotation.NonNull; import android.util.Log; import com.vasilkoff.easyvpnfree.BuildConfig; import com.vasilkoff.easyvpnfree.R; import junit.framework.Assert; import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.Arrays; import java.util.Collections; import java.util.LinkedList; import java.util.Locale; import java.util.Vector; import de.blinkt.openvpn.VpnProfile; import de.blinkt.openvpn.core.VpnStatus.ConnectionStatus; public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { private static final String TAG = "openvpn"; private final Handler mResumeHandler; private LocalSocket mSocket; private VpnProfile mProfile; private OpenVPNService mOpenVPNService; private LinkedList mFDList = new LinkedList<>(); private LocalServerSocket mServerSocket; private boolean mWaitingForRelease = false; private long mLastHoldRelease = 0; private static final Vector active = new Vector<>(); private LocalSocket mServerSocketLocal; private pauseReason lastPauseReason = pauseReason.noNetwork; private PausedStateCallback mPauseCallback; private boolean mShuttingDown; public OpenVpnManagementThread(VpnProfile profile, OpenVPNService openVpnService) { mProfile = profile; mOpenVPNService = openVpnService; mResumeHandler = new Handler(openVpnService.getMainLooper()); } private Runnable mResumeHoldRunnable = new Runnable() { @Override public void run() { if (shouldBeRunning()) { releaseHoldCmd(); } } }; public boolean openManagementInterface(@NonNull Context c) { // Could take a while to open connection int tries = 8; String socketName = (c.getCacheDir().getAbsolutePath() + "/" + "mgmtsocket"); // The mServerSocketLocal is transferred to the LocalServerSocket, ignore warning mServerSocketLocal = new LocalSocket(); while (tries > 0 && !mServerSocketLocal.isBound()) { try { mServerSocketLocal.bind(new LocalSocketAddress(socketName, LocalSocketAddress.Namespace.FILESYSTEM)); } catch (IOException e) { // wait 300 ms before retrying try { Thread.sleep(300); } catch (InterruptedException ignored) { } } tries--; } try { mServerSocket = new LocalServerSocket(mServerSocketLocal.getFileDescriptor()); return true; } catch (IOException e) { VpnStatus.logException(e); } return false; } public void managmentCommand(String cmd) { try { if (mSocket != null && mSocket.getOutputStream() != null) { mSocket.getOutputStream().write(cmd.getBytes()); mSocket.getOutputStream().flush(); } } catch (IOException e) { // Ignore socket stack traces } } @Override public void run() { byte[] buffer = new byte[2048]; // mSocket.setSoTimeout(5); // Setting a timeout cannot be that bad String pendingInput = ""; synchronized (active) { active.add(this); } try { // Wait for a client to connect mSocket = mServerSocket.accept(); InputStream instream = mSocket.getInputStream(); // Close the management socket after client connected mServerSocket.close(); // Closing one of the two sockets also closes the other //mServerSocketLocal.close(); while (true) { int numbytesread = instream.read(buffer); if (numbytesread == -1) return; FileDescriptor[] fds = null; try { fds = mSocket.getAncillaryFileDescriptors(); } catch (IOException e) { VpnStatus.logException("Error reading fds from socket", e); } if (fds != null) { Collections.addAll(mFDList, fds); } String input = new String(buffer, 0, numbytesread, "UTF-8"); pendingInput += input; pendingInput = processInput(pendingInput); } } catch (IOException e) { if (!e.getMessage().equals("socket closed") && !e.getMessage().equals("Connection reset by peer")) VpnStatus.logException(e); } synchronized (active) { active.remove(this); } } //! Hack O Rama 2000! private void protectFileDescriptor(FileDescriptor fd) { try { Method getInt = FileDescriptor.class.getDeclaredMethod("getInt$"); int fdint = (Integer) getInt.invoke(fd); // You can even get more evil by parsing toString() and extract the int from that :) boolean result = mOpenVPNService.protect(fdint); if (!result) VpnStatus.logWarning("Could not protect VPN socket"); //ParcelFileDescriptor pfd = ParcelFileDescriptor.fromFd(fdint); //pfd.close(); NativeUtils.jniclose(fdint); return; } catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException | IllegalAccessException | NullPointerException e) { VpnStatus.logException("Failed to retrieve fd from socket (" + fd + ")", e); } Log.d("Openvpn", "Failed to retrieve fd from socket: " + fd); } private String processInput(String pendingInput) { while (pendingInput.contains("\n")) { String[] tokens = pendingInput.split("\\r?\\n", 2); processCommand(tokens[0]); if (tokens.length == 1) // No second part, newline was at the end pendingInput = ""; else pendingInput = tokens[1]; } return pendingInput; } private void processCommand(String command) { //Log.i(TAG, "Line from managment" + command); if (command.startsWith(">") && command.contains(":")) { String[] parts = command.split(":", 2); String cmd = parts[0].substring(1); String argument = parts[1]; switch (cmd) { case "INFO": /* Ignore greeting from management */ return; case "PASSWORD": processPWCommand(argument); break; case "HOLD": handleHold(argument); break; case "NEED-OK": processNeedCommand(argument); break; case "BYTECOUNT": processByteCount(argument); break; case "STATE": if (!mShuttingDown) processState(argument); break; case "PROXY": processProxyCMD(argument); break; case "LOG": processLogMessage(argument); break; case "RSA_SIGN": processSignCommand(argument); break; default: VpnStatus.logWarning("MGMT: Got unrecognized command" + command); Log.i(TAG, "Got unrecognized command" + command); break; } } else if (command.startsWith("SUCCESS:")) { /* Ignore this kind of message too */ return; } else if (command.startsWith("PROTECTFD: ")) { FileDescriptor fdtoprotect = mFDList.pollFirst(); if (fdtoprotect != null) protectFileDescriptor(fdtoprotect); } else { Log.i(TAG, "Got unrecognized line from managment" + command); VpnStatus.logWarning("MGMT: Got unrecognized line from management:" + command); } } private void processLogMessage(String argument) { String[] args = argument.split(",", 4); // 0 unix time stamp // 1 log level N,I,E etc. /* (b) zero or more message flags in a single string: I -- informational F -- fatal error N -- non-fatal error W -- warning D -- debug, and */ // 2 log message Log.d("OpenVPN", argument); VpnStatus.LogLevel level; switch (args[1]) { case "I": level = VpnStatus.LogLevel.INFO; break; case "W": level = VpnStatus.LogLevel.WARNING; break; case "D": level = VpnStatus.LogLevel.VERBOSE; break; case "F": level = VpnStatus.LogLevel.ERROR; break; default: level = VpnStatus.LogLevel.INFO; break; } int ovpnlevel = Integer.parseInt(args[2]) & 0x0F; String msg = args[3]; if (msg.startsWith("MANAGEMENT: CMD")) ovpnlevel = Math.max(4, ovpnlevel); VpnStatus.logMessageOpenVPN(level, ovpnlevel, msg); } boolean shouldBeRunning() { if (mPauseCallback == null) return false; else return mPauseCallback.shouldBeRunning(); } private void handleHold(String argument) { int waittime = Integer.parseInt(argument.split(":")[1]); if (shouldBeRunning()) { if (waittime > 1) VpnStatus.updateStateString("CONNECTRETRY", String.valueOf(waittime), R.string.state_waitconnectretry, ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET); mResumeHandler.postDelayed(mResumeHoldRunnable, waittime * 1000); if (waittime > 5) { VpnStatus.logInfo(R.string.state_waitconnectretry, String.valueOf(waittime)); } } else { mWaitingForRelease = true; VpnStatus.updateStatePause(lastPauseReason); } } private void releaseHoldCmd() { mResumeHandler.removeCallbacks(mResumeHoldRunnable); if ((System.currentTimeMillis() - mLastHoldRelease) < 5000) { try { Thread.sleep(3000); } catch (InterruptedException ignored) { } } mWaitingForRelease = false; mLastHoldRelease = System.currentTimeMillis(); managmentCommand("hold release\n"); managmentCommand("bytecount " + mBytecountInterval + "\n"); managmentCommand("state on\n"); //managmentCommand("log on all\n"); } public void releaseHold() { if (mWaitingForRelease) releaseHoldCmd(); } private void processProxyCMD(String argument) { String[] args = argument.split(",", 3); SocketAddress proxyaddr = ProxyDetection.detectProxy(mProfile); if (args.length >= 2) { String proto = args[1]; if (proto.equals("UDP")) { proxyaddr = null; } } if (proxyaddr instanceof InetSocketAddress) { InetSocketAddress isa = (InetSocketAddress) proxyaddr; VpnStatus.logInfo(R.string.using_proxy, isa.getHostName(), isa.getPort()); String proxycmd = String.format(Locale.ENGLISH, "proxy HTTP %s %d\n", isa.getHostName(), isa.getPort()); managmentCommand(proxycmd); } else { managmentCommand("proxy NONE\n"); } } private void processState(String argument) { String[] args = argument.split(",", 3); String currentstate = args[1]; if (args[2].equals(",,")) VpnStatus.updateStateString(currentstate, ""); else VpnStatus.updateStateString(currentstate, args[2]); } private void processByteCount(String argument) { // >BYTECOUNT:{BYTES_IN},{BYTES_OUT} int comma = argument.indexOf(','); long in = Long.parseLong(argument.substring(0, comma)); long out = Long.parseLong(argument.substring(comma + 1)); VpnStatus.updateByteCount(in, out); } private void processNeedCommand(String argument) { int p1 = argument.indexOf('\''); int p2 = argument.indexOf('\'', p1 + 1); String needed = argument.substring(p1 + 1, p2); String extra = argument.split(":", 2)[1]; String status = "ok"; switch (needed) { case "PROTECTFD": FileDescriptor fdtoprotect = mFDList.pollFirst(); protectFileDescriptor(fdtoprotect); break; case "DNSSERVER": mOpenVPNService.addDNS(extra); break; case "DNSDOMAIN": mOpenVPNService.setDomain(extra); break; case "ROUTE": { String[] routeparts = extra.split(" "); /* buf_printf (&out, "%s %s %s dev %s", network, netmask, gateway, rgi->iface); else buf_printf (&out, "%s %s %s", network, netmask, gateway); */ if (routeparts.length == 5) { if (BuildConfig.DEBUG) Assert.assertEquals("dev", routeparts[3]); mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], routeparts[4]); } else if (routeparts.length >= 3) { mOpenVPNService.addRoute(routeparts[0], routeparts[1], routeparts[2], null); } else { VpnStatus.logError("Unrecognized ROUTE cmd:" + Arrays.toString(routeparts) + " | " + argument); } break; } case "ROUTE6": { String[] routeparts = extra.split(" "); mOpenVPNService.addRoutev6(routeparts[0], routeparts[1]); break; } case "IFCONFIG": String[] ifconfigparts = extra.split(" "); int mtu = Integer.parseInt(ifconfigparts[2]); mOpenVPNService.setLocalIP(ifconfigparts[0], ifconfigparts[1], mtu, ifconfigparts[3]); break; case "IFCONFIG6": mOpenVPNService.setLocalIPv6(extra); break; case "PERSIST_TUN_ACTION": // check if tun cfg stayed the same status = mOpenVPNService.getTunReopenStatus(); break; case "OPENTUN": if (sendTunFD(needed, extra)) return; else status = "cancel"; // This not nice or anything but setFileDescriptors accepts only FilDescriptor class :( break; default: Log.e(TAG, "Unknown needok command " + argument); return; } String cmd = String.format("needok '%s' %s\n", needed, status); managmentCommand(cmd); } private boolean sendTunFD(String needed, String extra) { if (!extra.equals("tun")) { // We only support tun VpnStatus.logError(String.format("Device type %s requested, but only tun is possible with the Android API, sorry!", extra)); return false; } ParcelFileDescriptor pfd = mOpenVPNService.openTun(); if (pfd == null) return false; Method setInt; int fdint = pfd.getFd(); try { setInt = FileDescriptor.class.getDeclaredMethod("setInt$", int.class); FileDescriptor fdtosend = new FileDescriptor(); setInt.invoke(fdtosend, fdint); FileDescriptor[] fds = {fdtosend}; mSocket.setFileDescriptorsForSend(fds); // Trigger a send so we can close the fd on our side of the channel // The API documentation fails to mention that it will not reset the file descriptor to // be send and will happily send the file descriptor on every write ... String cmd = String.format("needok '%s' %s\n", needed, "ok"); managmentCommand(cmd); // Set the FileDescriptor to null to stop this mad behavior mSocket.setFileDescriptorsForSend(null); pfd.close(); return true; } catch (NoSuchMethodException | IllegalArgumentException | InvocationTargetException | IOException | IllegalAccessException exp) { VpnStatus.logException("Could not send fd over socket", exp); } return false; } private void processPWCommand(String argument) { //argument has the form Need 'Private Key' password // or ">PASSWORD:Verification Failed: '%s' ['%s']" String needed; try { int p1 = argument.indexOf('\''); int p2 = argument.indexOf('\'', p1 + 1); needed = argument.substring(p1 + 1, p2); if (argument.startsWith("Verification Failed")) { proccessPWFailed(needed, argument.substring(p2 + 1)); return; } } catch (StringIndexOutOfBoundsException sioob) { VpnStatus.logError("Could not parse management Password command: " + argument); return; } String pw = null; if (needed.equals("Private Key")) { pw = mProfile.getPasswordPrivateKey(); } else if (needed.equals("Auth")) { String usercmd = String.format("username '%s' %s\n", needed, VpnProfile.openVpnEscape(mProfile.mUsername)); managmentCommand(usercmd); pw = mProfile.getPasswordAuth(); } if (pw != null) { String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw)); managmentCommand(cmd); } else { VpnStatus.logError(String.format("Openvpn requires Authentication type '%s' but no password/key information available", needed)); } } private void proccessPWFailed(String needed, String args) { VpnStatus.updateStateString("AUTH_FAILED", needed + args, R.string.state_auth_failed, ConnectionStatus.LEVEL_AUTH_FAILED); } private static boolean stopOpenVPN() { synchronized (active) { boolean sendCMD = false; for (OpenVpnManagementThread mt : active) { mt.managmentCommand("signal SIGINT\n"); sendCMD = true; try { if (mt.mSocket != null) mt.mSocket.close(); } catch (IOException e) { // Ignore close error on already closed socket } } return sendCMD; } } @Override public void networkChange(boolean samenetwork) { if (mWaitingForRelease) releaseHold(); else if (samenetwork) managmentCommand("network-change samenetwork\n"); else managmentCommand("network-change\n"); } @Override public void setPauseCallback(PausedStateCallback callback) { mPauseCallback = callback; } public void signalusr1() { mResumeHandler.removeCallbacks(mResumeHoldRunnable); if (!mWaitingForRelease) managmentCommand("signal SIGUSR1\n"); else // If signalusr1 is called update the state string // if there is another for stopping VpnStatus.updateStatePause(lastPauseReason); } public void reconnect() { signalusr1(); releaseHold(); } private void processSignCommand(String b64data) { String signed_string = mProfile.getSignedData(b64data); if (signed_string == null) { managmentCommand("rsa-sig\n"); managmentCommand("\nEND\n"); stopOpenVPN(); return; } managmentCommand("rsa-sig\n"); managmentCommand(signed_string); managmentCommand("\nEND\n"); } @Override public void pause(pauseReason reason) { lastPauseReason = reason; signalusr1(); } @Override public void resume() { releaseHold(); /* Reset the reason why we are disconnected */ lastPauseReason = pauseReason.noNetwork; } @Override public boolean stopVPN(boolean replaceConnection) { mShuttingDown = true; return stopOpenVPN(); } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/PRNGFixes.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core;/* * This software is provided 'as-is', without any express or implied * warranty. In no event will Google be held liable for any damages * arising from the use of this software. * * Permission is granted to anyone to use this software for any purpose, * including commercial applications, and to alter it and redistribute it * freely, as long as the origin is not misrepresented. */ import android.os.Build; import android.os.Process; import android.util.Log; import java.io.ByteArrayOutputStream; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.UnsupportedEncodingException; import java.security.NoSuchAlgorithmException; import java.security.Provider; import java.security.SecureRandom; import java.security.SecureRandomSpi; import java.security.Security; /** * Fixes for the output of the default PRNG having low entropy. * * The fixes need to be applied via {@link #apply()} before any use of Java * Cryptography Architecture primitives. A good place to invoke them is in the * application's {@code onCreate}. */ public final class PRNGFixes { private static final int VERSION_CODE_JELLY_BEAN = 16; private static final int VERSION_CODE_JELLY_BEAN_MR2 = 18; private static final byte[] BUILD_FINGERPRINT_AND_DEVICE_SERIAL = getBuildFingerprintAndDeviceSerial(); /** Hidden constructor to prevent instantiation. */ private PRNGFixes() {} /** * Applies all fixes. * * @throws SecurityException if a fix is needed but could not be applied. */ public static void apply() { applyOpenSSLFix(); installLinuxPRNGSecureRandom(); } /** * Applies the fix for OpenSSL PRNG having low entropy. Does nothing if the * fix is not needed. * * @throws SecurityException if the fix is needed but could not be applied. */ private static void applyOpenSSLFix() throws SecurityException { if ((Build.VERSION.SDK_INT < VERSION_CODE_JELLY_BEAN) || (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2)) { // No need to apply the fix return; } try { // Mix in the device- and invocation-specific seed. Class.forName("org.apache.harmony.xnet.provider.jsse.NativeCrypto") .getMethod("RAND_seed", byte[].class) .invoke(null, generateSeed()); // Mix output of Linux PRNG into OpenSSL's PRNG int bytesRead = (Integer) Class.forName( "org.apache.harmony.xnet.provider.jsse.NativeCrypto") .getMethod("RAND_load_file", String.class, long.class) .invoke(null, "/dev/urandom", 1024); if (bytesRead != 1024) { throw new IOException( "Unexpected number of bytes read from Linux PRNG: " + bytesRead); } } catch (Exception e) { throw new SecurityException("Failed to seed OpenSSL PRNG", e); } } /** * Installs a Linux PRNG-backed {@code SecureRandom} implementation as the * default. Does nothing if the implementation is already the default or if * there is not need to install the implementation. * * @throws SecurityException if the fix is needed but could not be applied. */ private static void installLinuxPRNGSecureRandom() throws SecurityException { if (Build.VERSION.SDK_INT > VERSION_CODE_JELLY_BEAN_MR2) { // No need to apply the fix return; } // Install a Linux PRNG-based SecureRandom implementation as the // default, if not yet installed. Provider[] secureRandomProviders = Security.getProviders("SecureRandom.SHA1PRNG"); if ((secureRandomProviders == null) || (secureRandomProviders.length < 1) || (!LinuxPRNGSecureRandomProvider.class.equals( secureRandomProviders[0].getClass()))) { Security.insertProviderAt(new LinuxPRNGSecureRandomProvider(), 1); } // Assert that new SecureRandom() and // SecureRandom.getInstance("SHA1PRNG") return a SecureRandom backed // by the Linux PRNG-based SecureRandom implementation. SecureRandom rng1 = new SecureRandom(); if (!LinuxPRNGSecureRandomProvider.class.equals( rng1.getProvider().getClass())) { throw new SecurityException( "new SecureRandom() backed by wrong Provider: " + rng1.getProvider().getClass()); } SecureRandom rng2; try { rng2 = SecureRandom.getInstance("SHA1PRNG"); } catch (NoSuchAlgorithmException e) { throw new SecurityException("SHA1PRNG not available", e); } if (!LinuxPRNGSecureRandomProvider.class.equals( rng2.getProvider().getClass())) { throw new SecurityException( "SecureRandom.getInstance(\"SHA1PRNG\") backed by wrong" + " Provider: " + rng2.getProvider().getClass()); } } /** * {@code Provider} of {@code SecureRandom} engines which pass through * all requests to the Linux PRNG. */ private static class LinuxPRNGSecureRandomProvider extends Provider { public LinuxPRNGSecureRandomProvider() { super("LinuxPRNG", 1.0, "A Linux-specific random number provider that uses" + " /dev/urandom"); // Although /dev/urandom is not a SHA-1 PRNG, some apps // explicitly request a SHA1PRNG SecureRandom and we thus need to // prevent them from getting the default implementation whose output // may have low entropy. put("SecureRandom.SHA1PRNG", LinuxPRNGSecureRandom.class.getName()); put("SecureRandom.SHA1PRNG ImplementedIn", "Software"); } } /** * {@link SecureRandomSpi} which passes all requests to the Linux PRNG * ({@code /dev/urandom}). */ public static class LinuxPRNGSecureRandom extends SecureRandomSpi { /* * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed * are passed through to the Linux PRNG (/dev/urandom). Instances of * this class seed themselves by mixing in the current time, PID, UID, * build fingerprint, and hardware serial number (where available) into * Linux PRNG. * * Concurrency: Read requests to the underlying Linux PRNG are * serialized (on sLock) to ensure that multiple threads do not get * duplicated PRNG output. */ private static final File URANDOM_FILE = new File("/dev/urandom"); private static final Object sLock = new Object(); /** * Input stream for reading from Linux PRNG or {@code null} if not yet * opened. * * @GuardedBy("sLock") */ private static DataInputStream sUrandomIn; /** * Output stream for writing to Linux PRNG or {@code null} if not yet * opened. * * @GuardedBy("sLock") */ private static OutputStream sUrandomOut; /** * Whether this engine instance has been seeded. This is needed because * each instance needs to seed itself if the client does not explicitly * seed it. */ private boolean mSeeded; @Override protected void engineSetSeed(byte[] bytes) { try { OutputStream out; synchronized (sLock) { out = getUrandomOutputStream(); } out.write(bytes); out.flush(); } catch (IOException e) { // On a small fraction of devices /dev/urandom is not writable. // Log and ignore. Log.w(PRNGFixes.class.getSimpleName(), "Failed to mix seed into " + URANDOM_FILE); } finally { mSeeded = true; } } @Override protected void engineNextBytes(byte[] bytes) { if (!mSeeded) { // Mix in the device- and invocation-specific seed. engineSetSeed(generateSeed()); } try { DataInputStream in; synchronized (sLock) { in = getUrandomInputStream(); } synchronized (in) { in.readFully(bytes); } } catch (IOException e) { throw new SecurityException( "Failed to read from " + URANDOM_FILE, e); } } @Override protected byte[] engineGenerateSeed(int size) { byte[] seed = new byte[size]; engineNextBytes(seed); return seed; } private DataInputStream getUrandomInputStream() { synchronized (sLock) { if (sUrandomIn == null) { // NOTE: Consider inserting a BufferedInputStream between // DataInputStream and FileInputStream if you need higher // PRNG output performance and can live with future PRNG // output being pulled into this process prematurely. try { sUrandomIn = new DataInputStream( new FileInputStream(URANDOM_FILE)); } catch (IOException e) { throw new SecurityException("Failed to open " + URANDOM_FILE + " for reading", e); } } return sUrandomIn; } } private OutputStream getUrandomOutputStream() throws IOException { synchronized (sLock) { if (sUrandomOut == null) { sUrandomOut = new FileOutputStream(URANDOM_FILE); } return sUrandomOut; } } } /** * Generates a device- and invocation-specific seed to be mixed into the * Linux PRNG. */ private static byte[] generateSeed() { try { ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer); seedBufferOut.writeLong(System.currentTimeMillis()); seedBufferOut.writeLong(System.nanoTime()); seedBufferOut.writeInt(Process.myPid()); seedBufferOut.writeInt(Process.myUid()); seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); seedBufferOut.close(); return seedBuffer.toByteArray(); } catch (IOException e) { throw new SecurityException("Failed to generate seed", e); } } /** * Gets the hardware serial number of this device. * * @return serial number or {@code null} if not available. */ private static String getDeviceSerialNumber() { // We're using the Reflection API because Build.SERIAL is only available // since API Level 9 (Gingerbread, Android 2.3). try { return (String) Build.class.getField("SERIAL").get(null); } catch (Exception ignored) { return null; } } private static byte[] getBuildFingerprintAndDeviceSerial() { StringBuilder result = new StringBuilder(); String fingerprint = Build.FINGERPRINT; if (fingerprint != null) { result.append(fingerprint); } String serial = getDeviceSerialNumber(); if (serial != null) { result.append(serial); } try { return result.toString().getBytes("UTF-8"); } catch (UnsupportedEncodingException e) { throw new RuntimeException("UTF-8 encoding not supported"); } } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/ProfileManager.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.app.Activity; import android.content.Context; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.preference.PreferenceManager; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Set; import de.blinkt.openvpn.VpnProfile; public class ProfileManager { private static final String PREFS_NAME = "VPNList"; private static final String LAST_CONNECTED_PROFILE = "lastConnectedProfile"; private static ProfileManager instance; private static VpnProfile mLastConnectedVpn = null; private HashMap profiles = new HashMap<>(); private static VpnProfile tmpprofile = null; private static VpnProfile get(String key) { if (tmpprofile != null && tmpprofile.getUUIDString().equals(key)) return tmpprofile; if (instance == null) return null; return instance.profiles.get(key); } private ProfileManager() { } private static void checkInstance(Context context) { if (instance == null) { instance = new ProfileManager(); instance.loadVPNList(context); } } synchronized public static ProfileManager getInstance(Context context) { checkInstance(context); return instance; } public static void setConntectedVpnProfileDisconnected(Context c) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); Editor prefsedit = prefs.edit(); prefsedit.putString(LAST_CONNECTED_PROFILE, null); prefsedit.apply(); } /** * Sets the profile that is connected (to connect if the service restarts) */ public static void setConnectedVpnProfile(Context c, VpnProfile connectedProfile) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); Editor prefsedit = prefs.edit(); prefsedit.putString(LAST_CONNECTED_PROFILE, connectedProfile.getUUIDString()); prefsedit.apply(); mLastConnectedVpn = connectedProfile; } /** * Returns the profile that was last connected (to connect if the service restarts) */ public static VpnProfile getLastConnectedProfile(Context c) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(c); String lastConnectedProfile = prefs.getString(LAST_CONNECTED_PROFILE, null); if (lastConnectedProfile != null) return get(c, lastConnectedProfile); else return null; } public Collection getProfiles() { return profiles.values(); } public VpnProfile getProfileByName(String name) { for (VpnProfile vpnp : profiles.values()) { if (vpnp.getName().equals(name)) { return vpnp; } } return null; } public void saveProfileList(Context context) { SharedPreferences sharedprefs = context.getSharedPreferences(PREFS_NAME, Activity.MODE_PRIVATE); Editor editor = sharedprefs.edit(); editor.putStringSet("vpnlist", profiles.keySet()); // For reasing I do not understand at all // Android saves my prefs file only one time // if I remove the debug code below :( int counter = sharedprefs.getInt("counter", 0); editor.putInt("counter", counter + 1); editor.apply(); } public void addProfile(VpnProfile profile) { profiles.put(profile.getUUID().toString(), profile); } public static void setTemporaryProfile(VpnProfile tmp) { ProfileManager.tmpprofile = tmp; } public static boolean isTempProfile() { return mLastConnectedVpn == tmpprofile; } public void saveProfile(Context context, VpnProfile profile) { ObjectOutputStream vpnfile; try { vpnfile = new ObjectOutputStream(context.openFileOutput((profile.getUUID().toString() + ".vp"), Activity.MODE_PRIVATE)); vpnfile.writeObject(profile); vpnfile.flush(); vpnfile.close(); } catch (IOException e) { VpnStatus.logException("saving VPN profile", e); throw new RuntimeException(e); } } private void loadVPNList(Context context) { profiles = new HashMap<>(); SharedPreferences listpref = context.getSharedPreferences(PREFS_NAME, Activity.MODE_PRIVATE); Set vlist = listpref.getStringSet("vpnlist", null); if (vlist == null) { vlist = new HashSet<>(); } for (String vpnentry : vlist) { try { ObjectInputStream vpnfile = new ObjectInputStream(context.openFileInput(vpnentry + ".vp")); VpnProfile vp = ((VpnProfile) vpnfile.readObject()); // Sanity check if (vp == null || vp.mName == null || vp.getUUID() == null) continue; vp.upgradeProfile(); profiles.put(vp.getUUID().toString(), vp); } catch (IOException | ClassNotFoundException e) { VpnStatus.logException("Loading VPN List", e); } } } public void removeProfile(Context context, VpnProfile profile) { String vpnentry = profile.getUUID().toString(); profiles.remove(vpnentry); saveProfileList(context); context.deleteFile(vpnentry + ".vp"); if (mLastConnectedVpn == profile) mLastConnectedVpn = null; } public static VpnProfile get(Context context, String profileUUID) { checkInstance(context); return get(profileUUID); } public static VpnProfile getLastConnectedVpn() { return mLastConnectedVpn; } public static VpnProfile getAlwaysOnVPN(Context context) { checkInstance(context); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); String uuid = prefs.getString("alwaysOnVpn", null); return get(uuid); } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/ProxyDetection.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import com.vasilkoff.easyvpnfree.R; import java.net.InetSocketAddress; import java.net.MalformedURLException; import java.net.Proxy; import java.net.ProxySelector; import java.net.SocketAddress; import java.net.URISyntaxException; import java.net.URL; import java.util.List; import de.blinkt.openvpn.VpnProfile; public class ProxyDetection { static SocketAddress detectProxy(VpnProfile vp) { // Construct a new url with https as protocol try { URL url = new URL(String.format("https://%s:%s",vp.mServerName,vp.mServerPort)); Proxy proxy = getFirstProxy(url); if(proxy==null) return null; SocketAddress addr = proxy.address(); if (addr instanceof InetSocketAddress) { return addr; } } catch (MalformedURLException e) { VpnStatus.logError(R.string.getproxy_error, e.getLocalizedMessage()); } catch (URISyntaxException e) { VpnStatus.logError(R.string.getproxy_error, e.getLocalizedMessage()); } return null; } static Proxy getFirstProxy(URL url) throws URISyntaxException { System.setProperty("java.net.useSystemProxies", "true"); List proxylist = ProxySelector.getDefault().select(url.toURI()); if (proxylist != null) { for (Proxy proxy: proxylist) { SocketAddress addr = proxy.address(); if (addr != null) { return proxy; } } } return null; } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/VPNLaunchHelper.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.annotation.TargetApi; import android.content.Context; import android.content.Intent; import android.os.Build; import com.vasilkoff.easyvpnfree.R; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; import java.util.Vector; import de.blinkt.openvpn.VpnProfile; public class VPNLaunchHelper { private static final String MININONPIEVPN = "nopie_openvpn"; private static final String MINIPIEVPN = "pie_openvpn"; private static final String OVPNCONFIGFILE = "android.conf"; private static String writeMiniVPN(Context context) { String[] abis; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) abis = getSupportedABIsLollipop(); else //noinspection deprecation abis = new String[]{Build.CPU_ABI, Build.CPU_ABI2}; String nativeAPI = NativeUtils.getNativeAPI(); if (!nativeAPI.equals(abis[0])) { VpnStatus.logWarning(R.string.abi_mismatch, Arrays.toString(abis), nativeAPI); abis = new String[] {nativeAPI}; } for (String abi: abis) { File vpnExecutable = new File(context.getCacheDir(), getMiniVPNExecutableName() + "." + abi); if ((vpnExecutable.exists() && vpnExecutable.canExecute()) || writeMiniVPNBinary(context, abi, vpnExecutable)) { return vpnExecutable.getPath(); } } return null; } @TargetApi(Build.VERSION_CODES.LOLLIPOP) private static String[] getSupportedABIsLollipop() { return Build.SUPPORTED_ABIS; } private static String getMiniVPNExecutableName() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) return MINIPIEVPN; else return MININONPIEVPN; } public static String[] replacePieWithNoPie(String[] mArgv) { mArgv[0] = mArgv[0].replace(MINIPIEVPN, MININONPIEVPN); return mArgv; } static String[] buildOpenvpnArgv(Context c) { Vector args = new Vector<>(); String binaryName = writeMiniVPN(c); // Add fixed paramenters //args.add("/data/data/de.blinkt.openvpn/lib/openvpn"); if(binaryName==null) { VpnStatus.logError("Error writing minivpn binary"); return null; } args.add(binaryName); args.add("--config"); args.add(getConfigFilePath(c)); return args.toArray(new String[args.size()]); } private static boolean writeMiniVPNBinary(Context context, String abi, File mvpnout) { try { InputStream mvpn; try { mvpn = context.getAssets().open(getMiniVPNExecutableName() + "." + abi); } catch (IOException errabi) { VpnStatus.logInfo("Failed getting assets for archicture " + abi); return false; } FileOutputStream fout = new FileOutputStream(mvpnout); byte buf[]= new byte[4096]; int lenread = mvpn.read(buf); while(lenread> 0) { fout.write(buf, 0, lenread); lenread = mvpn.read(buf); } fout.close(); if(!mvpnout.setExecutable(true)) { VpnStatus.logError("Failed to make OpenVPN executable"); return false; } return true; } catch (IOException e) { VpnStatus.logException(e); return false; } } public static void startOpenVpn(VpnProfile startprofile, Context context) { Intent startVPN = startprofile.prepareStartService(context); if(startVPN!=null) context.startService(startVPN); } public static String getConfigFilePath(Context context) { return context.getCacheDir().getAbsolutePath() + "/" + OVPNCONFIGFILE; } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/VpnStatus.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.content.Context; import android.os.Build; import android.os.HandlerThread; import android.os.Message; import com.vasilkoff.easyvpnfree.R; import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; import java.util.LinkedList; import java.util.Locale; import java.util.Vector; public class VpnStatus { public static LinkedList logbuffer; private static Vector logListener; private static Vector stateListener; private static Vector byteCountListener; private static String mLaststatemsg = ""; private static String mLaststate = "NOPROCESS"; private static int mLastStateresid = R.string.state_noprocess; private static long mlastByteCount[] = {0, 0, 0, 0}; private static HandlerThread mHandlerThread; public static void logException(LogLevel ll, String context, Exception e) { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); LogItem li; if (context != null) { li = new LogItem(ll, R.string.unhandled_exception_context, e.getMessage(), sw.toString(), context); } else { li = new LogItem(ll, R.string.unhandled_exception, e.getMessage(), sw.toString()); } newLogItem(li); } public static void logException(Exception e) { logException(LogLevel.ERROR, null, e); } public static void logException(String context, Exception e) { logException(LogLevel.ERROR, context, e); } static final int MAXLOGENTRIES = 1000; public static boolean isVPNActive() { return mLastLevel != ConnectionStatus.LEVEL_AUTH_FAILED && !(mLastLevel == ConnectionStatus.LEVEL_NOTCONNECTED); } public static String getLastCleanLogMessage(Context c) { String message = mLaststatemsg; switch (mLastLevel) { case LEVEL_CONNECTED: String[] parts = mLaststatemsg.split(","); /* (a) the integer unix date/time, (b) the state name, 0 (c) optional descriptive string (used mostly on RECONNECTING and EXITING to show the reason for the disconnect), 1 (d) optional TUN/TAP local IPv4 address 2 (e) optional address of remote server, 3 (f) optional port of remote server, 4 (g) optional local address, 5 (h) optional local port, and 6 (i) optional TUN/TAP local IPv6 address. */ // Return only the assigned IP addresses in the UI if (parts.length >= 7) message = String.format(Locale.US, "%s %s", parts[1], parts[6]); break; } while (message.endsWith(",")) message = message.substring(0, message.length() - 1); String status = mLaststate; if (status.equals("NOPROCESS")) return message; if (mLastStateresid == R.string.state_waitconnectretry) { return c.getString(R.string.state_waitconnectretry, mLaststatemsg); } String prefix = c.getString(mLastStateresid); if (mLastStateresid == R.string.unknown_state) message = status + message; if (message.length() > 0) prefix += ": "; return prefix + message; } public static void initLogCache(File cacheDir) { mHandlerThread = new HandlerThread("LogFileWriter", Thread.MIN_PRIORITY); mHandlerThread.start(); mLogFileHandler = new LogFileHandler(mHandlerThread.getLooper()); Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_INIT, cacheDir); mLogFileHandler.sendMessage(m); } public static void flushLog() { if (mLogFileHandler != null) mLogFileHandler.sendEmptyMessage(LogFileHandler.FLUSH_TO_DISK); } public enum ConnectionStatus { LEVEL_CONNECTED, LEVEL_VPNPAUSED, LEVEL_CONNECTING_SERVER_REPLIED, LEVEL_CONNECTING_NO_SERVER_REPLY_YET, LEVEL_NONETWORK, LEVEL_NOTCONNECTED, LEVEL_START, LEVEL_AUTH_FAILED, LEVEL_WAITING_FOR_USER_INPUT, UNKNOWN_LEVEL } public enum LogLevel { INFO(2), ERROR(-2), WARNING(1), VERBOSE(3), DEBUG(4); protected int mValue; LogLevel(int value) { mValue = value; } public int getInt() { return mValue; } public static LogLevel getEnumByValue(int value) { switch (value) { case 1: return INFO; case 2: return ERROR; case 3: return WARNING; case 4: return DEBUG; default: return null; } } } // keytool -printcert -jarfile de.blinkt.openvpn_85.apk public static final byte[] officalkey = {-58, -42, -44, -106, 90, -88, -87, -88, -52, -124, 84, 117, 66, 79, -112, -111, -46, 86, -37, 109}; public static final byte[] officaldebugkey = {-99, -69, 45, 71, 114, -116, 82, 66, -99, -122, 50, -70, -56, -111, 98, -35, -65, 105, 82, 43}; public static final byte[] amazonkey = {-116, -115, -118, -89, -116, -112, 120, 55, 79, -8, -119, -23, 106, -114, -85, -56, -4, 105, 26, -57}; public static final byte[] fdroidkey = {-92, 111, -42, -46, 123, -96, -60, 79, -27, -31, 49, 103, 11, -54, -68, -27, 17, 2, 121, 104}; private static ConnectionStatus mLastLevel = ConnectionStatus.LEVEL_NOTCONNECTED; private static LogFileHandler mLogFileHandler; static { logbuffer = new LinkedList<>(); logListener = new Vector<>(); stateListener = new Vector<>(); byteCountListener = new Vector<>(); logInformation(); } public interface LogListener { void newLog(LogItem logItem); } public interface StateListener { void updateState(String state, String logmessage, int localizedResId, ConnectionStatus level); } public interface ByteCountListener { void updateByteCount(long in, long out, long diffIn, long diffOut); } public synchronized static void logMessage(LogLevel level, String prefix, String message) { newLogItem(new LogItem(level, prefix + message)); } public synchronized static void clearLog() { logbuffer.clear(); logInformation(); if (mLogFileHandler != null) mLogFileHandler.sendEmptyMessage(LogFileHandler.TRIM_LOG_FILE); } private static void logInformation() { /* String nativeAPI; try { nativeAPI = NativeUtils.getNativeAPI(); } catch (UnsatisfiedLinkError ignore) { nativeAPI = "error"; } logInfo(R.string.mobile_info, Build.MODEL, Build.BOARD, Build.BRAND, Build.VERSION.SDK_INT, nativeAPI, Build.VERSION.RELEASE, Build.ID, Build.FINGERPRINT, "", "");*/ } public synchronized static void addLogListener(LogListener ll) { logListener.add(ll); } public synchronized static void removeLogListener(LogListener ll) { logListener.remove(ll); } public synchronized static void addByteCountListener(ByteCountListener bcl) { bcl.updateByteCount(mlastByteCount[0], mlastByteCount[1], mlastByteCount[2], mlastByteCount[3]); byteCountListener.add(bcl); } public synchronized static void removeByteCountListener(ByteCountListener bcl) { byteCountListener.remove(bcl); } public synchronized static void addStateListener(StateListener sl) { if (!stateListener.contains(sl)) { stateListener.add(sl); if (mLaststate != null) sl.updateState(mLaststate, mLaststatemsg, mLastStateresid, mLastLevel); } } private static int getLocalizedState(String state) { switch (state) { case "CONNECTING": return R.string.state_connecting; case "WAIT": return R.string.state_wait; case "AUTH": return R.string.state_auth; case "GET_CONFIG": return R.string.state_get_config; case "ASSIGN_IP": return R.string.state_assign_ip; case "ADD_ROUTES": return R.string.state_add_routes; case "CONNECTED": return R.string.state_connected; case "DISCONNECTED": return R.string.state_disconnected; case "RECONNECTING": return R.string.state_reconnecting; case "EXITING": return R.string.state_exiting; case "RESOLVE": return R.string.state_resolve; case "TCP_CONNECT": return R.string.state_tcp_connect; default: return R.string.unknown_state; } } public static void updateStatePause(OpenVPNManagement.pauseReason pauseReason) { switch (pauseReason) { case noNetwork: VpnStatus.updateStateString("NONETWORK", "", R.string.state_nonetwork, ConnectionStatus.LEVEL_NONETWORK); break; case screenOff: VpnStatus.updateStateString("SCREENOFF", "", R.string.state_screenoff, ConnectionStatus.LEVEL_VPNPAUSED); break; case userPause: VpnStatus.updateStateString("USERPAUSE", "", R.string.state_userpause, ConnectionStatus.LEVEL_VPNPAUSED); break; } } private static ConnectionStatus getLevel(String state) { String[] noreplyet = {"CONNECTING", "WAIT", "RECONNECTING", "RESOLVE", "TCP_CONNECT"}; String[] reply = {"AUTH", "GET_CONFIG", "ASSIGN_IP", "ADD_ROUTES"}; String[] connected = {"CONNECTED"}; String[] notconnected = {"DISCONNECTED", "EXITING"}; for (String x : noreplyet) if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTING_NO_SERVER_REPLY_YET; for (String x : reply) if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTING_SERVER_REPLIED; for (String x : connected) if (state.equals(x)) return ConnectionStatus.LEVEL_CONNECTED; for (String x : notconnected) if (state.equals(x)) return ConnectionStatus.LEVEL_NOTCONNECTED; return ConnectionStatus.UNKNOWN_LEVEL; } public synchronized static void removeStateListener(StateListener sl) { stateListener.remove(sl); } synchronized public static LogItem[] getlogbuffer() { // The stoned way of java to return an array from a vector // brought to you by eclipse auto complete return logbuffer.toArray(new LogItem[logbuffer.size()]); } public static void updateStateString(String state, String msg) { int rid = getLocalizedState(state); ConnectionStatus level = getLevel(state); updateStateString(state, msg, rid, level); } public synchronized static void updateStateString(String state, String msg, int resid, ConnectionStatus level) { // Workound for OpenVPN doing AUTH and wait and being connected // Simply ignore these state if (mLastLevel == ConnectionStatus.LEVEL_CONNECTED && (state.equals("WAIT") || state.equals("AUTH"))) { newLogItem(new LogItem((LogLevel.DEBUG), String.format("Ignoring OpenVPN Status in CONNECTED state (%s->%s): %s", state, level.toString(), msg))); return; } mLaststate = state; mLaststatemsg = msg; mLastStateresid = resid; mLastLevel = level; for (StateListener sl : stateListener) { sl.updateState(state, msg, resid, level); } //newLogItem(new LogItem((LogLevel.DEBUG), String.format("New OpenVPN Status (%s->%s): %s",state,level.toString(),msg))); } public static void logInfo(String message) { newLogItem(new LogItem(LogLevel.INFO, message)); } public static void logDebug(String message) { newLogItem(new LogItem(LogLevel.DEBUG, message)); } public static void logInfo(int resourceId, Object... args) { newLogItem(new LogItem(LogLevel.INFO, resourceId, args)); } public static void logDebug(int resourceId, Object... args) { newLogItem(new LogItem(LogLevel.DEBUG, resourceId, args)); } private static void newLogItem(LogItem logItem) { newLogItem(logItem, false); } synchronized static void newLogItem(LogItem logItem, boolean cachedLine) { if (cachedLine) { logbuffer.addFirst(logItem); } else { logbuffer.addLast(logItem); if (mLogFileHandler != null) { Message m = mLogFileHandler.obtainMessage(LogFileHandler.LOG_MESSAGE, logItem); mLogFileHandler.sendMessage(m); } } if (logbuffer.size() > MAXLOGENTRIES + MAXLOGENTRIES / 2) { while (logbuffer.size() > MAXLOGENTRIES) logbuffer.removeFirst(); if (mLogFileHandler != null) mLogFileHandler.sendMessage(mLogFileHandler.obtainMessage(LogFileHandler.TRIM_LOG_FILE)); } //if (BuildConfig.DEBUG && !cachedLine && !BuildConfig.FLAVOR.equals("test")) // Log.d("OpenVPN", logItem.getString(null)); for (LogListener ll : logListener) { ll.newLog(logItem); } } public static void logError(String msg) { newLogItem(new LogItem(LogLevel.ERROR, msg)); } public static void logWarning(int resourceId, Object... args) { newLogItem(new LogItem(LogLevel.WARNING, resourceId, args)); } public static void logWarning(String msg) { newLogItem(new LogItem(LogLevel.WARNING, msg)); } public static void logError(int resourceId) { newLogItem(new LogItem(LogLevel.ERROR, resourceId)); } public static void logError(int resourceId, Object... args) { newLogItem(new LogItem(LogLevel.ERROR, resourceId, args)); } public static void logMessageOpenVPN(LogLevel level, int ovpnlevel, String message) { newLogItem(new LogItem(level, ovpnlevel, message)); } public static synchronized void updateByteCount(long in, long out) { long lastIn = mlastByteCount[0]; long lastOut = mlastByteCount[1]; long diffIn = mlastByteCount[2] = Math.max(0, in - lastIn); long diffOut = mlastByteCount[3] = Math.max(0, out - lastOut); mlastByteCount = new long[]{in, out, diffIn, diffOut}; for (ByteCountListener bcl : byteCountListener) { bcl.updateByteCount(in, out, diffIn, diffOut); } } } ================================================ FILE: Android-code/app/src/main/java/de/blinkt/openvpn/core/X509Utils.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package de.blinkt.openvpn.core; import android.content.Context; import android.content.res.Resources; import android.text.TextUtils; import com.vasilkoff.easyvpnfree.R; import de.blinkt.openvpn.VpnProfile; import org.spongycastle.util.io.pem.PemObject; import org.spongycastle.util.io.pem.PemReader; import javax.security.auth.x500.X500Principal; import java.io.*; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateFactory; import java.security.cert.CertificateNotYetValidException; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Date; import java.util.Hashtable; import java.util.Vector; public class X509Utils { public static Certificate[] getCertificatesFromFile(String certfilename) throws FileNotFoundException, CertificateException { CertificateFactory certFact = CertificateFactory.getInstance("X.509"); Vector certificates = new Vector<>(); if(VpnProfile.isEmbedded(certfilename)) { int subIndex = certfilename.indexOf("-----BEGIN CERTIFICATE-----"); do { // The java certifcate reader is ... kind of stupid // It does NOT ignore chars before the --BEGIN ... subIndex = Math.max(0, subIndex); InputStream inStream = new ByteArrayInputStream(certfilename.substring(subIndex).getBytes()); certificates.add(certFact.generateCertificate(inStream)); subIndex = certfilename.indexOf("-----BEGIN CERTIFICATE-----", subIndex+1); } while (subIndex > 0); return certificates.toArray(new Certificate[certificates.size()]); } else { InputStream inStream = new FileInputStream(certfilename); return new Certificate[] {certFact.generateCertificate(inStream)}; } } public static PemObject readPemObjectFromFile (String keyfilename) throws IOException { Reader inStream; if(VpnProfile.isEmbedded(keyfilename)) inStream = new StringReader(VpnProfile.getEmbeddedContent(keyfilename)); else inStream = new FileReader(new File(keyfilename)); PemReader pr = new PemReader(inStream); PemObject r = pr.readPemObject(); pr.close(); return r; } public static String getCertificateFriendlyName (Context c, String filename) { if(!TextUtils.isEmpty(filename)) { try { X509Certificate cert = (X509Certificate) getCertificatesFromFile(filename)[0]; String friendlycn = getCertificateFriendlyName(cert); friendlycn = getCertificateValidityString(cert, c.getResources()) + friendlycn; return friendlycn; } catch (Exception e) { VpnStatus.logError("Could not read certificate" + e.getLocalizedMessage()); } } return c.getString(R.string.cannotparsecert); } public static String getCertificateValidityString(X509Certificate cert, Resources res) { try { cert.checkValidity(); } catch (CertificateExpiredException ce) { return "EXPIRED: "; } catch (CertificateNotYetValidException cny) { return "NOT YET VALID: "; } Date certNotAfter = cert.getNotAfter(); Date now = new Date(); long timeLeft = certNotAfter.getTime() - now.getTime(); // Time left in ms // More than 72h left, display days // More than 3 months display months if (timeLeft > 90l* 24 * 3600 * 1000) { long months = getMonthsDifference(now, certNotAfter); return res.getString(R.string.months_left, months); } else if (timeLeft > 72 * 3600 * 1000) { long days = timeLeft / (24 * 3600 * 1000); return res.getString(R.string.days_left, days); } else { long hours = timeLeft / (3600 * 1000); return res.getString(R.string.hours_left, hours); } } public static int getMonthsDifference(Date date1, Date date2) { int m1 = date1.getYear() * 12 + date1.getMonth(); int m2 = date2.getYear() * 12 + date2.getMonth(); return m2 - m1 + 1; } public static String getCertificateFriendlyName(X509Certificate cert) { X500Principal principal = cert.getSubjectX500Principal(); byte[] encodedSubject = principal.getEncoded(); String friendlyName=null; /* Hack so we do not have to ship a whole Spongy/bouncycastle */ Exception exp=null; try { Class X509NameClass = Class.forName("com.android.org.bouncycastle.asn1.x509.X509Name"); Method getInstance = X509NameClass.getMethod("getInstance",Object.class); Hashtable defaultSymbols = (Hashtable) X509NameClass.getField("DefaultSymbols").get(X509NameClass); if (!defaultSymbols.containsKey("1.2.840.113549.1.9.1")) defaultSymbols.put("1.2.840.113549.1.9.1","eMail"); Object subjectName = getInstance.invoke(X509NameClass, encodedSubject); Method toString = X509NameClass.getMethod("toString",boolean.class,Hashtable.class); friendlyName= (String) toString.invoke(subjectName,true,defaultSymbols); } catch (ClassNotFoundException e) { exp =e ; } catch (NoSuchMethodException e) { exp =e; } catch (InvocationTargetException e) { exp =e; } catch (IllegalAccessException e) { exp =e; } catch (NoSuchFieldException e) { exp =e; } if (exp!=null) VpnStatus.logException("Getting X509 Name from certificate", exp); /* Fallback if the reflection method did not work */ if(friendlyName==null) friendlyName = principal.getName(); // Really evil hack to decode email address // See: http://code.google.com/p/android/issues/detail?id=21531 String[] parts = friendlyName.split(","); for (int i=0;i>> 2) & 0x3f]); out.write(encodingTable[((a1 << 4) | (a2 >>> 4)) & 0x3f]); out.write(encodingTable[((a2 << 2) | (a3 >>> 6)) & 0x3f]); out.write(encodingTable[a3 & 0x3f]); } /* * process the tail end. */ int b1, b2, b3; int d1, d2; switch (modulus) { case 0: /* nothing left to do */ break; case 1: d1 = data[off + dataLength] & 0xff; b1 = (d1 >>> 2) & 0x3f; b2 = (d1 << 4) & 0x3f; out.write(encodingTable[b1]); out.write(encodingTable[b2]); out.write(padding); out.write(padding); break; case 2: d1 = data[off + dataLength] & 0xff; d2 = data[off + dataLength + 1] & 0xff; b1 = (d1 >>> 2) & 0x3f; b2 = ((d1 << 4) | (d2 >>> 4)) & 0x3f; b3 = (d2 << 2) & 0x3f; out.write(encodingTable[b1]); out.write(encodingTable[b2]); out.write(encodingTable[b3]); out.write(padding); break; } return (dataLength / 3) * 4 + ((modulus == 0) ? 0 : 4); } private boolean ignore( char c) { return (c == '\n' || c =='\r' || c == '\t' || c == ' '); } /** * decode the base 64 encoded byte data writing it to the given output stream, * whitespace characters will be ignored. * * @return the number of bytes produced. */ public int decode( byte[] data, int off, int length, OutputStream out) throws IOException { byte b1, b2, b3, b4; int outLen = 0; int end = off + length; while (end > off) { if (!ignore((char)data[end - 1])) { break; } end--; } int i = off; int finish = end - 4; i = nextI(data, i, finish); while (i < finish) { b1 = decodingTable[data[i++]]; i = nextI(data, i, finish); b2 = decodingTable[data[i++]]; i = nextI(data, i, finish); b3 = decodingTable[data[i++]]; i = nextI(data, i, finish); b4 = decodingTable[data[i++]]; out.write((b1 << 2) | (b2 >> 4)); out.write((b2 << 4) | (b3 >> 2)); out.write((b3 << 6) | b4); outLen += 3; i = nextI(data, i, finish); } outLen += decodeLastBlock(out, (char)data[end - 4], (char)data[end - 3], (char)data[end - 2], (char)data[end - 1]); return outLen; } private int nextI(byte[] data, int i, int finish) { while ((i < finish) && ignore((char)data[i])) { i++; } return i; } /** * decode the base 64 encoded String data writing it to the given output stream, * whitespace characters will be ignored. * * @return the number of bytes produced. */ public int decode( String data, OutputStream out) throws IOException { byte b1, b2, b3, b4; int length = 0; int end = data.length(); while (end > 0) { if (!ignore(data.charAt(end - 1))) { break; } end--; } int i = 0; int finish = end - 4; i = nextI(data, i, finish); while (i < finish) { b1 = decodingTable[data.charAt(i++)]; i = nextI(data, i, finish); b2 = decodingTable[data.charAt(i++)]; i = nextI(data, i, finish); b3 = decodingTable[data.charAt(i++)]; i = nextI(data, i, finish); b4 = decodingTable[data.charAt(i++)]; out.write((b1 << 2) | (b2 >> 4)); out.write((b2 << 4) | (b3 >> 2)); out.write((b3 << 6) | b4); length += 3; i = nextI(data, i, finish); } length += decodeLastBlock(out, data.charAt(end - 4), data.charAt(end - 3), data.charAt(end - 2), data.charAt(end - 1)); return length; } private int decodeLastBlock(OutputStream out, char c1, char c2, char c3, char c4) throws IOException { byte b1, b2, b3, b4; if (c3 == padding) { b1 = decodingTable[c1]; b2 = decodingTable[c2]; out.write((b1 << 2) | (b2 >> 4)); return 1; } else if (c4 == padding) { b1 = decodingTable[c1]; b2 = decodingTable[c2]; b3 = decodingTable[c3]; out.write((b1 << 2) | (b2 >> 4)); out.write((b2 << 4) | (b3 >> 2)); return 2; } else { b1 = decodingTable[c1]; b2 = decodingTable[c2]; b3 = decodingTable[c3]; b4 = decodingTable[c4]; out.write((b1 << 2) | (b2 >> 4)); out.write((b2 << 4) | (b3 >> 2)); out.write((b3 << 6) | b4); return 3; } } private int nextI(String data, int i, int finish) { while ((i < finish) && ignore(data.charAt(i))) { i++; } return i; } } ================================================ FILE: Android-code/app/src/main/java/org/spongycastle/util/encoders/Encoder.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package org.spongycastle.util.encoders; import java.io.IOException; import java.io.OutputStream; /** * Encode and decode byte arrays (typically from binary to 7-bit ASCII * encodings). */ public interface Encoder { int encode(byte[] data, int off, int length, OutputStream out) throws IOException; int decode(byte[] data, int off, int length, OutputStream out) throws IOException; int decode(String data, OutputStream out) throws IOException; } ================================================ FILE: Android-code/app/src/main/java/org/spongycastle/util/io/pem/PemGenerationException.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package org.spongycastle.util.io.pem; import java.io.IOException; @SuppressWarnings("serial") public class PemGenerationException extends IOException { private Throwable cause; public PemGenerationException(String message, Throwable cause) { super(message); this.cause = cause; } public PemGenerationException(String message) { super(message); } public Throwable getCause() { return cause; } } ================================================ FILE: Android-code/app/src/main/java/org/spongycastle/util/io/pem/PemHeader.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package org.spongycastle.util.io.pem; public class PemHeader { private String name; private String value; public PemHeader(String name, String value) { this.name = name; this.value = value; } public String getName() { return name; } public String getValue() { return value; } public int hashCode() { return getHashCode(this.name) + 31 * getHashCode(this.value); } public boolean equals(Object o) { if (!(o instanceof PemHeader)) { return false; } PemHeader other = (PemHeader)o; return other == this || (isEqual(this.name, other.name) && isEqual(this.value, other.value)); } private int getHashCode(String s) { if (s == null) { return 1; } return s.hashCode(); } private boolean isEqual(String s1, String s2) { if (s1 == s2) { return true; } if (s1 == null || s2 == null) { return false; } return s1.equals(s2); } } ================================================ FILE: Android-code/app/src/main/java/org/spongycastle/util/io/pem/PemObject.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package org.spongycastle.util.io.pem; import java.util.ArrayList; import java.util.Collections; import java.util.List; @SuppressWarnings("all") public class PemObject implements PemObjectGenerator { private static final List EMPTY_LIST = Collections.unmodifiableList(new ArrayList()); private String type; private List headers; private byte[] content; /** * Generic constructor for object without headers. * * @param type pem object type. * @param content the binary content of the object. */ public PemObject(String type, byte[] content) { this(type, EMPTY_LIST, content); } /** * Generic constructor for object with headers. * * @param type pem object type. * @param headers a list of PemHeader objects. * @param content the binary content of the object. */ public PemObject(String type, List headers, byte[] content) { this.type = type; this.headers = Collections.unmodifiableList(headers); this.content = content; } public String getType() { return type; } public List getHeaders() { return headers; } public byte[] getContent() { return content; } public PemObject generate() throws PemGenerationException { return this; } } ================================================ FILE: Android-code/app/src/main/java/org/spongycastle/util/io/pem/PemObjectGenerator.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package org.spongycastle.util.io.pem; public interface PemObjectGenerator { PemObject generate() throws PemGenerationException; } ================================================ FILE: Android-code/app/src/main/java/org/spongycastle/util/io/pem/PemReader.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package org.spongycastle.util.io.pem; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.List; import org.spongycastle.util.encoders.Base64; public class PemReader extends BufferedReader { private static final String BEGIN = "-----BEGIN "; private static final String END = "-----END "; public PemReader(Reader reader) { super(reader); } public PemObject readPemObject() throws IOException { String line = readLine(); while (line != null && !line.startsWith(BEGIN)) { line = readLine(); } if (line != null) { line = line.substring(BEGIN.length()); int index = line.indexOf('-'); String type = line.substring(0, index); if (index > 0) { return loadObject(type); } } return null; } private PemObject loadObject(String type) throws IOException { String line; String endMarker = END + type; StringBuilder buf = new StringBuilder(); List headers = new ArrayList(); while ((line = readLine()) != null) { if (line.indexOf(":") >= 0) { int index = line.indexOf(':'); String hdr = line.substring(0, index); String value = line.substring(index + 1).trim(); headers.add(new PemHeader(hdr, value)); continue; } if (line.indexOf(endMarker) != -1) { break; } buf.append(line.trim()); } if (line == null) { throw new IOException(endMarker + " not found"); } return new PemObject(type, headers, Base64.decode(buf.toString())); } } ================================================ FILE: Android-code/app/src/main/java/org/spongycastle/util/io/pem/PemWriter.java ================================================ /* * Copyright (c) 2012-2016 Arne Schwabe * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt */ package org.spongycastle.util.io.pem; import java.io.BufferedWriter; import java.io.IOException; import java.io.Writer; import java.util.Iterator; import org.spongycastle.util.encoders.Base64; /** * A generic PEM writer, based on RFC 1421 */ @SuppressWarnings("all") public class PemWriter extends BufferedWriter { private static final int LINE_LENGTH = 64; private final int nlLength; private char[] buf = new char[LINE_LENGTH]; /** * Base constructor. * * @param out output stream to use. */ public PemWriter(Writer out) { super(out); String nl = System.getProperty("line.separator"); if (nl != null) { nlLength = nl.length(); } else { nlLength = 2; } } /** * Return the number of bytes or characters required to contain the * passed in object if it is PEM encoded. * * @param obj pem object to be output * @return an estimate of the number of bytes */ public int getOutputSize(PemObject obj) { // BEGIN and END boundaries. int size = (2 * (obj.getType().length() + 10 + nlLength)) + 6 + 4; if (!obj.getHeaders().isEmpty()) { for (Iterator it = obj.getHeaders().iterator(); it.hasNext();) { PemHeader hdr = (PemHeader)it.next(); size += hdr.getName().length() + ": ".length() + hdr.getValue().length() + nlLength; } size += nlLength; } // base64 encoding int dataLen = ((obj.getContent().length + 2) / 3) * 4; size += dataLen + (((dataLen + LINE_LENGTH - 1) / LINE_LENGTH) * nlLength); return size; } public void writeObject(PemObjectGenerator objGen) throws IOException { PemObject obj = objGen.generate(); writePreEncapsulationBoundary(obj.getType()); if (!obj.getHeaders().isEmpty()) { for (Iterator it = obj.getHeaders().iterator(); it.hasNext();) { PemHeader hdr = (PemHeader)it.next(); this.write(hdr.getName()); this.write(": "); this.write(hdr.getValue()); this.newLine(); } this.newLine(); } writeEncoded(obj.getContent()); writePostEncapsulationBoundary(obj.getType()); } private void writeEncoded(byte[] bytes) throws IOException { bytes = Base64.encode(bytes); for (int i = 0; i < bytes.length; i += buf.length) { int index = 0; while (index != buf.length) { if ((i + index) >= bytes.length) { break; } buf[index] = (char)bytes[i + index]; index++; } this.write(buf, 0, index); this.newLine(); } } private void writePreEncapsulationBoundary( String type) throws IOException { this.write("-----BEGIN " + type + "-----"); this.newLine(); } private void writePostEncapsulationBoundary( String type) throws IOException { this.write("-----END " + type + "-----"); this.newLine(); } } ================================================ FILE: Android-code/app/src/main/res/anim/scale.xml ================================================ ================================================ FILE: Android-code/app/src/main/res/drawable/button_bg.xml ================================================ ================================================ FILE: Android-code/app/src/main/res/drawable/connected_bg.xml ================================================ ================================================ FILE: Android-code/app/src/main/res/drawable/info_servers_bg.xml ================================================ ================================================ FILE: Android-code/app/src/main/res/drawable/server_info_bg.xml ================================================ ================================================ FILE: Android-code/app/src/main/res/drawable/side_nav_bar.xml ================================================ ================================================ FILE: Android-code/app/src/main/res/layout/activity_about.xml ================================================ ================================================ FILE: Android-code/app/src/main/res/layout/activity_base.xml ================================================ ================================================ FILE: Android-code/app/src/main/res/layout/activity_bookmark_server_list.xml ================================================ ================================================ FILE: Android-code/app/src/main/res/layout/activity_home.xml ================================================