pax_global_header00006660000000000000000000000064140420223270014506gustar00rootroot0000000000000052 comment=b6d8deb6b6a5596c4c99cb686adc58b5ad6d04ff mozilla-vpn-client-2.2.0/000077500000000000000000000000001404202232700152335ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/.clang-format000066400000000000000000000023061404202232700176070ustar00rootroot00000000000000# See https://hg.mozilla.org/mozilla-central/file/tip/.clang-format BasedOnStyle: Google # Prevent the loss of indentation with these macros MacroBlockBegin: "^\ JS_BEGIN_MACRO|\ NS_INTERFACE_MAP_BEGIN|\ NS_INTERFACE_TABLE_HEAD|\ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION|\ NS_IMPL_CYCLE_COLLECTION_.*_BEGIN|\ NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED|\ NS_INTERFACE_TABLE_BEGIN|\ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED|\ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED|\ NS_QUERYFRAME_HEAD$" MacroBlockEnd: "^\ JS_END_MACRO|\ NS_INTERFACE_MAP_END|\ NS_IMPL_CYCLE_COLLECTION_.*_END|\ NS_INTERFACE_TABLE_END|\ NS_INTERFACE_TABLE_TAIL.*|\ NS_INTERFACE_MAP_END_.*|\ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END_INHERITED|\ NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED|\ NS_QUERYFRAME_TAIL.*$" SortIncludes: false IndentPPDirectives: AfterHash StatementMacros: [MARKUPMAP, ASSERT_TRUE, ASSERT_FALSE, TEST, CHECK] # The Google coding style states: # You should do this consistently within a single file, so, when modifying an # existing file, use the style in that file. # Let's be more prescriptive and default to the one used in the Mozilla # coding style DerivePointerAlignment: false PointerAlignment: Left mozilla-vpn-client-2.2.0/.clang-format-ignore000066400000000000000000000000751404202232700210710ustar00rootroot00000000000000./3rdparty/* ./android/modules/wireguard/* ./src/hacl-star/* mozilla-vpn-client-2.2.0/.github/000077500000000000000000000000001404202232700165735ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/.github/l10n/000077500000000000000000000000001404202232700173455ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/.github/l10n/check_l10n_issues.py000066400000000000000000000044501404202232700232240ustar00rootroot00000000000000#! /usr/bin/env python3 # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. from lxml import etree import json import os import sys script_folder = os.path.abspath(os.path.dirname(__file__)) vpn_root_folder = os.path.join(script_folder, os.pardir, os.pardir) # Extract all strings in the .ts file srcFile = os.path.join(vpn_root_folder, "src", "src.pro") os.system(f"lupdate {srcFile} -ts") # Load the exceptions file. String IDs can be added to each category to # ignore errors. with open(os.path.join(script_folder, "exceptions.json")) as f: exceptions = json.load(f) # Load the .ts file ts_file = os.path.join(vpn_root_folder, "translations", "mozillavpn_en.ts") tree = etree.parse(ts_file) root = tree.getroot() misused_characters = { "'": "Invalid character ' in string. Use a typographic apostrophe ’ instead, or add an exception.", '"': 'Invalid character " in string. Use typographic quotes “” instead, or add an exception.', "...": 'Invalid "..." in string: use proper ellipsis (…) instead.', } errors = {} for message in root.xpath("//message"): message_id = message.get("id") source_text = message.xpath("./source")[0].text source_comment = ( message.xpath("./extracomment")[0].text if message.xpath("./extracomment") else "" ) # Check if there are misused characters in the strings if message_id not in exceptions["characters"]: for character in misused_characters.keys(): if character in source_text: errors[message_id] = misused_characters[character] # Check if the string is empty if message_id not in exceptions["empty"]: if source_text == "": errors[message_id] = "String should not be empty." # Check if there are variables in the string but no comments if message_id not in exceptions["comments"]: if "%" in source_text and source_comment == "": errors[message_id] = "Strings with variables should always have a comment." if errors: print("\n----\nERRORS:") for message_id, error in errors.items(): print(f"{message_id}:\n {error}") sys.exit(1) else: print("\nNo errors found.") mozilla-vpn-client-2.2.0/.github/l10n/exceptions.json000066400000000000000000000000761404202232700224240ustar00rootroot00000000000000{ "characters": [], "comments": [], "empty": [] } mozilla-vpn-client-2.2.0/.github/workflows/000077500000000000000000000000001404202232700206305ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/.github/workflows/android.yaml000066400000000000000000000125621404202232700231420ustar00rootroot00000000000000name: Android on: push: branches: - main - 'releases/**' pull_request: branches: - main - 'releases/**' jobs: android-production-debug: runs-on: ubuntu-20.04 steps: - name: Clone repository uses: actions/checkout@v2 - name: Install Qt uses: jurplel/install-qt-action@v2 with: host: 'linux' version: '5.15.2' target: 'android' install-deps: 'true' modules: 'qtcharts qtnetworkauth' - name: set up JDK 1.8 uses: actions/setup-java@v1 with: java-version: 1.8 - uses: nttld/setup-ndk@v1 id: setup-ndk with: ndk-version: r20b - name: Checkout submodules shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" git submodule sync --recursive git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - name: Importing translation files shell: bash run: | python3 scripts/importLanguages.py -p - name: Compilation run: | export ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }} bash ./scripts/android_package.sh /home/runner/work/mozilla-vpn-client/Qt/5.15.2 -p -d - name: Upload APK uses: actions/upload-artifact@v1 with: name: production-debug path: .tmp/src/android-build/mozillavpn.apk android-production-release: runs-on: ubuntu-20.04 steps: - name: Clone repository uses: actions/checkout@v2 - name: Install Qt uses: jurplel/install-qt-action@v2 with: host: 'linux' version: '5.15.2' target: 'android' install-deps: 'true' modules: 'qtcharts qtnetworkauth' - name: set up JDK 1.8 uses: actions/setup-java@v1 with: java-version: 1.8 - uses: nttld/setup-ndk@v1 id: setup-ndk with: ndk-version: r20b - name: Checkout submodules shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" git submodule sync --recursive git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - name: Importing translation files shell: bash run: | python3 scripts/importLanguages.py -p - name: Compilation run: | export ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }} bash ./scripts/android_package.sh /home/runner/work/mozilla-vpn-client/Qt/5.15.2 -p - name: Upload APK uses: actions/upload-artifact@v1 with: name: production-release path: .tmp/src/android-build/build/outputs/apk/release/ - name: Upload Debug Symbols(arm64-v8a) uses: actions/upload-artifact@v1 with: name: production-release debug-sym(arm64-v8a) path: .tmp/src/android-build/build/intermediates/merged_native_libs/release/out/lib/arm64-v8a - name: Upload Debug Symbols uses: actions/upload-artifact@v1 with: name: production-release debug-sym(armeabi-v7a) path: .tmp/src/android-build/build/intermediates/merged_native_libs/release/out/lib/armeabi-v7a - name: Upload Debug Symbols uses: actions/upload-artifact@v1 with: name: production-release debug-sym(x86) path: .tmp/src/android-build/build/intermediates/merged_native_libs/release/out/lib/x86 - name: Upload Debug Symbols uses: actions/upload-artifact@v1 with: name: production-release debug-sym(x86_64) path: .tmp/src/android-build/build/intermediates/merged_native_libs/release/out/lib/x86_64 android-staging-debug: runs-on: ubuntu-20.04 steps: - name: Clone repository uses: actions/checkout@v2 - name: Install Qt uses: jurplel/install-qt-action@v2 with: host: 'linux' version: '5.15.2' target: 'android' install-deps: 'true' modules: 'qtcharts qtnetworkauth' - name: set up JDK 1.8 uses: actions/setup-java@v1 with: java-version: 1.8 - uses: nttld/setup-ndk@v1 id: setup-ndk with: ndk-version: r20b - name: Checkout submodules shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" git submodule sync --recursive git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - name: Importing translation files shell: bash run: | python3 scripts/importLanguages.py - name: Compilation run: | export ANDROID_NDK_ROOT=${{ steps.setup-ndk.outputs.ndk-path }} bash ./scripts/android_package.sh /home/runner/work/mozilla-vpn-client/Qt/5.15.2 -d - name: Upload APK uses: actions/upload-artifact@v1 with: name: staging-debug path: .tmp/src/android-build/mozillavpn.apk mozilla-vpn-client-2.2.0/.github/workflows/functional_tests.yaml000066400000000000000000000051471404202232700251070ustar00rootroot00000000000000name: Functional tests on: push: branches: - main - 'releases/**' pull_request: branches: - main - 'releases/**' jobs: functionaltests: name: Functional tests runs-on: ubuntu-20.04 steps: - name: Install Linux packages run: | # Add external PPA, latest version of QT is 5.12.x for Ubuntu 20.04 sudo add-apt-repository ppa:beineri/opt-qt-5.15.2-focal -y sudo apt update sudo apt install git qt515base qt515tools qt515svg qt515networkauth-no-lgpl qt515charts-no-lgpl libgl-dev libpolkit-gobject-1-dev qt515quickcontrols2 qt515imageformats qt515graphicaleffects qt515websockets qt515declarative -y - name: Clone repository uses: actions/checkout@v2 - name: Checkout submodules shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" git submodule sync --recursive git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - name: Importing translation files shell: bash run: | # Manually add QT executables to path export PATH=/opt/qt515/bin:$PATH python3 scripts/importLanguages.py - name: Compile shell: bash run: | # Manually add QT executables to path export PATH=/opt/qt515/bin:$PATH qmake CONFIG+=DUMMY QMAKE_CXX=clang++ QMAKE_LINK=clang++ CONFIG+=debug CONFIG+=inspector QT+=svg make -j8 - name: Install xvfb run: | sudo apt install xvfb -y - name: Install firefox run: | sudo apt install firefox -y - name: Install geckodriver run: | sudo apt install wget -y wget https://github.com/mozilla/geckodriver/releases/download/v0.26.0/geckodriver-v0.26.0-linux64.tar.gz -O geckodriver.tar.gz tar xvf geckodriver.tar.gz - name: Install node dependecies run: | npm install dotenv npm install selenium-webdriver npm install mocha npm install websocket - name: Run the test script run: | export PATH=.:$(npm bin):$PATH export HEADLESS=yes xvfb-run -a ./scripts/test_function.sh ./src/mozillavpn env: ACCOUNT_EMAIL: ${{ secrets.ACCOUNT_EMAIL }} ACCOUNT_PASSWORD: ${{ secrets.ACCOUNT_PASSWORD }} - name: Uploading screenshots uses: actions/upload-artifact@v1 with: name: Screen capture path: /tmp/screencapture mozilla-vpn-client-2.2.0/.github/workflows/import_lang.yaml000066400000000000000000000020451404202232700240300ustar00rootroot00000000000000name: UpdateStrings on: workflow_dispatch: schedule: - cron: '0 7 * * 1-5' # Run Mon-Fri at 7AM UTC jobs: fetch: runs-on: ubuntu-20.04 steps: - name: Clone main code repository uses: actions/checkout@v2 - name: Checkout submodules shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" git submodule sync --recursive git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - name: Checkout mozilla-vpn-client-l10n Git run: | cd i18n/ git fetch default_branch=$(git symbolic-ref refs/remotes/origin/HEAD | sed 's@^refs/remotes/origin/@@') git checkout "origin/$default_branch" --force - uses: peter-evans/create-pull-request@v3 with: commit-message: "Extract new strings and update all locales" branch: l10n_automation delete-branch: true title: "Extract new strings" mozilla-vpn-client-2.2.0/.github/workflows/linters.yaml000066400000000000000000000030751404202232700232010ustar00rootroot00000000000000name: Linters (clang, l10n) on: # Triggers the workflow on pull request events but only for the main branch pull_request: branches: - main - 'releases/**' types: [ opened, synchronize, reopened ] jobs: fetch: runs-on: ubuntu-20.04 steps: - name: Install Linux packages run: | # Add external PPA, latest version of QT is 5.12.x for Ubuntu 20.04 sudo add-apt-repository ppa:beineri/opt-qt-5.15.2-focal -y sudo apt update sudo apt install git qt515tools -y - name: Clone repository uses: actions/checkout@v2 - name: Set up Python 3 uses: actions/setup-python@v2 with: python-version: '3.8' - name: Install Python dependencies run: | pip install lxml - name: Check for l10n errors run: | # Manually add QT executables to path export PATH=/opt/qt515/bin:$PATH lupdate -version python .github/l10n/check_l10n_issues.py - name: Check for issues with clang-format uses: DoozyX/clang-format-lint-action@v0.11 with: source: '.' clangFormatVersion: 11 style: file ktlint: name: Run ktLint runs-on: ubuntu-latest steps: - name: Clone repo uses: actions/checkout@master with: fetch-depth: 1 - name: ktlint uses: ScaCap/action-ktlint@master with: github_token: ${{ secrets.github_token }} reporter: github-pr-review # Change reporter android: true mozilla-vpn-client-2.2.0/.github/workflows/linux.yaml000066400000000000000000000052511404202232700226560ustar00rootroot00000000000000name: Linux package (focal) on: push: branches: - main - 'releases/**' pull_request: branches: - main - 'releases/**' jobs: staging: runs-on: ubuntu-20.04 steps: - name: Install Linux packages run: | # Add external PPA, latest version of QT is 5.12.x for Ubuntu 20.04 sudo add-apt-repository ppa:beineri/opt-qt-5.15.2-focal -y sudo apt update sudo apt install git qt515tools qt515svg qt515networkauth-no-lgpl qt515charts-no-lgpl libgl-dev libpolkit-gobject-1-dev devscripts debhelper cdbs quilt qt515graphicaleffects qt515imageformats qt515quickcontrols2 libxcb-image0-dev libxcb-shape0-dev libxcb-sync0-dev libxcb-render-util0-dev libxcb-xfixes0-dev libxcb-icccm4-dev libx11-xcb-dev libxcb-keysyms1-dev libasound2-dev libaudio-dev libcups2-dev libdbus-1-dev libglu1-mesa-dev libmng-dev libtiff5-dev libxcursor-dev libxi-dev libxinerama-dev libxmu-dev libxrandr-dev libxv-dev libedit-dev libvulkan-dev qt515websockets -y - name: Clone repository uses: actions/checkout@v2 - name: Create package structure shell: bash run: | export PATH=/opt/qt515/bin:$PATH ./scripts/linux_script.sh -s mkdir packages cp .tmp/*.deb packages - name: Uploading uses: actions/upload-artifact@v1 with: name: Staging Build path: packages production: runs-on: ubuntu-20.04 steps: - name: Install Linux packages run: | # Add external PPA, latest version of QT is 5.12.x for Ubuntu 20.04 sudo add-apt-repository ppa:beineri/opt-qt-5.15.2-focal -y sudo apt update sudo apt install git qt515tools qt515svg qt515networkauth-no-lgpl qt515charts-no-lgpl libgl-dev libpolkit-gobject-1-dev devscripts debhelper cdbs quilt qt515graphicaleffects qt515imageformats qt515quickcontrols2 libxcb-image0-dev libxcb-shape0-dev libxcb-sync0-dev libxcb-render-util0-dev libxcb-xfixes0-dev libxcb-icccm4-dev libx11-xcb-dev libxcb-keysyms1-dev libasound2-dev libaudio-dev libcups2-dev libdbus-1-dev libglu1-mesa-dev libmng-dev libtiff5-dev libxcursor-dev libxi-dev libxinerama-dev libxmu-dev libxrandr-dev libxv-dev libedit-dev libvulkan-dev qt515websockets -y - name: Clone repository uses: actions/checkout@v2 - name: Create package structure shell: bash run: | export PATH=/opt/qt515/bin:$PATH ./scripts/linux_script.sh mkdir packages cp .tmp/*.deb packages - name: Uploading uses: actions/upload-artifact@v1 with: name: Production Build path: packages mozilla-vpn-client-2.2.0/.github/workflows/macos-build.yaml000066400000000000000000000144731404202232700237240ustar00rootroot00000000000000name: MacOS on: push: branches: - main - 'releases/**' pull_request: branches: - main - 'releases/**' jobs: macos-staging: runs-on: macos-latest steps: - name: Clone repository uses: actions/checkout@v2 - name: Checkout submodules shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" git submodule sync --recursive git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - name: Install Qt shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" git clone https://github.com/bakulf/qt_static_macos cd qt_static_macos cat x* > qt_static.tar.gz tar xf qt_static.tar.gz cd .. - name: Retrieving wireguard-go version shell: bash run: | (cd macos/gobridge && go list -m golang.zx2c4.com/wireguard | sed -n 's/.*v\([0-9.]*\).*/#define WIREGUARD_GO_VERSION "\1"/p') > macos/gobridge/wireguard-go-version.h - name: Importing translation files shell: bash run: | export PATH=/Users/runner/work/mozilla-vpn-client/mozilla-vpn-client/qt_static_macos/qt/bin:$PATH python3 scripts/importLanguages.py - name: Configuring the build shell: bash run: | export PATH=/Users/runner/work/mozilla-vpn-client/mozilla-vpn-client/qt_static_macos/qt/bin:$PATH SHORTVERSION=$(cat version.pri | grep VERSION | grep defined | cut -d= -f2 | tr -d \ ) FULLVERSION=$(echo $SHORTVERSION | cut -d. -f1).$(date +"%Y%m%d%H%M") echo "$SHORTVERSION - $FULLVERSION" echo "DEVELOPMENT_TEAM = 43AQ936H96" >> xcode.xconfig echo "GROUP_ID_MACOS = group.org.mozilla.macos.Guardian" >> xcode.xconfig echo "APP_ID_MACOS = org.mozilla.macos.FirefoxVPN" >> xcode.xconfig echo "NETEXT_ID_MACOS = org.mozilla.macos.FirefoxVPN.network-extension" >> xcode.xconfig echo "LOGIN_ID_MACOS = org.mozilla.macos.FirefoxVPN.login" >> xcode.xconfig echo "GROUP_ID_IOS = group.org.mozilla.ios.Guardian" >> xcode.xconfig echo "APP_ID_IOS = org.mozilla.ios.FirefoxVPN" >> xcode.xconfig echo "NETEXT_ID_IOS = org.mozilla.ios.FirefoxVPN.network-extension" >> xcode.xconfig qmake \ QTPLUGINS+=qsvg \ CONFIG-=static \ CONFIG+=release \ CONFIG-=debug \ CONFIG-=debug_and_release \ VERSION=$SHORTVERSION \ -spec macx-xcode \ MVPN_MACOS=1 \ src/src.pro ruby scripts/xcode_patcher.rb \ "MozillaVPN.xcodeproj" \ "$SHORTVERSION" \ "$FULLVERSION" \ macos - name: Compiling shell: bash run: xcodebuild build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -project MozillaVPN.xcodeproj - name: Upload app uses: actions/upload-artifact@v2 with: name: staging path: "/Users/runner/work/mozilla-vpn-client/mozilla-vpn-client/Release/Mozilla VPN*" macos-production: runs-on: macos-latest steps: - name: Clone repository uses: actions/checkout@v2 - name: Checkout submodules shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" git submodule sync --recursive git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - name: Install Qt shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" git clone https://github.com/bakulf/qt_static_macos cd qt_static_macos cat x* > qt_static.tar.gz tar xf qt_static.tar.gz cd .. - name: Retrieving wireguard-go version shell: bash run: | (cd macos/gobridge && go list -m golang.zx2c4.com/wireguard | sed -n 's/.*v\([0-9.]*\).*/#define WIREGUARD_GO_VERSION "\1"/p') > macos/gobridge/wireguard-go-version.h - name: Importing translation files shell: bash run: | export PATH=/Users/runner/work/mozilla-vpn-client/mozilla-vpn-client/qt_static_macos/qt/bin:$PATH python3 scripts/importLanguages.py -p - name: Configuring the build shell: bash run: | export PATH=/Users/runner/work/mozilla-vpn-client/mozilla-vpn-client/qt_static_macos/qt/bin:$PATH SHORTVERSION=$(cat version.pri | grep VERSION | grep defined | cut -d= -f2 | tr -d \ ) FULLVERSION=$(echo $SHORTVERSION | cut -d. -f1).$(date +"%Y%m%d%H%M") echo "$SHORTVERSION - $FULLVERSION" echo "DEVELOPMENT_TEAM = 43AQ936H96" >> xcode.xconfig echo "GROUP_ID_MACOS = group.org.mozilla.macos.Guardian" >> xcode.xconfig echo "APP_ID_MACOS = org.mozilla.macos.FirefoxVPN" >> xcode.xconfig echo "NETEXT_ID_MACOS = org.mozilla.macos.FirefoxVPN.network-extension" >> xcode.xconfig echo "LOGIN_ID_MACOS = org.mozilla.macos.FirefoxVPN.login" >> xcode.xconfig echo "GROUP_ID_IOS = group.org.mozilla.ios.Guardian" >> xcode.xconfig echo "APP_ID_IOS = org.mozilla.ios.FirefoxVPN" >> xcode.xconfig echo "NETEXT_ID_IOS = org.mozilla.ios.FirefoxVPN.network-extension" >> xcode.xconfig qmake \ QTPLUGINS+=qsvg \ CONFIG-=static \ CONFIG+=release \ CONFIG-=debug \ CONFIG-=debug_and_release \ CONFIG+=production \ VERSION=$SHORTVERSION \ -spec macx-xcode \ MVPN_MACOS=1 \ src/src.pro ruby scripts/xcode_patcher.rb \ "MozillaVPN.xcodeproj" \ "$SHORTVERSION" \ "$FULLVERSION" \ macos - name: Compiling shell: bash run: xcodebuild build CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -project MozillaVPN.xcodeproj - name: Upload app uses: actions/upload-artifact@v2 with: name: production path: "/Users/runner/work/mozilla-vpn-client/mozilla-vpn-client/Release/Mozilla VPN*" mozilla-vpn-client-2.2.0/.github/workflows/test_coverage.yaml000066400000000000000000000025741404202232700243560ustar00rootroot00000000000000name: Unit Test Coverage on: push: branches: - main - 'releases/**' pull_request: branches: - main - 'releases/**' jobs: fetch: runs-on: ubuntu-20.04 steps: - name: Install Linux packages run: | # Add external PPA, latest version of QT is 5.12.x for Ubuntu 20.04 sudo add-apt-repository ppa:beineri/opt-qt-5.15.2-focal -y sudo apt update sudo apt install git qt515tools qt515svg qt515networkauth-no-lgpl qt515charts-no-lgpl libgl-dev libpolkit-gobject-1-dev -y - name: Clone repository uses: actions/checkout@v2 - name: Checkout submodules shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" git submodule sync --recursive git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - name: Compile shell: bash run: | # Manually add QT executables to path export PATH=/opt/qt515/bin:$PATH qmake CONFIG+=DUMMY QMAKE_CXX=clang++ QMAKE_LINK=clang++ CONFIG+=debug QT+=svg make -j8 - name: Run the test script shell: bash run: | # Manually add QT executables to path export PATH=/opt/qt515/bin:$PATH ./scripts/test_coverage.sh mozilla-vpn-client-2.2.0/.github/workflows/wasm.yaml000066400000000000000000000034451404202232700224710ustar00rootroot00000000000000name: WebAssembly on: push: branches: - main - 'releases/**' pull_request: branches: - main - 'releases/**' jobs: wasm: runs-on: ubuntu-20.04 steps: - name: Clone repository uses: actions/checkout@v2 - name: Checkout submodules shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" git submodule sync --recursive git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - name: Install Qt shell: bash run: | python3 -m pip install aqtinstall python3 -m aqt install --outputdir /opt 5.15.0 linux desktop wasm_32 -m qtcharts - name: Setup emsdk uses: mymindstorm/setup-emsdk@v7 with: version: 1.39.8 - name: Compiling shell: bash run: | ./scripts/wasm_compile.sh /opt/5.15.0/wasm_32/bin - name: Uploading uses: actions/upload-artifact@v1 with: name: WebAssembly Build path: wasm ghPages: runs-on: ubuntu-20.04 needs: [wasm] if: github.ref == 'refs/heads/main' # Only do this on Main Branch Pushes and not PR's name: Publish Wasm on Github Pages env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Download a Build Artifact uses: actions/download-artifact@v2.0.8 with: name: WebAssembly Build # Destination path path: _site - name: GH Pages deploy uses: Cecilapp/GitHub-Pages-deploy@3.1.0 with: # A verified email. email: sstreich@mozilla.com # Where static files are. build_dir: _site mozilla-vpn-client-2.2.0/.github/workflows/windows-build.yaml000066400000000000000000000072331404202232700243100ustar00rootroot00000000000000name: Windows on: push: branches: - main - 'releases/**' pull_request: branches: - main - 'releases/**' jobs: windows-staging: name: Windows staging build runs-on: windows-latest steps: - name: Clone repository uses: actions/checkout@v2 - name: Checkout submodules shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" git submodule sync --recursive git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - name: Install Qt shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" git clone https://github.com/bakulf/qt_static_windows cd qt_static_windows cat x* > qt_static.tar.bz2 tar xf qt_static.tar.bz2 tar xf msm.tar.gz mkdir /c/MozillaVPNBuild cp -r * /c/MozillaVPNBuild cp -r *.msm .. cd .. - name: Adding msbuild to PATH uses: ilammy/msvc-dev-cmd@v1 - name: Compilation script shell: bash run: | export PATH=/c/MozillaVPNBuild/bin:$PATH ./scripts/windows_compile.bat - name: Upload app uses: actions/upload-artifact@v2 with: name: staging path: windows/installer/x64/MozillaVPN.msi windows-production: name: Windows production build runs-on: windows-latest steps: - name: Clone repository uses: actions/checkout@v2 - name: Checkout submodules shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" git submodule sync --recursive git -c "http.extraheader=$auth_header" -c protocol.version=2 submodule update --init --force --recursive --depth=1 - name: Install Qt shell: bash run: | auth_header="$(git config --local --get http.https://github.com/.extraheader)" git clone https://github.com/bakulf/qt_static_windows cd qt_static_windows cat x* > qt_static.tar.bz2 tar xf qt_static.tar.bz2 tar xf msm.tar.gz mkdir /c/MozillaVPNBuild cp -r * /c/MozillaVPNBuild cd .. cp /c/MozillaVPNBuild/bin/libssl-1_1-x64.dll . cp /c/MozillaVPNBuild/bin/libcrypto-1_1-x64.dll . cp /c/MozillaVPNBuild/bin/libEGL.dll . cp /c/MozillaVPNBuild/bin/libGLESv2.dll . cp /c/MozillaVPNBuild/*.msm . - name: Adding msbuild to PATH uses: ilammy/msvc-dev-cmd@v1 - name: Compilation script shell: bash run: | export PATH=/c/MozillaVPNBuild/bin:$PATH ./scripts/windows_compile.bat -p - name: Create the zip package for signature shell: bash run: | mkdir unsigned cp /c/MozillaVPNBuild/bin/libssl-1_1-x64.dll unsigned cp /c/MozillaVPNBuild/bin/libcrypto-1_1-x64.dll unsigned cp /c/MozillaVPNBuild/bin/libEGL.dll unsigned cp /c/MozillaVPNBuild/bin/libGLESv2.dll unsigned cp /c/MozillaVPNBuild/*.msm unsigned cp windows/tunnel/x64/tunnel.dll unsigned cp balrog/x64/balrog.dll unsigned cp *.exe unsigned - name: Upload app uses: actions/upload-artifact@v2 with: name: production path: windows/installer/x64/MozillaVPN.msi - name: Upload unsigned app uses: actions/upload-artifact@v2 with: name: unsigned path: unsigned mozilla-vpn-client-2.2.0/.gitignore000066400000000000000000000016751404202232700172340ustar00rootroot00000000000000.DS_Store Makefile *.o .obj .moc .rcc *.pro.user *.pro.user* src/mozillavpn src/mozillavpn.app src/mozillavpn.framework mozillavpn_wg mozillavpn.qmltypes mozillavpn_metatypes.json mozillavpn_qmltyperegistrations.cpp balrog/balrog.a balrog/balrog.h qrc_qml.cpp .qmake.stash dbus_adaptor.* dbus_interface.* mozillavpn-daemon *_plugin_import.cpp translations/*.qm translations/*.pri .qm .tmp .env # Ignore files that might be added by Android Studio # if someone opens the android/ subproject android/.gradle/ android/.idea/ android/build android/local.properties xcode.xconfig .gradle/ *.ts tests/unit/tests build Debug/ !android/src/debug/ Debug-iphone* Release/ Release-iphone* macos/gobridge/wireguard-go-version.h # Editors *.swp .idea/ .vscode/ MozillaVPN.xcodeproj/ MozillaVPN.vcxproj* #Windows .deps tunnel.dll tunnel.h *.wixobj MozillaVPN.exe MozillaVPN.exp MozillaVPN.lib MozillaVPN_resource.rc # WASM mozillavpn.js mozillavpn.wasm qtloader.js mozilla-vpn-client-2.2.0/.gitmodules000066400000000000000000000012201404202232700174030ustar00rootroot00000000000000[submodule "i18n"] path = i18n url = https://github.com/mozilla-l10n/mozilla-vpn-client-l10n.git branch = main [submodule "3rdparty/wireguard-tools"] path = 3rdparty/wireguard-tools url = https://github.com/WireGuard/wireguard-tools ignore=all [submodule "3rdparty/wireguard-apple"] path = 3rdparty/wireguard-apple url = https://github.com/WireGuard/wireguard-apple ignore=all [submodule "3rdparty/openSSL"] path = 3rdparty/openSSL url = https://github.com/KDAB/android_openssl ignore=all [submodule "3rdparty/wireguard-go"] path = 3rdparty/wireguard-go url = https://github.com/WireGuard/wireguard-go ignore=all mozilla-vpn-client-2.2.0/3rdparty/000077500000000000000000000000001404202232700170035ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/3rdparty/openSSL/000077500000000000000000000000001404202232700203265ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/3rdparty/wireguard-apple/000077500000000000000000000000001404202232700220735ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/3rdparty/wireguard-go/000077500000000000000000000000001404202232700213775ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/3rdparty/wireguard-tools/000077500000000000000000000000001404202232700221325ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/CODE_OF_CONDUCT.md000066400000000000000000000012621404202232700200330ustar00rootroot00000000000000# Community Participation Guidelines This repository is governed by Mozilla's code of conduct and etiquette guidelines. For more details, please read the [Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). ## How to Report For more information on how to report violations of the Community Participation Guidelines, please read our '[How to Report](https://www.mozilla.org/about/governance/policies/participation/reporting/)' page. mozilla-vpn-client-2.2.0/CONTRIBUTING.md000066400000000000000000000013641404202232700174700ustar00rootroot00000000000000# Contributing Discussions happen in [#mozilla-vpn:mozilla.org](https://chat.mozilla.org/#/room/#mozilla-vpn:mozilla.org) Matrix Channel. 1. [Issues marked as `good-first-bug`](https://github.com/mozilla-mobile/mozilla-vpn-client/labels/good%20first%20issue) are self-contained enough that a contributor should be able to work on them. 2. Issues are considered not assigned, until there is a PR linked to them. ## CPP Notes We have the includes following these guidelines: - first the internal headers - after an empty line, the external headers - each block (internal and external headers) alphabetic sorted - if there is a corresponding header for the cpp class, that goes on top. e.g. for file wgutils.cpp, wgutils.h (even if not alphabetic sorted) mozilla-vpn-client-2.2.0/LICENSE.md000066400000000000000000001502641404202232700166470ustar00rootroot00000000000000Mozilla VPN Software Licenses ============================= Unless otherwise specified below, all files in the Mozilla VPN project are licensed under the Mozilla Public License version 2.0 (*MPL2*), with copyright belonging to the Mozilla Foundation. The full text of the MPL is [available on Mozilla's website](https://www.mozilla.org/en-US/MPL/2.0/) and a copy is provided below. This project relies on [QT Quick](https://doc.qt.io/qt-5/qtquick-index.html), provided by [the QT Company](http://www.qt.io/about-us/) and used unmodified under the terms of the Lesser GNU Public License 3.0 (*LGPL-3.0*). The full text of the LGPL3 is [available here](https://opensource.org/licenses/lgpl-3.0) and a copy is provided below. On MacOS and iOS, this project depends on several files provided by the [WireGuard project](https://github.com/WireGuard/wireguard-apple/) under the terms of the MIT Public License (*MIT*), a copy of which is [available here](https://opensource.org/licenses/MIT) and a copy is provided below. The Linux release of this project also relies on several files from the [WireGuard Tools](https://github.com/WireGuard/wireguard-tools/) repository, as well as the FreeDesktop project's [polkit library](https://gitlab.freedesktop.org/polkit/polkit/), all of which are provided under the terms of the Lesser GNU Public License 2.0 (*LGPL-2.0*). A copy of the LGPL-2.0 is [available here](https://opensource.org/licenses/LGPL-2.0) and a copy is provided below. Finally, this project uses EC25519 and CHACHA-POLY implementations from HACL\*, available under the terms of the MIT Public License (*MIT*) and copyright (c) 2016-2020 INRIA, CMU, and Microsoft Corporation. Mozilla Public License Version 2.0 ================================== ### 1. Definitions **1.1. “Contributor”** means each individual or legal entity that creates, contributes to the creation of, or owns Covered Software. **1.2. “Contributor Version”** means the combination of the Contributions of others (if any) used by a Contributor and that particular Contributor's Contribution. **1.3. “Contribution”** means Covered Software of a particular Contributor. **1.4. “Covered Software”** means Source Code Form to which the initial Contributor has attached the notice in Exhibit A, the Executable Form of such Source Code Form, and Modifications of such Source Code Form, in each case including portions thereof. **1.5. “Incompatible With Secondary Licenses”** means * **(a)** that the initial Contributor has attached the notice described in Exhibit B to the Covered Software; or * **(b)** that the Covered Software was made available under the terms of version 1.1 or earlier of the License, but not also under the terms of a Secondary License. **1.6. “Executable Form”** means any form of the work other than Source Code Form. **1.7. “Larger Work”** means a work that combines Covered Software with other material, in a separate file or files, that is not Covered Software. **1.8. “License”** means this document. **1.9. “Licensable”** means having the right to grant, to the maximum extent possible, whether at the time of the initial grant or subsequently, any and all of the rights conveyed by this License. **1.10. “Modifications”** means any of the following: * **(a)** any file in Source Code Form that results from an addition to, deletion from, or modification of the contents of Covered Software; or * **(b)** any new file in Source Code Form that contains any Covered Software. **1.11. “Patent Claims” of a Contributor** means any patent claim(s), including without limitation, method, process, and apparatus claims, in any patent Licensable by such Contributor that would be infringed, but for the grant of the License, by the making, using, selling, offering for sale, having made, import, or transfer of either its Contributions or its Contributor Version. **1.12. “Secondary License”** means either the GNU General Public License, Version 2.0, the GNU Lesser General Public License, Version 2.1, the GNU Affero General Public License, Version 3.0, or any later versions of those licenses. **1.13. “Source Code Form”** means the form of the work preferred for making modifications. **1.14. “You” (or “Your”)** means an individual or a legal entity exercising rights under this License. For legal entities, “You” includes any entity that controls, is controlled by, or is under common control with You. For purposes of this definition, “control” means **(a)** the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or **(b)** ownership of more than fifty percent (50%) of the outstanding shares or beneficial ownership of such entity. ### 2. License Grants and Conditions #### 2.1. Grants Each Contributor hereby grants You a world-wide, royalty-free, non-exclusive license: * **(a)** under intellectual property rights (other than patent or trademark) Licensable by such Contributor to use, reproduce, make available, modify, display, perform, distribute, and otherwise exploit its Contributions, either on an unmodified basis, with Modifications, or as part of a Larger Work; and * **(b)** under Patent Claims of such Contributor to make, use, sell, offer for sale, have made, import, and otherwise transfer either its Contributions or its Contributor Version. #### 2.2. Effective Date The licenses granted in Section 2.1 with respect to any Contribution become effective for each Contribution on the date the Contributor first distributes such Contribution. #### 2.3. Limitations on Grant Scope The licenses granted in this Section 2 are the only rights granted under this License. No additional rights or licenses will be implied from the distribution or licensing of Covered Software under this License. Notwithstanding Section 2.1(b) above, no patent license is granted by a Contributor: * **(a)** for any code that a Contributor has removed from Covered Software; or * **(b)** for infringements caused by: **(i)** Your and any other third party's modifications of Covered Software, or **(ii)** the combination of its Contributions with other software (except as part of its Contributor Version); or * **(c)** under Patent Claims infringed by Covered Software in the absence of its Contributions. This License does not grant any rights in the trademarks, service marks, or logos of any Contributor (except as may be necessary to comply with the notice requirements in Section 3.4). #### 2.4. Subsequent Licenses No Contributor makes additional grants as a result of Your choice to distribute the Covered Software under a subsequent version of this License (see Section 10.2) or under the terms of a Secondary License (if permitted under the terms of Section 3.3). #### 2.5. Representation Each Contributor represents that the Contributor believes its Contributions are its original creation(s) or it has sufficient rights to grant the rights to its Contributions conveyed by this License. #### 2.6. Fair Use This License is not intended to limit any rights You have under applicable copyright doctrines of fair use, fair dealing, or other equivalents. #### 2.7. Conditions Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in Section 2.1. ### 3. Responsibilities #### 3.1. Distribution of Source Form All distribution of Covered Software in Source Code Form, including any Modifications that You create or to which You contribute, must be under the terms of this License. You must inform recipients that the Source Code Form of the Covered Software is governed by the terms of this License, and how they can obtain a copy of this License. You may not attempt to alter or restrict the recipients' rights in the Source Code Form. #### 3.2. Distribution of Executable Form If You distribute Covered Software in Executable Form then: * **(a)** such Covered Software must also be made available in Source Code Form, as described in Section 3.1, and You must inform recipients of the Executable Form how they can obtain a copy of such Source Code Form by reasonable means in a timely manner, at a charge no more than the cost of distribution to the recipient; and * **(b)** You may distribute such Executable Form under the terms of this License, or sublicense it under different terms, provided that the license for the Executable Form does not attempt to limit or alter the recipients' rights in the Source Code Form under this License. #### 3.3. Distribution of a Larger Work You may create and distribute a Larger Work under terms of Your choice, provided that You also comply with the requirements of this License for the Covered Software. If the Larger Work is a combination of Covered Software with a work governed by one or more Secondary Licenses, and the Covered Software is not Incompatible With Secondary Licenses, this License permits You to additionally distribute such Covered Software under the terms of such Secondary License(s), so that the recipient of the Larger Work may, at their option, further distribute the Covered Software under the terms of either this License or such Secondary License(s). #### 3.4. Notices You may not remove or alter the substance of any license notices (including copyright notices, patent notices, disclaimers of warranty, or limitations of liability) contained within the Source Code Form of the Covered Software, except that You may alter any license notices to the extent required to remedy known factual inaccuracies. #### 3.5. Application of Additional Terms You may choose to offer, and to charge a fee for, warranty, support, indemnity or liability obligations to one or more recipients of Covered Software. However, You may do so only on Your own behalf, and not on behalf of any Contributor. You must make it absolutely clear that any such warranty, support, indemnity, or liability obligation is offered by You alone, and You hereby agree to indemnify every Contributor for any liability incurred by such Contributor as a result of warranty, support, indemnity or liability terms You offer. You may include additional disclaimers of warranty and limitations of liability specific to any jurisdiction. ### 4. Inability to Comply Due to Statute or Regulation If it is impossible for You to comply with any of the terms of this License with respect to some or all of the Covered Software due to statute, judicial order, or regulation then You must: **(a)** comply with the terms of this License to the maximum extent possible; and **(b)** describe the limitations and the code they affect. Such description must be placed in a text file included with all distributions of the Covered Software under this License. Except to the extent prohibited by statute or regulation, such description must be sufficiently detailed for a recipient of ordinary skill to be able to understand it. ### 5. Termination **5.1.** The rights granted under this License will terminate automatically if You fail to comply with any of its terms. However, if You become compliant, then the rights granted under this License from a particular Contributor are reinstated **(a)** provisionally, unless and until such Contributor explicitly and finally terminates Your grants, and **(b)** on an ongoing basis, if such Contributor fails to notify You of the non-compliance by some reasonable means prior to 60 days after You have come back into compliance. Moreover, Your grants from a particular Contributor are reinstated on an ongoing basis if such Contributor notifies You of the non-compliance by some reasonable means, this is the first time You have received notice of non-compliance with this License from such Contributor, and You become compliant prior to 30 days after Your receipt of the notice. **5.2.** If You initiate litigation against any entity by asserting a patent infringement claim (excluding declaratory judgment actions, counter-claims, and cross-claims) alleging that a Contributor Version directly or indirectly infringes any patent, then the rights granted to You by any and all Contributors for the Covered Software under Section 2.1 of this License shall terminate. **5.3.** In the event of termination under Sections 5.1 or 5.2 above, all end user license agreements (excluding distributors and resellers) which have been validly granted by You or Your distributors under this License prior to termination shall survive termination. ### 6. Disclaimer of Warranty > Covered Software is provided under this License on an “as is” > basis, without warranty of any kind, either expressed, implied, or > statutory, including, without limitation, warranties that the > Covered Software is free of defects, merchantable, fit for a > particular purpose or non-infringing. The entire risk as to the > quality and performance of the Covered Software is with You. > Should any Covered Software prove defective in any respect, You > (not any Contributor) assume the cost of any necessary servicing, > repair, or correction. This disclaimer of warranty constitutes an > essential part of this License. No use of any Covered Software is > authorized under this License except under this disclaimer. ### 7. Limitation of Liability > Under no circumstances and under no legal theory, whether tort > (including negligence), contract, or otherwise, shall any > Contributor, or anyone who distributes Covered Software as > permitted above, be liable to You for any direct, indirect, > special, incidental, or consequential damages of any character > including, without limitation, damages for lost profits, loss of > goodwill, work stoppage, computer failure or malfunction, or any > and all other commercial damages or losses, even if such party > shall have been informed of the possibility of such damages. This > limitation of liability shall not apply to liability for death or > personal injury resulting from such party's negligence to the > extent applicable law prohibits such limitation. Some > jurisdictions do not allow the exclusion or limitation of > incidental or consequential damages, so this exclusion and > limitation may not apply to You. ### 8. Litigation Any litigation relating to this License may be brought only in the courts of a jurisdiction where the defendant maintains its principal place of business and such litigation shall be governed by laws of that jurisdiction, without reference to its conflict-of-law provisions. Nothing in this Section shall prevent a party's ability to bring cross-claims or counter-claims. ### 9. Miscellaneous This License represents the complete agreement concerning the subject matter hereof. If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any law or regulation which provides that the language of a contract shall be construed against the drafter shall not be used to construe this License against a Contributor. ### 10. Versions of the License #### 10.1. New Versions Mozilla Foundation is the license steward. Except as provided in Section 10.3, no one other than the license steward has the right to modify or publish new versions of this License. Each version will be given a distinguishing version number. #### 10.2. Effect of New Versions You may distribute the Covered Software under the terms of the version of the License under which You originally received the Covered Software, or under the terms of any subsequent version published by the license steward. #### 10.3. Modified Versions If you create software not governed by this License, and you want to create a new license for such software, you may create and use a modified version of this License if you rename the license and remove any references to the name of the license steward (except to note that such modified license differs from this License). #### 10.4. Distributing Source Code Form that is Incompatible With Secondary Licenses If You choose to distribute Source Code Form that is Incompatible With Secondary Licenses under the terms of this version of the License, the notice described in Exhibit B of this License must be attached. ## Exhibit A - Source Code Form License Notice This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. You may add additional accurate notices of copyright ownership. ## Exhibit B - “Incompatible With Secondary Licenses” Notice This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0. The MIT License (MIT) - HACL * ============================== Copyright © 2016-2020 INRIA, CMU and Microsoft Corporation 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. The MIT License (MIT) - WireGuard ================================= Copyright © 2018-2019 WireGuard LLC. All Rights Reserved. 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. GNU Lesser General Public License ================================= _Version 2.1, February 1999_ _Copyright © 1991, 1999 Free Software Foundation, Inc._ _51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA_ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. _This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1._ ### Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. When we speak of free software, we are referring to freedom of use, 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 and use pieces of it in new free programs; and that you are informed that you can do these things. To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. We protect your rights with a two-step method: **(1)** we copyright the library, and **(2)** we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. We call this license the “Lesser” General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a “work based on the library” and a “work that uses the library”. The former contains code derived from the library, whereas the latter must be combined with the library in order to run. ### TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION **0.** This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called “this License”). Each licensee is addressed as “you”. A “library” means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. The “Library”, below, refers to any such software library or work which has been distributed under these terms. A “work based on the Library” means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term “modification”.) “Source code” for a work means the preferred form of the work for making modifications to it. For a library, 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 library. Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. **1.** You may copy and distribute verbatim copies of the Library's complete 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 distribute a copy of this License along with the Library. 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 Library or any portion of it, thus forming a work based on the Library, 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)** The modified work must itself be a software library. * **b)** You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. * **c)** You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. * **d)** If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, 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 Library, 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 Library. In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. **3.** You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. This option is useful when you wish to copy part of the code of the Library into a program that is not a library. **4.** You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you 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. If distribution of 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 satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. **5.** A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a “work that uses the Library”. Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. However, linking a “work that uses the Library” with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a “work that uses the library”. The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. When a “work that uses the Library” uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. **6.** As an exception to the Sections above, you may also combine or link a “work that uses the Library” with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: * **a)** Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable “work that uses the Library”, as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) * **b)** Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. * **c)** Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. * **d)** If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. * **e)** Verify that the user has already received a copy of these materials or that you have already sent this user a copy. For an executable, the required form of the “work that uses the Library” must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be 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. It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. **7.** You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: * **a)** Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. * **b)** Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. **8.** You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library 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. **9.** 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 Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. **10.** Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library 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 with this License. **11.** 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 Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library 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 Library. 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. **12.** If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library 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. **13.** The Free Software Foundation may publish revised and/or new versions of the Lesser 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 Library 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 Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. **14.** If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, 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 **15.** BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY “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 LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. **16.** 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 LIBRARY 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 LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. _END OF TERMS AND CONDITIONS_ ### How to Apply These Terms to Your New Libraries If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. Copyright (C) This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. You should also get your employer (if you work as a programmer) or your school, if any, to sign a “copyright disclaimer” for the library, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the library `Frob' (a library for tweaking knobs) written by James Random Hacker. , 1 April 1990 Ty Coon, President of Vice That's all there is to it! GNU Lesser General Public License ================================= _Version 3, 29 June 2007_ _Copyright © 2007 Free Software Foundation, Inc. <>_ Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. This version of the GNU Lesser General Public License incorporates the terms and conditions of version 3 of the GNU General Public License, supplemented by the additional permissions listed below. ### 0. Additional Definitions As used herein, “this License” refers to version 3 of the GNU Lesser General Public License, and the “GNU GPL” refers to version 3 of the GNU General Public License. “The Library” refers to a covered work governed by this License, other than an Application or a Combined Work as defined below. An “Application” is any work that makes use of an interface provided by the Library, but which is not otherwise based on the Library. Defining a subclass of a class defined by the Library is deemed a mode of using an interface provided by the Library. A “Combined Work” is a work produced by combining or linking an Application with the Library. The particular version of the Library with which the Combined Work was made is also called the “Linked Version”. The “Minimal Corresponding Source” for a Combined Work means the Corresponding Source for the Combined Work, excluding any source code for portions of the Combined Work that, considered in isolation, are based on the Application, and not on the Linked Version. The “Corresponding Application Code” for a Combined Work means the object code and/or source code for the Application, including any data and utility programs needed for reproducing the Combined Work from the Application, but excluding the System Libraries of the Combined Work. ### 1. Exception to Section 3 of the GNU GPL You may convey a covered work under sections 3 and 4 of this License without being bound by section 3 of the GNU GPL. ### 2. Conveying Modified Versions If you modify a copy of the Library, and, in your modifications, a facility refers to a function or data to be supplied by an Application that uses the facility (other than as an argument passed when the facility is invoked), then you may convey a copy of the modified version: * **a)** under this License, provided that you make a good faith effort to ensure that, in the event an Application does not supply the function or data, the facility still operates, and performs whatever part of its purpose remains meaningful, or * **b)** under the GNU GPL, with none of the additional permissions of this License applicable to that copy. ### 3. Object Code Incorporating Material from Library Header Files The object code form of an Application may incorporate material from a header file that is part of the Library. You may convey such object code under terms of your choice, provided that, if the incorporated material is not limited to numerical parameters, data structure layouts and accessors, or small macros, inline functions and templates (ten or fewer lines in length), you do both of the following: * **a)** Give prominent notice with each copy of the object code that the Library is used in it and that the Library and its use are covered by this License. * **b)** Accompany the object code with a copy of the GNU GPL and this license document. ### 4. Combined Works You may convey a Combined Work under terms of your choice that, taken together, effectively do not restrict modification of the portions of the Library contained in the Combined Work and reverse engineering for debugging such modifications, if you also do each of the following: * **a)** Give prominent notice with each copy of the Combined Work that the Library is used in it and that the Library and its use are covered by this License. * **b)** Accompany the Combined Work with a copy of the GNU GPL and this license document. * **c)** For a Combined Work that displays copyright notices during execution, include the copyright notice for the Library among these notices, as well as a reference directing the user to the copies of the GNU GPL and this license document. * **d)** Do one of the following: - **0)** Convey the Minimal Corresponding Source under the terms of this License, and the Corresponding Application Code in a form suitable for, and under terms that permit, the user to recombine or relink the Application with a modified version of the Linked Version to produce a modified Combined Work, in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source. - **1)** Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that **(a)** uses at run time a copy of the Library already present on the user's computer system, and **(b)** will operate properly with a modified version of the Library that is interface-compatible with the Linked Version. * **e)** Provide Installation Information, but only if you would otherwise be required to provide such information under section 6 of the GNU GPL, and only to the extent that such information is necessary to install and execute a modified version of the Combined Work produced by recombining or relinking the Application with a modified version of the Linked Version. (If you use option **4d0**, the Installation Information must accompany the Minimal Corresponding Source and Corresponding Application Code. If you use option **4d1**, you must provide the Installation Information in the manner specified by section 6 of the GNU GPL for conveying Corresponding Source.) ### 5. Combined Libraries You may place library facilities that are a work based on the Library side by side in a single library together with other library facilities that are not Applications and are not covered by this License, and convey such a combined library under terms of your choice, if you do both of the following: * **a)** Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities, conveyed under the terms of this License. * **b)** Give prominent notice with the combined library that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. ### 6. Revised Versions of the GNU Lesser General Public License The Free Software Foundation may publish revised and/or new versions of the GNU Lesser 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 Library as you received it specifies that a certain numbered version of the GNU Lesser General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that published version or of any later version published by the Free Software Foundation. If the Library as you received it does not specify a version number of the GNU Lesser General Public License, you may choose any version of the GNU Lesser General Public License ever published by the Free Software Foundation. If the Library as you received it specifies that a proxy can decide whether future versions of the GNU Lesser General Public License shall apply, that proxy's public statement of acceptance of any version is permanent authorization for you to choose that version for the Library. mozilla-vpn-client-2.2.0/README.md000066400000000000000000000161771404202232700165260ustar00rootroot00000000000000# Mozilla VPN See: https://vpn.mozilla.org ## Dev instructions After checking out the code: * Install the git pre-commit hook (`./scripts/git-pre-commit-format install`) * Build the source (See below) * Run the tests `./scripts/test_coverage.sh` or `./scripts/test_function.sh` ## How to build from the source code ### Linux On linux, the compilation of MozillaVPN is relative easy. You need the following dependencies: - Qt5 >= 5.15.0 - libpolkit-gobject-1-dev >=0.105 - wireguard >=1.0.20200513 - wireguard-tools >=1.0.20200513 - resolvconf >= 1.82 #### QT5 Qt5 can be installed in a number of ways: - download a binary package or the installer from the official QT website: https://www.qt.io/download - use a linux package manager - compile Qt5 (dinamically or statically). To build QT5 statically on Ubuntu/Debian, go to the root directory of this project and follow these steps: ``` curl -L https://download.qt.io/archive/qt/5.15/5.15.1/single/qt-everywhere-src-5.15.1.tar.xz --output qt-everywhere-src-5.15.1.tar.xz tar xvf qt-everywhere-src-5.15.1.tar.xz mv qt-everywhere-src-5.15.1 qt sudo apt build-dep qt5-default sudo apt install libxcb-xinerama0-dev bash scripts/qt5_compile.sh qt qt ``` See https://wiki.qt.io/Building_Qt_5_from_Git#Linux.2FX11 if you get stuck or are on another distro. Finally, **add `$(pwd)/qt/qt/bin` to `PATH`.** #### Initialize submodules ``` git submodule init git submodule update ``` #### Build To build next to source: ``` qmake CONFIG+=production make -j8 # replace 8 with the number of cores. Or use: make -j$(nproc) sudo make install ``` If you prefer to not install at /usr or /etc, you can specify alternate prefixes. Using no prefixes is equivalent to: ``` qmake USRPATH=/usr ETCPATH=/etc CONFIG+=production ``` #### Run If you have built into /usr, simply run ``` mozillavpn ``` Alternatively, you can use two terminals to run the daemon manually and seperately e.g. ``` sudo mozillavpn linuxdaemon mozillavpn ``` mozillavpn linuxdaemon needs privileged access and so if you do not run as root, you will get an authentication prompt every time you try to reconnect the vpn. ### MacOS On macOS, we strongly suggest to compile Qt5 statically. To do that, follow these steps: ``` curl -L https://download.qt.io/archive/qt/5.15/5.15.1/single/qt-everywhere-src-5.15.1.tar.xz --output qt-everywhere-src-5.15.1.tar.xz tar vxf qt-everywhere-src-5.15.1.tar.xz mv qt-everywhere-src-5.15.1 qt bash scripts/qt5_compile.sh `pwd`/qt qt export QT_MACOS_BIN=`pwd`/qt/qt/bin ``` The procedure to compile MozillaVPN for macOS is the following: 1. Install XCodeProj: $ [sudo] gem install xcodeproj 1. Install go if you haven't done it before: https://golang.org/dl/ 1. Update the submodules: $ git submodule init $ git submodule update --remote 1. Run the script (use QT\_MACOS\_BIN env to set the path for the Qt5 macos build bin folder): $ ./scripts/apple\_compile.sh macos 1. Copy `xcode.xconfig.template` to `xcode.xconfig` $ cp xcode.xconfig.template xcode.xconfig 1. Modify xcode.xconfig to something like: ``` DEVELOPMENT_TEAM = 43AQ936H96 # MacOS configuration GROUP_ID_MACOS = <> APP_ID_MACOS = org.mozilla.macos.FirefoxVPN NETEXT_ID_MACOS = org.mozilla.macos.FirefoxVPN.network-extension LOGIN_ID_MACOS = org.mozilla.macos.FirefoxVPN.login # IOS configuration GROUP_ID_IOS = <> APP_ID_IOS = <> NETEXT_ID_IOS = <> ``` 1. Open Xcode and run/test/archive/ship the app To build a Release style build (ready for signing), use: ``` cd MozillaVPN.xcodeproj xcodebuild -scheme MozillaVPN -workspace project.xcworkspace -configuration Release clean build CODE_SIGNING_ALLOWED=NO ``` The built up will show up in `Release/Mozilla VPN.app` (relative to the root of the repo). ### IOS The IOS procedure is similar to the macOS one: 1. Install XCodeProj: $ [sudo] gem install xcodeproj 1. Update the submodules: $ git submodule init $ git submodule update --remote 1. Copy `xcode.xconfig.template` to `xcode.xconfig` $ cp xcode.xconfig.template xcode.xconfig 1. Modify xcode.xconfig to something like: ``` DEVELOPMENT_TEAM = 43AQ936H96 # MacOS configuration GROUP_ID_MACOS = <> APP_ID_IOS = <> NETEXT_ID_IOS = <> LOGIN_ID_IOS = <> # IOS configuration GROUP_ID_IOS = <> APP_ID_IOS = org.mozilla.ios.FirefoxVPN NETEXT_ID_IOS = org.mozilla.ios.FirefoxVPN.network-extension ``` 1. Run the script (use QT\_IOS\_BIN env to set the path for the Qt5 ios build bin folder): $ ./scripts/apple\_compile.sh ios 1. Open Xcode and run/test/archive/ship the app ### Android 1. Install go if you haven't done it before: https://golang.org/dl/ 2. Install Android SDK/NDK + JDK - https://doc.qt.io/qt-5/android-getting-started.html 3. We currently require NDK r20b and SDK >=21 4. Update the submodules: ```bash $ git submodule init $ git submodule update --remote ``` 5. Build the apk ```bash $ ./scripts/android_package.sh /path/to/Qt/5.15.x/ (debug|release) ``` 6. The apk will be located in ```.tmp/src/android-build//build/outputs/apk/debug/android-build-debug.apk``` 7. Install with adb on device/emulator ```bash $ adb install .tmp/src/android-build//build/outputs/apk/debug/android-build-debug.apk ``` ### Windows We use a statically-compiled QT5.15 version to deploy the app. There are many tutorials about to how to compile QT5 on windows, but to make this task easier for everyone, there is a batch script to execute into a visual-studio x86 context: `$ scripts\qt5_compile.bat` The dependencies are: 1. perl: http://strawberryperl.com/ 2. nasm: https://www.nasm.us/ 3. python3: https://www.python.org/downloads/windows/ 4. visual studio 2019: https://visualstudio.microsoft.com/vs/ Openssl can be obtained from here: https://www.openssl.org/source/ Qt5.15 can be obtained from: https://download.qt.io/archive/qt/5.15/5.15.1/single/qt-everywhere-src-5.15.1.tar.xz There is also a script to compile the application: `scripts\windows_compile.bat` ## Bug report Please file bugs here: https://github.com/mozilla-mobile/mozilla-vpn-client/issues ## Status [![Unit Test Coverage](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/test_coverage.yaml/badge.svg)](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/test_coverage.yaml) [![Functional tests](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/functional_tests.yaml/badge.svg)](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/functional_tests.yaml) [![Linters (clang, l10n)](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/linters.yaml/badge.svg)](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/linters.yaml) [![Android](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/android.yaml/badge.svg)](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/android.yaml) [![MacOS](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/macos-build.yaml/badge.svg)](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/macos-build.yaml) [![Windows](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/windows-build.yaml/badge.svg)](https://github.com/mozilla-mobile/mozilla-vpn-client/actions/workflows/windows-build.yaml) mozilla-vpn-client-2.2.0/android/000077500000000000000000000000001404202232700166535ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/AndroidManifest.xml000066400000000000000000000176721404202232700224610ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/android/build.gradle000066400000000000000000000117541404202232700211420ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ buildscript { ext{ kotlin_version = "1.4.10" // for libwg appcompatVersion = '1.1.0' annotationsVersion = '1.0.1' databindingVersion = '3.3.1' jsr305Version = '3.0.2' streamsupportVersion = '1.7.0' threetenabpVersion = '1.1.1' } repositories { google() jcenter() mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:4.0.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } repositories { mavenCentral() google() jcenter() } allprojects { repositories { mavenCentral() google() } } apply plugin: 'com.android.application' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) implementation 'androidx.core:core-ktx:1.1.0' implementation 'com.android.installreferrer:installreferrer:1.1' implementation 'com.wireguard.android:tunnel:1.0.20200927' coreLibraryDesugaring "com.android.tools:desugar_jdk_libs:1.0.10" } android { /******************************************************* * The following variables: * - androidBuildToolsVersion, * - androidCompileSdkVersion * - qt5AndroidDir - holds the path to qt android files * needed to build any Qt application * on Android. * * are defined in gradle.properties file. This file is * updated by QtCreator and androiddeployqt tools. * Changing them manually might break the compilation! *******************************************************/ compileSdkVersion androidCompileSdkVersion.toInteger() buildToolsVersion '28.0.3' dexOptions { javaMaxHeapSize "3g" } sourceSets { main { manifest.srcFile 'AndroidManifest.xml' java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java'] aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl'] res.srcDirs = [qt5AndroidDir + '/res', 'res'] resources.srcDirs = ['resources'] renderscript.srcDirs = ['src'] assets.srcDirs = ['assets'] jniLibs.srcDirs = ['libs'] } } // In case generate a new key anytime: // $: keytool -genkey -v -keystore debug.keystore -storepass android \ // -alias androiddebugkey -keypass android -keyalg RSA \ // -keysize 2048 -validity 10000 signingConfigs { debug { storeFile rootProject.file('./debug.keystore') storePassword('android') keyAlias 'androiddebugkey' keyPassword('android') } } lintOptions { abortOnError false } // Do not compress Qt binary resources file aaptOptions { noCompress 'rcc' } buildTypes { release { // That would enable treeshaking and remove java code that is just called from qt minifyEnabled false } debug { applicationIdSuffix ".debug" versionNameSuffix "-debug" } } defaultConfig { resConfig "en" minSdkVersion 21 targetSdkVersion 30 versionCode System.getenv("VERSIONCODE").toInteger() versionName System.getenv("SHORTVERSION") } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 coreLibraryDesugaringEnabled = true } splits { // Configures multiple APKs based on ABI. abi { if(System.getenv("SPLITAPK")== "1" ){ enable true // Resets the list of ABIs that Gradle should create APKs for to none. reset() // Specifies a list of ABIs that Gradle should create APKs for. include "x86", "armeabi-v7a", "arm64-v8a", "x86_64" // Also generate one universal apk that contains all abi's universalApk true }else{ enable false } } } } import com.android.build.OutputFile // Map for the version code that gives each ABI a value. ext.abiCodes = ['armeabi-v7a':1,"arm64-v8a":2, x86:3, x86_64:4] android.applicationVariants.all { variant -> // Assigns a different version code for each output APK // other than the universal APK. variant.outputs.each { output -> def baseAbiVersionCode = project.ext.abiCodes.get(output.getFilter(OutputFile.ABI)) if (baseAbiVersionCode != null) { output.versionCodeOverride = baseAbiVersionCode + variant.versionCode } } } mozilla-vpn-client-2.2.0/android/debug.keystore000066400000000000000000000043351404202232700215350ustar00rootroot00000000000000androiddebugkeyxJx00 +*yâǖg)1C@hAOtݧ^@PmJDՄzOeUv<]܃jaʲظmf.Hqu4Pp~"H]׌khi^+(2r(n): {THM轗UO`xaZEe&enGd7z2C%U}UUs|[8i9WG-iHjء/ݎ 3h>/cI%Hjki}T;#3R\0?jXnfKZ&wŘ>J1ec9y)i%vq)gHAL93 {ln/ZsqA(xI-E'뉯2^B@l3ne%;dS/2c2~'ϨGi4~jpx-xc zl@16F8sٲCg7 Eι~!ݝtp `{l hM\IhG2 glɧBQƹpTi 0 aciT2am7D<iMm[LV iPNA$GȄxl17L~]B+@FXhZ/z2v'yyUЯyI>1fX.50900q<<0  *H  0u10UUnknown10UUnknown10 UBerlin10U Mozilla10U SecurityProducts10UGithubCI0 210319134925Z 480804134925Z0u10UUnknown10UUnknown10 UBerlin10U Mozilla10U SecurityProducts10UGithubCI0"0  *H 0 /sM NRV/e. 7y {>5?;UE![ea.#R)M f\>5(=n’1}5=l ܹ,97Or6muzY.gOŪ _YC :#`vxjpINRũYǖG5UF92_BLϔi(%L90Z0'Nq N@4$*0MrsRŦqaj~Y+6TT' P1{XG;ALϐ!7}IցT &ߔ{ix8 v8?nMCP̥1)IrgqܱIcTM+Nnmozilla-vpn-client-2.2.0/android/gradle.properties000066400000000000000000000024711404202232700222330ustar00rootroot00000000000000# Project-wide Gradle settings. # IDE (e.g. Android Studio) users: # Gradle settings configured through the IDE *will override* # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx3g # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true # Automatically convert third-party libraries to use AndroidX android.enableJetifier=true # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official kotlin.code.style=official android.bundle.enableUncompressedNativeLibs=false androidBuildToolsVersion=30.0.2 androidCompileSdkVersion=30 buildDir=build org.gradle.caching=true mozilla-vpn-client-2.2.0/android/gradle/000077500000000000000000000000001404202232700201115ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/gradle/wrapper/000077500000000000000000000000001404202232700215715ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/gradle/wrapper/gradle-wrapper.jar000066400000000000000000001520711404202232700252110ustar00rootroot00000000000000PKA META-INF/PKA(M?TMETA-INF/MANIFEST.MFMLK-. K-*ϳR03-IM+I, dZ)%bµrrPKAorg/PKA org/gradle/PKAorg/gradle/wrapper/PKAzZ -org/gradle/wrapper/BootstrapMainStarter.classVYwV˖#DgH !bSJiIh @c5žv2$ڗSz?B;Wopsٿ;#g^O2Bx[aKXpA .X%˒ e\U &Ẅ)XpCƻHXp+|y/wPe#+!ǼC(H8=' f Ni[.יuM]׉JYU_U-kDJb)*$ Y⁥nn2+q4M%U3Ҷj̚/񣀣ѵ]UHۖff]f&4:V< ./EQY@˽Z}4le'uTJj1jN\_IձrvHz|;$󗘽a X,py$9ؘM/yfLq[YikQQ)Ke#,p)OżTGd`3¢r,[Yh.-(8Q3 ^:J=G]ܓ+(`* ,q_l0zFqjq8~XlC`5eOGX@.]O|Ϩ^Yb >Ǩr-? BJC|Mݸ(+o|z^)Vd;04z.bvD`".g3rJD3V+?~0?{g7y}^@O4V%\IZ$S$9T.٬qf ]}UA!  + ԏ{e n]*d0}Nε0mJ-kмYqZNeex45yz7n#4b$wѦ9z'e42l$ X)L#jSCŁv)I@EBM@ mg0}y[vvDc:t`րC܏c*2&H+?'9TOˈ@+WH?TA XR*;C]$ݝ']AL &w#,G\7 xϣ;NZr?a\VmˈãFX&1 cqBJCl"[F$k'UBmk$u : Hd VcO1 bᥩgsSxgL 'x* #_/2a/O0REϝ [&H| qs6!0~tMu4YO?G'N";8IPKAhdf#org/gradle/wrapper/Download$1.class}M 0h5Z+v/ ׆p!.Mˎµqd#c-l̈0b\7pK^\dNPKA&!org/gradle/wrapper/Download.classY `u=OFVH`b- FZ$`l#ia5#r[7C4Mܴq>Nb'NZ_ N-n7m~6iM?i~Zm}}{Ͻ).HກaTgdYټ,W/_/ienxbWyu[ߓ˿/???13y E}Y_Kk[7AknU3rN_3_RͿd|]6)/mG7to؇t|_&*HT8""$4]蚨S.juajx& Q/t]M5ѤhD.uFj"n&]lͺb.nV6MlĭM^J@xs/-fYSzc b9& ]'[?a2v@mCcOT ܙlrJ5?o{Cjq5gSs;e931K:3\6yLrހ奒#;J8TR Yxʤ,/X~fe6Ge0};^t=%l;Lw0܄$dέ oRq.s MNzSijs3ޔ-0.`Kc"͛Ma&|dpʹݬw< 5S9U2϶vMtStiCi )v~ QѧBN4C.IeGwc#x xw*kb)L_gW}51`C)HŞ\mR/3 M511$T E D ^.هM1"kb'Ę&b8)M1!LqJ,)ބMik3wlβ@P*mCO  葓'G*ADg{Cms~t) D}7ZHQ2E+GQdFtEGS6ӃdK'+G|_&Rʩ'Cl~ ϙbRrJ$LaiE>41cYLi,&69q>RmE\9tʋ*M8HbN: 0W/+6UBQ9{\k2$Z/Ltis?ZDori1^H9dϳIWM:z1Ϯd:~4wC"ʹ&щaهrAޕrFnOg 8.SCn84\4[6mLq{{{:ں=Wk _dHJNb^:ielK%(-&ZB XU[2Jo#mi+⽱ZF0K#Ojayɍ R;HziUٝ&}A RYuemi\,gNILIJ IcgX8. EI))JLL6,M\z]oˎPR$0 !l9g? s;%caD`?,_ |+k".]hAik/mQUl){k{}w%HUzRXh^\mn[DU[UTu^E-`[U- VJb-nC؄J]l.YJ:#zusM띊o@꼁,֞\4kx&{_Uߜh樇}:{1!10hGcD02'qO8k~ԟ#k 8/ q|saR75+x~;U/.B!5(gdCxj)$G^'pb/*Q>T GPx?>@,M/\bp#|T8W7a(ٞ@ p#=H p=|,aE] O#*g_,*S3s2BW/Qy=%ΆBCJrO0lO(<٨+XCB1| u#9G IR%̈Ad% rݸ^R?YU"Ta^H?PKAN/ݡ1org/gradle/wrapper/DownloadProgressListener.classu @E+jDEкe$B~҇)̘֢"n~<0y&\8VfsE1a< 2y`+Χ0TxLnQD1Aj,IX(D8gQkY?0&/׌##Ѕ>v~PKA~0^ 3org/gradle/wrapper/ExclusiveFileAccessManager.classV[s~>Y5685D2؂@*$FT sqB҇xVKOIKڤmzJچrӛ"әf&τ\3nMgzQ|;}{WG8:¹< 5Lj8#) _ӑ9yA_R'u5\T% /R=(4U qYV%:`+:ᴣ:Q7tPO@u}WqMu )SoD)H=喽J$m׹Uw45.^A>PoeIKeٷ1#Y"Y lrTg]+̭MTI#=+I2qvŗt\fdjlݖt=^DE'/;JK[֑a4^/5Rp~00Fkho <NY^!OҼWoi557Y6].p0՝zYHj$%=T2/iG I'E%)4Pĸ9~!9ӻlX~glDiNk0K17k^&gj_ nhwpR- Va-a~n7!"^ 3ܓά~ENY^nIgkI-UzZ&H`/c!MVY׈/]ˮvڵM35ϩ l]/u6;7`v]-%#kD͑>H6 9=#\rb Z0͠)nC id 0] :zUW q ! wӔiS;?Dl`LJh{?T>9s܃&<1#5|_~nDRb\_H ,">"wq@GΡ;0N 6O躅Cj:} nn{t/r K2- v:\G/Ug}[qx[{\"h\2l  >{*yR 4v.ebT.ٻ+`9=34HTM| )׹L"hgOE7s-6ep_̴kH="sEVb˔z5UVMmm|G0Z(=ç8}'Ei Q}(8!v]wq&2{ 4zmOgoG#|gmH"aAG5=R+ȃɹws??~XªQx)I)`^F\F ṂzQFRhMK K [A*_ɮoANϖvӟtp854˰ZsM0ݍ+e錞K{zahӱa{jr⿅ >4fڦ?(06 %L7k}8e*)v0 DqZ5*>F]m4xqNuj}g'-mZ0Zjw䜦[b!ڋ3)UD0A\>yIA$Rf MxfFӴ*e]ӫxwԯ x wuH𘗽P`{!!}%nx/ q}Jhͮ0,މ=q@{,Qzii“G7 !8CH3 `_[(`+8$U)<$4OZd4}/z@:CYׅ"D "Vv I (&%꿮)[|SW/9s ,n%BrUv/PKAj 4*org/gradle/wrapper/GradleWrapperMain.classX|?}ll0" A/l؄Ա dlcbHBE$Nfu$It&3I'I[ٍδN+{;˲t?޷^Ï 6؆UlKRLi%\+xUrj=~kNEĪToRf5-*㭒vC}_ ǻ$*= ޫbާb(xP><~|@E>" 5)a?xDc ΪxV|Tq >S*IgWEyz2/LHZR-WGb kmQ-6F^y%1Wk<ǂ<2*5[qU<((#p.<.m {9Dev4vG *Z_x;jsH0de[׽΅ .y:öOνyu/r. ]T9x8S;'e, Wá2JRilSƶh8iūt--إb֩p!Ů8?r/>ЊΜߒsS$7&drM-2yayyIR ³Ϥ @t̰Ʊ;Wh[":xG0zxr]CvYcN/s >gh[Ry.ϴS|l=1Ѹ)/@nE]6}y1xuss(c;6b7f CX 5Cy{y +I!!kqAR7p82D1B,WN㓘i.) OeJx~l`P(BWHpB)K$ 1@k$[Bhj(Yy7O"(7K[OV56Napl ˹VLbBSX5O`.rZźw!u-ݼ] E# ;тt5@lAI\۰w/Vƕ8vww#|6џe@_2 9nDX{a"$}~3ɥRP( ^q8i7_on5uh[낚@3b@'ElZMTQ(U]yUNvMο3՝]_@XxY_s% l(dדǮ6ǎtt{W56f)Y\eCEd [&pYh e^,Gmx~$3κʴO3&+( lA`Sg?V[=Y0xsi9]_]f0K;DYtn|f]:ۙ;"MigS;]=1vK<4i zY}g}%je]cFz* lLL5L&z9L_1w1uw3mČ^ݪ _I ΂ hqk;@Y{A͢_z39E ,e! $w wܷ+0H0}uSJ`IPKAXs"org/gradle/wrapper/IDownload.classE 0  ^b AP^26J;t>;ɗ|{z~+%5O&WΔ(a_4[gR#!XbQVg={}1AYCX'R5c/J$S@pP\mKulPKA6"org/gradle/wrapper/Install$1.classW{W~O2fZp[hF, P mC@Bcu;l&3,jVjTw%mW7ly9||;9y߯` [ C22LɐO"3Nj(&DAd%pJ"eMb$> /H|T8#&g8"yGEc|':I|JO'pAğI\`BO%<>#KIBg5<+ ]g v}*=h٦Š)㌑ܬw+tN*Ö򬩊oθg9lFD-:'b3DO7zl3 =e'yY0cFc0m8" U٪]-zc{.R\v^ozSQSf}er̔59)1ód]Fe PGl\6)kl㾣tBu{[gJՏVm"ogG|p=Q1Ӧ/wLPv:k 2l4[n)g1mx+=,R}ZLnd`;p.o$fYWn:e%Ҳ1~ţ}癎bHcn˛aJZַ {n>d.[t<n_v;q`A|C&TXJ.xAǷmVq{Zwd=u|}~ C ?2.)Ķ cyQ g~_ᲆWtq:aN5Ẏߊ߁^=lڦ/NH*&kx]:~?h?U*.w&xKl~R,'Zk~bdd< w8=+jF6E_^$-`y+$1cҷķï/ݦ,AÄ"{zZkspyǭrs%q*v74cf]x+pkm4ruggI,6z2 Jvm_:ڶ'W-.(C,U 1ǶޮCgl >FFǻ[R4+AG]-ao߰[+J۪M<:ҽϬRrf("S}V`rn Mt(7X7vGoZ3snK)vWfWru<+"l}5VĻ8o<2f0 fGZt@1y*s,:2}dRYD3,bT|UhPp^z0 Hw"N/g|h*Z >wTST*UqgrOEa}h:9=<KŮᝊ=.7ARq%DS ϡg^yU*x7ihQwo9 L3wyqcs\;]!}k9#MN"rd$yT ɢF]aߤ߱a&^J2xX ᠪuU/z 2y1N0'Y4dySTZ=GVaR$CWA(apL2*zc5^(%QJƻB_7ՠ̻8ǃ U4/PKA @$) org/gradle/wrapper/Install.classY |?'ͰB#v4( ! a1 $w%ΝKVVZuwUT4*uZgk}}o{o)fnnnAofs}yoO=KD q@E4y҄Kx'~.D!(c)d"MarQni҄) VU!Z^^Z(5Өy .MA^+!:I>[By<7ISͼE nI[C/Tx{Zo3_q4 !j6Cۂ 3. NyFEuh͗Jc) NnƖ)w)n^Y'^y]!>\+Im .'F)\߰vS-74ڴq]04]ҫzŶx quxf=.W1Nbє:;-=5{,۰A F=hB0X.ݺU cmQx_u<0u%`6FU*Иiصf2;m!ozcSz#nݮӂ:V7SGB{ڨgRreOG_]Ѿ3)YZ2VjTb`CNQyDj7<FmD`ԅTӼᐭ_ymq hZθn,c F0ɴa7TXqadRPMmLf0~FdGܜ#zuŠLG@ 8˨뭄dU)3RžFNceoEΊ8XZn?{JMk1~%b1=(S"axLn1ؗt+L$uY1OR$S~ylbhO!_fB9\#n[}_8vj$GdruY焔)}Qx*fd1dP.J|Ec!:=ݗJL&$Ui WZfbi 3Pc _v U~m˴By'">*?Qi/ݣ߃U)v'kKR/sbz֥26Èk [1S[1m46߳ܞu[ ,4XOxHL#*=5)Z53 X#%nAt6FvZif[-D1-PShPZԤ!;ctmW8 dZ@3#GU>ȏ)Oq?YkYzN*?%oLj3)} `,kiZaaC=\i Fϫ1U;H4PSUރbA!д՚ʯ˴ٟ^1%Rш(z}Г3MNp:"p0أm@I$TU O-eoT~E Y;¿P=A/!KT|̿u#i)9{H[?T7|ʿۑ['ꑐGOCuܗ3R46s ^js$%ouƞRy֎ӎL vN+envgNX9z,2 SqIp7W(8S.2p`~v`^z `r۪Fh趔?paE ɤɹ%̷k/K K=qr@xt9nJűuCrYC)3q> t8i9Čm{<)L%e0.cƥHQTɑ=I_ۖLDS!4rCe?EdCdn[rd]I}yr/#v=J\ "8wDTqrQW%7N-ǒن4S6&ݴ:ǚKV9o'VN(fPQVy8p/ZMJ_IoP±ɹ :9FIeFtt d-7TZ2X䀦D' as6H[ JPi0G0ZT7=\9z$UP@,Yhhw>=T 9i v"9XA zu꧁NµE+~X`^Gʖ@/>FKOKipur^E)0tįopb >xG$<鬂@5)'ț?yt$[ԛxJr`t 3x4W>[[hE5pVAjE8$C[vT fCfu |s+m!t6 'HP9=KG~p+eM1j⽜ܞCcyzM>c ,䊃TZ^r^̓tNkm 8e ;N: N50Vcy9q4OkAjnu2?@$S`E̷&".~ڲyEhv+^ lG3r"#qlQ{`{a<bD} B̷!{W#GLE<(:ޏqXbcp< v=%Xjl?/+งfѫb,z^kh'ހOwy{ӁL?u:V@˳Cd̏@c=%{aZ(}|OE7~Hmc-V@`2! &˶bf'`)Gu\^u._Q= Eɬ'Ϡ؀9RV-8\3;5PKDfv G:XYI4't8pb k "ޫpd Zz8I:GKy=#I'32/ (zvv O No6λ p޴a aQ-sNAsn㩨PKAy0Vorg/gradle/wrapper/Logger.classoPǿ*1pL EWo1&d  f&Ki Xs9* OHfp+ۨ&Q,D];Bܕp A`w 32 I^m{]⯧>wީ}N6Ǫc;8ƄԵ]u`r壣FQs5mP힩TT-]鹎a-PDս +TYb|Tuz|<[(N$&cbXC3RWu:+mf=mdϞ:?2RlDLBZB3{/AQHGB< 2og\+j/>M\> {JQV \ZWHcHXŠ\_)Gt } РF If:B~@R &~ 6H#J2b&J9򼉖=W-ĝPKAbi+&org/gradle/wrapper/PathAssembler.classV_.HZYm,c;`HeL!mM++NB눕WzM+=Һ}֑O{]K&A{7̛p'>ENs2|^m_1$R_/1UeFߌ[1|/G(''_V}?Ppl'Mǵ͵k,Zֲ {;(8uy~lrzjuiaj~lnjuaq>;sYA|~SuY0Dr\rbPpdn~#[%ǰ6E7c2E&"^RО\V(vtڴa/kEC e6el Kv!ܲrٰ3s1ƨ6iѱ~hple)fBEwt-pl}-ʹt ;ƌ.DF4v0l\U`6콃8[d^+AC-<|xp`LqJ^Mw;7kۮTg۵q c<ֺQ8 &66K7-װ屣 %K΂-,gY\"Me]AFǙ!hTHʃ n&+}_ xTqQ+&C 𴆧0!i^佖5w4<Wx, [ÏE')hFs 闸;p+0Y#*~kO.&eBr(⯂hGV؝XiwT)VMl0U 4Fw w񚆪D|Wu67@ 5cƋWa3ɛhx]0]֞db0Dz*"[QH`fnj<ƽJf9#+~[w:()Pni!gn Ca1 XVk{Qp'dlX ෯PrPXR.=d$$ΉdeRxcgq.ݒы:pH!vkLf[BE/:Mm56}^ndgUt_`# I?g}r{l꽜T ʫ%1@BxJ08ݘH_C[?Byv`I$% &0/=oUʥjϤ5D.v_"RCWZG8t8Tq΋zPPdWRH ];pzd(8JFO!MC°t=ۘ"=~^f.x&.S] 5 RxR|2\85iD4%wÖmԱ x8OKm)tlA8n ^^4O2SӴ#㳞 : =W 'KqhJOkalq8Ub4^"/YwI%F&d t<:{`.w$-AGX'^dW{hB4z}9Xeu{d$ւ {̠T_FVPKAdzߒ= 0org/gradle/wrapper/SystemPropertiesHandler.classV[S[U6I8!ʽ4H[[R/H%*$%^!&sIoZ_go8ԗ0Ȍ}GN"dk}{N?o}Ť)0uL+#o`VN"Rms }h¤Q9.H(MT,Ixx+Xk>ƻR'A#CuZlyz"Ls}-D(؆ 4MYfLgEKeu 4k{ڠy0hvk].Yq-نn'id,;JFJmZ:ۡnѷm3IS8(kX# ӬcBZ뼆cږ 1Y"L0#daz'2swH-t9ZXQuE#nk%?>YnMCyE.LV×.91a({7'\mƀ 1#ajNfgs}☾zT<(Z2I b\Q TZF" CnQrėcl%ήSMfԆn \mNfo6=[]rU&IRS&dH# Y/F戠]3お/𥂯T|oOR-X@-mn+6Pyds 9vDx)+#tLbGbT$5\/۳^*x;@"kdPbYNֶ|xXfMًeǠ*$LTaS?zeZ*g?e̪$"OfBX:]w"5Yr%.G5\k p82z ϯ%ZBG>yH?e}GR-0A 0Q\,HCԭ57GxCC!5FFGrh׷u-zPDZ5}?ΡёxT~wg9t?z=~9]g=9V9z Gq9?1q4#E;Yxq䱕 _Yg9Q'-aش> aWI\b>t.awCtGzm2)p+G%qg\k,s0K0Q,Nwڵ|_F^{_PKA ^F-org/gradle/wrapper/WrapperConfiguration.classmOAփ>"!3 U1U H|Cp#]5Si"1e[733\_(K`6 q?<7yBi~yVb޼[_2DO O[Bxko 2;d4q.wXs $.{{yxsx>;Շx0Ga|O>gـϱeċ"ƗWC_g3! c}I7M, eq5oIh9ܛO zG%DNΜL[: aI/Zj:J*CB53uh>zh|=~ zSZDN.FA3-]+JXi^\g'+RGhKF'$Q1-}99eٜyT t=w ZCMIzNjnhvŪsV˔,YOk V'HQsTSgdSL8ZP6X-$Htٓ=g3Z8`Msie ֋H%1u b[ly (%3S Gv ("墩Yi9:3lʓa=O3toP!,P4kYE;UW,!! ɉR/U.2 S *{f 䣃mH mwR(ańm [1C>/9@U)vq[UڜPeϹEjj)wG+1_*QIg$Oې͔φi"U"y[QS[_]2䂷F%jm!V4My/0e\\qxͭ0 IǦh<:ID7o,nf "Rl` NpC ; S۹QVZQ*xŘ').) ~Ě$R5, 3JoN4'6FIOYLyT/"4[Ϙ* fM>}mrY(nN3>15ӭ_)g_oW%lU̱|qz`c!Oc=PG)?+ 'Ec!UCFT$|nMGf/>k٨ufP^[YO|ܮcK5<1UT1_UME?Zο[e5nx[LN.z c;߹ONQh tfZ"[vs?*_bͮxη k ZŵhD22܋af&g( [#F$~_/{Xɮ%|nq1i@ D:l#^Et^Ctz==z#"ܠi-f4/!}'QO+eFEe-М܄} rBHy4FKX<( %XYOG#'ѕt?. hg.^F#-%eWwvYBHhBem` Cߢ/42YPa9{s?p# 5ϕBy9yEx\Ye ZpՆaEjw I7|ZU c:tiw]HaٲzQdu;BrQfظ&DV\%6# ǹƙun]s̷X3ػe9H%ҲIk.=,ȍ-1ak^1蔕)9(id0^}y_7бp:%`i6t*㰊jBL F vUWm_ImuוD I8H7Լ&%DDՄ3qONB :ACvsπb0\8y 3CEw&T*kY+$@B!K͡5IYF6VANwh`+&XEKH,έQFV# J#hRq +dAy ~#TN*)oްOOSxΑ 86'??'k<ų@zV`PKAy0L ;org/gradle/cli/AbstractPropertiesCommandLineConverter.classV[WUN2a`rԄ IєR Mik$d&L0 ]w_ۗQ\KWuIH&Ҕ%Ys}g?!>񉄋H厄Uܕ{K3 #;ul c[|b_J!7ØWˎ"1 4]W6jTѬjA5( qYTSt)p#kDTJ5QhtMhoFM5mMVjUK쪡S5 eDhfk0fC%QQr"g^&I]VZb*ZPr$iж"\<50jy6ZΒn"#iLdC+LF*ur52TѶRkZ )+>m2a*CQl#6zDE U4vryw0oQTE<洲nux|6zNjnԢzY ^:^79n{QN^dIvew' 9ؓQƾMȨBaFw@M lu{ ]ʈbعJwHrv2̼j<^.T߆ڰCzs.k%ꕊ uRj9M:d|",}@\xQ ک7X9|ue0 ޷. |4lk;ɼIi ߮?+&Dw^z BuN2#iZk"pOag$7J(Bڌ"cܯ!16Cfkt[¤q0~xȱc3B( p1YITةQcBW~ͿDcD y f@]tӻLA^%6uV18(c]3 2gy=ݴoa̝̩ kqv>PKA'H g)org/gradle/cli/CommandLineConverter.classQMK@}ԯ'"4 FM)HQ ޷lI7ݔ6MBya=t$S l)8A {Oyb :˄3I5' JXdT"qx{a/4OR1=Q615 ڹ6ƇEWbRh{'qj]4 {wǪSC- 表V%:m7rG4gĔpWBWc1d||ՠ'2œl!_9| z]VpgnNn|q_KoEJTkZv0F٘7uV<'hd 2ƵWpy_ABNgD&x9tN;P=cW٭LXw¼Q9q=@!_!g}@N4 d $];s):XIf(}M ia%`cv'zhf#Y Y eI:Y^^u/s:Dd )&x\"9~I~Xkc-d^V$N^jcMW9,X$?/|W S 9ݢ&y vԭLaS9|ᶟTyW.8qX[7xJqqt-r|q9Dw !6noDMTݍME/7{Ƨh4\p:~\$ƭ|^6'_Di^Z,jמ 9Zo:PKA2_e(org/gradle/cli/CommandLineParser$1.classA 0EhZ v庈kCPEv-iIp.<S\p>?fxCDlnmMJ]k'iu#0BWՔ!f,By@wZ͕t!BI]#HI9|g|{ -|PKA:< ;org/gradle/cli/CommandLineParser$AfterFirstSubCommand.classVNQζ-P*+^D"ZP.RZH-e el| *񟉏C,̜9ߙ=? e iKh,lȇa1">׌I1.⁈ >LJHc)FV zӆY ^T|Q-E_Ok%Lk$lx.96U6.lseHW+}Y2t^Y9%+VdjNRI%QWC'P_RS C,Ŵۖf"޹aOo(o9g^HVK=&{x 8q$23v̼: V сK~.31<1eaՑn'@i*?:wljS)bu֔S3M[V/YsU=197vS=i {,LjLR)4*1ܱlqL@\P-!]PJ%UB{wxv36B]^_<+yyEς˷{|NC|D@{]rE#H q9L$cA9/T#FAw4fb?qq5B0GUnwj$J"PٲwJdhgt,Blv'\tN KnpqGw pY ߘa6S. 8fsa;jE1qe^\$IWBQDO}HK`+`h+PKAxڤ)3org/gradle/cli/CommandLineParser$AfterOptions.classmOP( @ 1|Dd^\:ݭGBD?xnWGe[ 4=s{m@KTLjHc$]Ŵ$f5SQT1ϐ:Ւ-,ђ̚ǫeV|ʽU]9YbHݱew*3$W*sҰ[߶g|!`ɭp=[1 콗=yۮF"<93Hu~Z7 CW-7.j٢4Ő^M&6B5܌J>հՁ!V|mlnkSM ;/Iu0Š9*2g z-[cgVEYE/4 ZD6%cTq+Ef9Q2LdOtwaDW!ΏC^ mM-.`^>=^ή#PFi=ERgd#i @э/H쓪Hardn#S@ I,4BKI|lRE YiEG; aϧ#J,4][\/r^k$~H AّTYkѐw|dnp#aIJmRx`900@^gi!*ǵ!\!&>1u68 Fta"oZTRkZRSkfynVCr^ ݾp}&1owr3 Қ1fYwr,YQky( UiitaOahZMm44-ytD' T=> ðimղ Ol48`qn,ZtF5U-ݨ-3Ի|jUt]ۙDrM ~tHUh8,\L$Yt=MZ4 ~1EiU(MDvaf NaL(Ðϸ^KR@ :izoiAF0اTyFiM&T)5*ɒJu^(%n(^Ua,F~D4As?J, |xbhÇkllvmq gDՖl \.RjX y2]x.³x%/KEx%¢JxMkki&Q6вMU5Xj Էf>n*ZMhFO5]ݨj_%PZ" qELjYDq$R`يJCҮ L8~vOI$ە>>ikvͲioF;iI۫uB޹YݭYe ?stn8_}Qȍ|nff_#.cHgjnR%l7Jbbj>FK`z]&DsenCo1UTfjWf1iGL 4TNƬjS1+vC Q)pRV O+;./lTl3ȰUsWr2#2"ehࡑ&򰌇k X4- :f(c5n[sBbgxkRF5e4QFX2!P}aRv z_m&h?^աr"3T+vVRCJe|ݏcav5Jޠ8(E=X_BѧXA E/`N!qDFeEw!]9!,:wdֈq)x$| CJ8!c|"SFsG2akTiV_5) KBI[>2bؿƐaPb1+.:hbuOV;kBf:5Nm7wu Tμn_ )`= ]+ :KPnV5 "+$j A-џnO%Zuxp)u `dשXDe5.k[uʶ=: F}Y}2:z$b,m'\25rb{u+2ߝ AM]%׼.O}e̤RVt+?Ma̭L*Y,6ɗ[M#ٻE$=d3'HT{q/ڛ?aqU 7:9_jwΗ:nHiUB<s!(@Mzya1FP0ppRfK8hlq$z@@G8Q.ZY֏V|gƾ{_@݂[>J$<XxrWp[ 4.AØfnӼzs=>F@I#?q '`7۰q yO@0JQF1O`3`~{ɉgu!cNvy]<#MQP4\#2򑔓b @b.jYQXZ]]Aa\LK|Q*We(5͖8R];)0]⫈CZy?iòNqsķ|?P's@<_qVsw"pj&Rmcb#n'g ;=A;fNl(/B&8R_iĩIQX]J]T™uy"n@dl D=N^PPHE4Zp-}Lw~̧N_2xPKA9K<org/gradle/cli/CommandLineParser$MissingOptionArgState.classmOPw(Ld !·'`0W̚o 7i|!~?ܶ &9c?Pª:($IQM)̪☏x#X`~/tMqd0 aʊ]tlm[ŊhpQZ AV(ʪ%,C9 85gH6Lot2Ru0潵(Z zXR,zR)e&EV=^=In+M(u߱ @lvAt#q*1r3@m&Dj67} O_ȇ9u+1LąA_wvR2vfVh@eXp2& b{0s3̟ 0}NXwlS˞zat Sx ^, J#j$i 0iOH3g ?'5B (y`$X`s$u!t9jBo-{mp}mIyF)  @a ' [$W0 &r~n1ق&[9fp-D}=DQOW 쫏3Ma*<$hа#4*louJiIh8%1+aVU'tPKA=org/gradle/cli/CommandLineParser$OptionAwareParserState.classUnP=7qqI(mXRҔ&6.,RT"F˧D%>BMMH g9;s?} $1AGN>MisM4 0ThP1b^C *UuNl9&Cf)x6u*>30k 1{pB!c*6ٔ [5S<5"#e*"e<|[@sbgn%`&S3!a,_/yNX4 O\4e}Iof hZme祂:NcI0N鸁q:nbEŪ53,*oseq!uzwF0*WHS$ h&{2!d> b#1ReIXcPjȞ:OR#B00/Q1c3]Z{xb]..c"PU2>!JTCdW"suWz2C!unjd`sRWFpS9)'rVYpE_f [$dPKAJ7org/gradle/cli/CommandLineParser$OptionComparator.classUmOP~Q6|Aq 2@h,`2C2yIג# _FgQ]lRLsys=_BA3 ţ2HWȶ cQƒ',cEX&CgY67yuA*9&@vgo=4CfX>TJ;1dv}%qh mn2<$bqZ5-SY\#lðE׆n2&Z; *AG#2q:-0p_6Gfv].z`#T͸w`|Se3,KQmg][un%)zt-6Z:Mfcu  21\A-GhAdl4{dxfL>?2;cOD RG^T.P`AF9z,uuu̫mcP5}ʳ, -r 4T|jd^@Ԩe[n%nvt>E# 1 ӳG(7 MkH- ?Y eH|B9՚tƥ('f?0~.aT!V&˸C| ):=ew?n,ċ9fGL2 IVia3.2M!]%yu7hQ ,$MON PKA#t8org/gradle/cli/CommandLineParser$OptionParserState.classR]KA=YvMMw)F)n_$"h@ <6IfVf'%?O>w6 ivΝ=w _|dŦmTҜ1d{mC);BNDfQkikɄaXq2ЯܵRB7"$(XAy/A7A#>;:|(F ͹=U&KcEZP|yq- S+·\U|HQS$dú݄31TMZ@wťK) sAChU0^]<. uʓGa(M"%z.업cg"W=d1fIٔd}F6,O).a9Ţ,se,~Bٿŋ#H9 0a1 Z|ea%wA~刡vTWabH:arYXb;_Ů@S,븉 Vq*1/E_mkǮE SgFo mgGÈQމF\nrj^v;Б5Ts!G?.CU8ˊ)Ӛ!zsmRuX2#M+&~Bf6)K+qy% }\[HPKAG;~U=org/gradle/cli/CommandLineParser$OptionStringComparator.classTOAfvۅP-"Vh)BJIFL0$zʤ Yvɛ{&ƳͶR1{7o뷏jc6lX(Xab^/-,mE eT` fߗ #u筣={.yF~C"P:@ HN(YE "?`HGmdiҞ|~pF;6rɣj Z[hr/}ǯz`v¡GX;=A@'Bڞ8̏c"Ob^JOuE#%㱐C]!ͫN X|{lc쇒^O Xrn~tjYVB9faPJkOSvq;3̡P^}3ʩrhIV_ u6>?#T'` 5z31-P:j췚GBL{ݿϽALx ۋV,0L_"W`n 7V i'9(^LX&3A)*LAڜehf90k F$p؄5ߛ̶Q hȇ,I[3,=Nq&2|RÊisF wYn PKA]4n?org/gradle/cli/CommandLineParser$UnknownOptionParserState.classURA==$a2@(""UZ`$SdL__\H*~+7nܸ_= B%q}q[A :0A0iDq5k rCw˺& ezabq [vJ%nWL[F ! ɕgY6rkk X>O 6E=}9A{gN)Ҧ>曖.^]S`{jHO~噎-pe}^^rJI=R7٠cSޒ{2iOcinی1^#r޲t1b7?4yvo=)L^)aFZ֜[c9*z'̼n$T BAa F&00 mGd6a4j3 C'/n{._VƵձ+1DX UR%H}4+.L_c=:"L32}F7>L`g%.dJ\)+QLgq.:K^dI׵{""¾j4e8 ț#u`@Ts䥓?|pP{fr mEhQr!"XOٯUVH/.\ 鋘6?9R?SͩUÕ@FcH:U1-NcjXc4Uy<blfq=PKA\oY(&org/gradle/cli/CommandLineParser.classYy`Tչ}Nn.! DH${%B 5J"$70g&B\^ںj5!Ҿ֭>[^u-ť]lO;3I.&s9oL^虣˓؃mxGyxWotw:{ ':ב?k||<>RcZ.M:f/ ~G@Q= yjP)j%? Sd&:J.OQwRQf,1[=J Jq曓'sePbur&u,hRc^*2MuBGکJMt@yWV, NYKt9UNSae,Wzu䊀Tz jF3t) 3Y(d&^ըcfgl&]Αsu i&lelyjU-HĊ ؛E#X(ҽ.׌hL֙q)"P"t5HPpgm>?7q67kM Km<] &(j [gMhdɸ<dn30MLh*ӔC]ԗ@s7Wd5%Zn6c"k&6=ߛJhJ T4 "Ih唔Ǻ-h# k&kb 7lE8L[_UIH' /'"CV f轴,q~fie;UE0YX:#b"rVZ1Ej C Գ'l\bxϊ$b1^V\ѾJ@W Yb쳁;[ęeg)M|azgy@`]ӷdA![f{W>h,]4:숻 X*șH}(ܳ7P=[եb2{Aku\gGPqHmNҨO\R͎+/Yhlb(:Qepk/R"+dszk/Iqj7&S/6BgnC._OdZ4i7C:yXA)nMz ~4hא3$"Q+ՆĄiY=ᡙm:UWpծJCbC.~C.ˈekIU5\.W(Or\Elնq {Z fjzX,3j wu\kur!VA.33r!7U|r|N[ UIqIm \`R%w%fY `$mq!wȝ);ku&39Ico5%wg 5"? 9xH1Kr&|ِưڐUyK㥑sАHԐURjGkLxqb0Uk3AO{VmS71qL/^X_Xevw[:k_Wn~ȐU5ԯ3#h,f䍤V:xŒ%= <"jMU;&)8 wD# 3=fB=XS,Tg5N5!)EN*۹򝽗5)Y]<.OЩ{3&$>|BǻZO&/ނ%2(>Υ}t~<&/$w}ǧw)1Btq.1A*N ;_J]V~*xNܯ;c;ėi`J] ~Yei ǯ9{xu a`_fuvu%Rת2n_gcR"A8>Q͍Ǔ9WTړNQiavkÔ.M^<"uAҤ&&wtDru '=;/U~Ӿ]e@"Ėm%ؗ;=MRxž;c)D#ؔ)3~n;{nvn s 3AU-:YR_3{ᏁN7iY5C[X(W/s$kl6w|*!M[:\0zUL `='*{4|]8M~&ɋOkvR#C_I<4k쨠uZV0pܜ75C71ӊbVgZվ~Lѻrn|֞?^j,nq|BޚokCV6y漃|RK2LN% Us8VT6ciA~0!J PC_^Σ)ṣrFU T7_]nQrYa,U|Sv"H=-\8PT7?҉RDE--*O;@1h ۰mY;w;v)\n|x8NKfK[##c",;O *+p܄<<_~yʫ+wV寸A>99,O(^~܇L4jf8šx||_.q!aRv{7ssrIQ7@hgu|! O(vB%n@-pUq;E"jOG\cz(,i dۇXU,kVA РU){Χ)E|YmޤDL OL,xsYAn9w SbKdFXYawHceKʛLw#|Ot|y9 /xQ%d?^[<KxU^!l.'pWl.~ߣ|ߎ9(sͱ Ϗz8ن ~̵aBc7aEk)^SI* ^3s9~D Z\ut[sK}6Hp /J!QEޑ,噮AtJ􌥹KqumBL[.e*oT~^%oa&7oP.szg_X=}zlhQU6p~ Rڽb絗nIڏhoa|UMoױ1}Ҍ?Yrs6zLd+G0{p$`ߓۀRygcan*lXNjyqs\1%rsԫ2s}+8~xǫ9^#?M#^?PKA[xn&org/gradle/cli/ParsedCommandLine.classWWFDUǎ&U8e9$x&M)qIpZ k"Ogȉ[JKЅ}34 &?7oW;3Kָ1/39;|g??0O 3Qq)ģ"Nݩ&I\x<#YKF\e'Nj2/%Ўd|9nq|E_~Uh&N~5X~]|Cq@ěxK|K<-ofMrdyֶtȝ:XZ2 ծZ_GeI5.ȔiZ(i%}qժh sqQ5 Sr,9UfVfSƝ-KZJiWlKEsgJ6nI)5K=_FHLMxf5 MBzʜWKgTKko&BM !}:ciRvt*݌x&H;b/&|f $t4Ұ\Qq8@ecKgEY+l_PKUƍ¤ t2VeWvjKj:p HWVZ9٠+tt˺,Z=6kћիn 7k'u;aJڼ÷8d%gG\M Dբz5膹UOd״IBA<&\K:@CfiĬYch&i6 ]'ZY`>p+o(8Y:nY XGQWwe|W}?%d735V?6FeTs q83x[S/W&SwE>F)L+5VeF'1Gy݌`RͫW5OޞJo0LA E.ȸ:V61'!unQúwTK7rXr̐[1E0w$DϞCόm  kV%8py'ЃZT1жO~0͢˚QX|l%>ow l (t((TyN,x0^*QpKWj'DG \! ׇ6!$>!À40d)w[@bbu1NbkP$LװMUWlV Gn"{$ G^FHbp,>"]vqvοVw΄1(|v3A?w2{10]/~ ^&IC9$Egd$}uvNrg5&<gSF2#SӤXLBi ,A|;"AmȜ2BXiL8 G:m$x0$aOIkZPKAWgradle-cli-classpath.properties+(JM.)**+MPKAbuild-receipt.properties+K-*+MJ-53PK>PKA META-INF/PKA(M?T)META-INF/MANIFEST.MFPKAorg/PKA org/gradle/PKAorg/gradle/wrapper/PKAzZ -org/gradle/wrapper/BootstrapMainStarter.classPKAhdf#Vorg/gradle/wrapper/Download$1.classPKAo:s@4:org/gradle/wrapper/Download$ProxyAuthenticator.classPKA&! org/gradle/wrapper/Download.classPKAN/ݡ1org/gradle/wrapper/DownloadProgressListener.classPKA~0^ 3org/gradle/wrapper/ExclusiveFileAccessManager.classPKAz\Q-org/gradle/wrapper/GradleUserHomeLookup.classPKAj 4*/"org/gradle/wrapper/GradleWrapperMain.classPKAXs"-org/gradle/wrapper/IDownload.classPKA6".org/gradle/wrapper/Install$1.classPKA @$) 5org/gradle/wrapper/Install.classPKAy0V}Horg/gradle/wrapper/Logger.classPKAj jV8Korg/gradle/wrapper/PathAssembler$LocalDistribution.classPKAbi+&Morg/gradle/wrapper/PathAssembler.classPKAdzߒ= 0FTorg/gradle/wrapper/SystemPropertiesHandler.classPKA ^F-&Yorg/gradle/wrapper/WrapperConfiguration.classPKA(\org/gradle/wrapper/WrapperExecutor.classPKA_#egradle-wrapper-classpath.propertiesPKAneorg/gradle/cli/PKA<S1eorg/gradle/cli/AbstractCommandLineConverter.classPKAy0L ;(horg/gradle/cli/AbstractPropertiesCommandLineConverter.classPKA# GK1lorg/gradle/cli/CommandLineArgumentException.classPKA'H g)cnorg/gradle/cli/CommandLineConverter.classPKA7&oorg/gradle/cli/CommandLineOption.classPKA2_e(uorg/gradle/cli/CommandLineParser$1.classPKA:< ;vorg/gradle/cli/CommandLineParser$AfterFirstSubCommand.classPKAxڤ)3Kzorg/gradle/cli/CommandLineParser$AfterOptions.classPKAS6| <@}org/gradle/cli/CommandLineParser$BeforeFirstSubCommand.classPKA =Forg/gradle/cli/CommandLineParser$CaseInsensitiveStringComparator.classPKAo\9=%org/gradle/cli/CommandLineParser$KnownOptionParserState.classPKA9K<_org/gradle/cli/CommandLineParser$MissingOptionArgState.classPKA=]org/gradle/cli/CommandLineParser$OptionAwareParserState.classPKAJ7Qorg/gradle/cli/CommandLineParser$OptionComparator.classPKA#t8gorg/gradle/cli/CommandLineParser$OptionParserState.classPKA tB3dorg/gradle/cli/CommandLineParser$OptionString.classPKAG;~U=Iorg/gradle/cli/CommandLineParser$OptionStringComparator.classPKAYM2"org/gradle/cli/CommandLineParser$ParserState.classPKA]4n?yorg/gradle/cli/CommandLineParser$UnknownOptionParserState.classPKA\oY(&org/gradle/cli/CommandLineParser.classPKA[xn&org/gradle/cli/ParsedCommandLine.classPKA ,morg/gradle/cli/ParsedCommandLineOption.classPKAA5l| :sorg/gradle/cli/ProjectPropertiesCommandLineConverter.classPKA;|9Gorg/gradle/cli/SystemPropertiesCommandLineConverter.classPKAWgradle-cli-classpath.propertiesPKA>kbuild-receipt.propertiesPK22_mozilla-vpn-client-2.2.0/android/gradle/wrapper/gradle-wrapper.properties000066400000000000000000000003501404202232700266210ustar00rootroot00000000000000#Tue Jan 05 17:01:45 CET 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip mozilla-vpn-client-2.2.0/android/gradlew000077500000000000000000000122601404202232700202270ustar00rootroot00000000000000#!/usr/bin/env sh ############################################################################## ## ## Gradle start up script for UN*X ## ############################################################################## # Attempt to set APP_HOME # Resolve links: $0 may be a link PRG="$0" # Need this for relative symlinks. while [ -h "$PRG" ] ; do ls=`ls -ld "$PRG"` link=`expr "$ls" : '.*-> \(.*\)$'` if expr "$link" : '/.*' > /dev/null; then PRG="$link" else PRG=`dirname "$PRG"`"/$link" fi done SAVED="`pwd`" cd "`dirname \"$PRG\"`/" >/dev/null APP_HOME="`pwd -P`" cd "$SAVED" >/dev/null APP_NAME="Gradle" APP_BASE_NAME=`basename "$0"` # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS="" # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD="maximum" warn () { echo "$*" } die () { echo echo "$*" echo exit 1 } # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false case "`uname`" in CYGWIN* ) cygwin=true ;; Darwin* ) darwin=true ;; MINGW* ) msys=true ;; NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables JAVACMD="$JAVA_HOME/jre/sh/java" else JAVACMD="$JAVA_HOME/bin/java" fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else JAVACMD="java" which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi # Increase the maximum file descriptors if we can. if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then MAX_FD_LIMIT=`ulimit -H -n` if [ $? -eq 0 ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then MAX_FD="$MAX_FD_LIMIT" fi ulimit -n $MAX_FD if [ $? -ne 0 ] ; then warn "Could not set maximum file descriptor limit: $MAX_FD" fi else warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" fi fi # For Darwin, add options to specify how the application appears in the dock if $darwin; then GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" fi # For Cygwin, switch paths to Windows format before running java if $cygwin ; then APP_HOME=`cygpath --path --mixed "$APP_HOME"` CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` JAVACMD=`cygpath --unix "$JAVACMD"` # We build the pattern for arguments to be converted via cygpath ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` SEP="" for dir in $ROOTDIRSRAW ; do ROOTDIRS="$ROOTDIRS$SEP$dir" SEP="|" done OURCYGPATTERN="(^($ROOTDIRS))" # Add a user-defined pattern to the cygpath arguments if [ "$GRADLE_CYGPATTERN" != "" ] ; then OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" fi # Now convert the arguments - kludge to limit ourselves to /bin/sh i=0 for arg in "$@" ; do CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` else eval `echo args$i`="\"$arg\"" fi i=$((i+1)) done case $i in (0) set -- ;; (1) set -- "$args0" ;; (2) set -- "$args0" "$args1" ;; (3) set -- "$args0" "$args1" "$args2" ;; (4) set -- "$args0" "$args1" "$args2" "$args3" ;; (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; esac fi # Escape application args save () { for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done echo " " } APP_ARGS=$(save "$@") # Collect all arguments for the java command, following the shell quoting and substitution rules eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then cd "$(dirname "$0")" fi exec "$JAVACMD" "$@" mozilla-vpn-client-2.2.0/android/gradlew.bat000066400000000000000000000042001404202232700207640ustar00rootroot00000000000000@if "%DEBUG%" == "" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @rem @rem ########################################################################## @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 if "%DIRNAME%" == "" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. set DEFAULT_JVM_OPTS= @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if "%ERRORLEVEL%" == "0" goto init echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :findJavaFromJavaHome set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto init echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% echo. echo Please set the JAVA_HOME variable in your environment to match the echo location of your Java installation. goto fail :init @rem Get command-line arguments, handling Windows variants if not "%OS%" == "Windows_NT" goto win9xME_args :win9xME_args @rem Slurp the command line arguments. set CMD_LINE_ARGS= set _SKIP=2 :win9xME_args_slurp if "x%~1" == "x" goto execute set CMD_LINE_ARGS=%* :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar @rem Execute Gradle "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% :end @rem End local scope for the variables with windows NT shell if "%ERRORLEVEL%"=="0" goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 exit /b 1 :mainEnd if "%OS%"=="Windows_NT" endlocal :omega mozilla-vpn-client-2.2.0/android/res/000077500000000000000000000000001404202232700174445ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/res/drawable/000077500000000000000000000000001404202232700212255ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/res/drawable/ic_launcher_foreground.xml000066400000000000000000000036451404202232700264650ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/android/res/drawable/ic_logo_on.xml000066400000000000000000000037351404202232700240660ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/android/res/drawable/ic_mozvpn_round.xml000066400000000000000000000032011404202232700251560ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/android/res/drawable/splash_background.xml000066400000000000000000000004211404202232700254350ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/android/res/mipmap-anydpi-v26/000077500000000000000000000000001404202232700226245ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/res/mipmap-anydpi-v26/vpnicon.xml000066400000000000000000000004011404202232700250150ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/android/res/mipmap-anydpi-v26/vpnicon_round.xml000066400000000000000000000004011404202232700262240ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/android/res/mipmap-hdpi/000077500000000000000000000000001404202232700216515ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/res/mipmap-hdpi/vpnicon.png000066400000000000000000000036001404202232700240320ustar00rootroot00000000000000PNG  IHDRHHUGGIDATx[hg7iI0I74X >|AA$X/b5-m"4i7c+)xGEQN13&;fw;6ݝs3PzWzWz%0,+`+p2g `/sYyL_ Î&co_}/0çlxIYm EQ?@* BWa@M6=@YYY2h 7nL0AF)@1Kf̘!;w+W͛7r}9yTWWKqqq3!˓y|>}$gϞS# ͛Çۭ[dܹsQc٩S$777eggQ=9s^Zݻ824i$y#ڵK$ <|*++2eʔ233eǎn߾|.]r͛2p@cWX6AGɓÎc566:sN#0O.wutСDpt={˗/W^.\| z/=v۷o߿?~38l5},Yzh<|b>IG:dZUVIss"[lym _$>S J.,Yz$(2)Nb@nJnۊ>g֬YqF<ɓ'J58ÿU`U&OXXXL;wYUE{JQ"! ړ>s2bs,:u@= vK{9n"SB\ /JL> -b۱;@rcp+;@9)~"@-drxե%xT(A&AYE:P V^'*W&eȳh5H]`D43Fͭ͸]# 9q'MzP "ò|F9#(V5v5 @|E+Ê 7/VbxRae -3{ǬÊ k݆mXa%e[nڰBJ ewInL_5,/6C_*&ɭ%;KJJ?,FvFQIENDB`mozilla-vpn-client-2.2.0/android/res/mipmap-hdpi/vpnicon_foreground.png000066400000000000000000000042551404202232700262730ustar00rootroot00000000000000PNG  IHDRtIDATx9K@3D1X4q ML idd& " *.870י骚[3s9q;mNW}WU%"""""""""""""""""""""""""""""""ć""""""""bɒ%СCŋѣG͛7ÇS?yy~Ŋcڵʕ+۷o!zjXn"*c…̙3ǏawgϞGqXlYxA(0"*bVX"z*W1)-Z?~q_1ϟMiӦFEd͛7+"7o&ϟpppѩ<)pETľzO/^۷߿|2:Ư_ªUQ{رcQ>}]5yQ{Ν;3_>i,SΧ_֭[Yq|OqZ̟??|V#Gdup>Ϋ8m?uݻ7k}EO" ,߿Ycr|*"NwՊsҥ8wDE{Պ˗ :8"*bO%W@Dj?Hu߱2urĉ, /_?% 8p ̛7/zgv_~jmq"gj ԩSSD;w .'O< lE7Uw)\ft}%vOi:sp]\_Etj}P1_SU&EdA7\gJ?۠IE zRǠGc)I|Z]vxYRml9I ?E, " c mۖ܌=5yuTt)FX'?E, " WxdR3S"uDqחs=\X@D19Qc2r믃ODfϞ=~"8A\5sW:pWzlwc4|Z<"s1kr1eUΔyĔ6#OWV_Y)A"lgٴyg945zך HI7dŴ}kYW* 뚮kӐ ߱ 4&S:s%4CAjVIIhJ "V HN_[ZעUfꇹi4l]sZ<"Dt*dCNBc0sKNLp-v)A7:ҋs^ziqcIE&RU$6{DipSðI(b Ll9&""R#搄vp7=lkؽ`U6#uWɤ&{Ԥ[YQāIʧ!Iu;O$rܩ$E#"";"*b~͊8.8Y 5#9cR3i0[F'QDn0M r:8׎q\:_|~GTĞ *7nSwSQ{"X:ϧs>ETľKcPZz& q֚IOGt% XCǵfE&DqE$kƟDG4;A؊8HtG k]g Rv6 8DoDWa[bV!"5QizҹөwUDEjBOjӭ#DŽ<<_h#FETDC ETDC ETDC ETDC ETDC ETDC ETDCh("*Ș4fѷIENDB`mozilla-vpn-client-2.2.0/android/res/mipmap-hdpi/vpnicon_round.png000066400000000000000000000063401404202232700252450ustar00rootroot00000000000000PNG  IHDRHHUG IDATx\ill>^M %ZC D6 p8\R$)^,A DC 32X02qժ\S=cf'gz~H߼(.^k%{X$-.X.t9IG,K\ue ?q$j.%}VM9deP񃿼p=\rac1StUW\( qy'QFe't"Zֈ.( pA2hйusXhg\jit3iί_\^+'0f@sӜtsmVpr&Ќ}}O2'޽3[|K mϩwH6dC-0:9x t$[&r!;6A;wX8Dep!T?۷˗իWy3fE92in3~x _p͜9Rj*3n߾T̶kjJ-M,&&ݻW Nvvv͈e&LsЬÇ'1Z[We!\u֬ɂM*hz<@tҥnHΐLXs\Iڵk^~dp999>}@۷ojcZ65ᴎd G+/޽{,777HUn@bmǭ:lf?Ѡ슉ݼyƌ]02h?*@˖-s"bHtt4۱c߂]VWWw;v̈Tj~i&?AU?-*** Pn:%r4̣5^=L |뫬LP;WgϞmrإK064P[n%Ŕ)Sؒ%K BMZ(mGYt+.;@d*Glx6mڰ~& 0 X&7"J-V^/Rj 2Q ¬*&`lbۙ#Ax@5/%,=ͥV'蕫] 3&ČρF޽ȈQ79ʹtcFkam&,*aɥ*a^y2 yyyFpx!dG5|L+O8aJ{dC &k}@ s/; + AS}+f@-QG(`6ϱCYp(@Gh7WE)Y Kg2m4dl9:pv4ƪhO;b(IΆuy_ |22d@(]l6'[Pah=͛7/e}Fm.k7[_ڃn`=ZrZñxhȐU-CvY{uٯ"8gM &|PBrY{К-|?Fe !_\  4΅/ R753 S+UsU:dȞ~SyKv)Ƒ/Z/BrRH0uD:( T*@&߳.BL7L rȄvMII15!@i!}Nk}'6\P1HhjGQ؂ ԕ R7- RүsjnTjUn fVAkĭ5ep)m@mܶ Cp|4+lIh,4њA}]wB'{.H ` 5κAc;3ܚI`qW%oJi9̛NsmDPDͤq=y7OMsBr*>RK݉K. 0> FxI.%ӂv!%|P@71Xuk mY,%-ALQdsT1&&LL|$tI e{3P|qӆP/[ʭǔ7- mFB$`?T9~.B,g[s4V"Q?K+vmL#!}JtP9vh҆8Xkn7IENDB`mozilla-vpn-client-2.2.0/android/res/mipmap-mdpi/000077500000000000000000000000001404202232700216565ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/res/mipmap-mdpi/vpnicon.png000066400000000000000000000017611404202232700240450ustar00rootroot00000000000000PNG  IHDR00WIDATxKAm~j -hZh ]k^@zO zCx H4-hvݱd˰ݙ}{ogӽ) _[>LcDeB2-T~qleot~Pƣ0XkK5z¨YY&YmhTKu4/2x$kK?QHR*˙6"%Б‚Q]\\TÝ;QT^=-Am\U*oa岚Wccc^H677wY]]m^܊$ejjff{~$zXTwZ$055+uӊK1~{{[]^^6O,`e",D|w@qqa===U=88P+++j`` ,x"FG$Dw:mDjU---ggggi BPR@KNbd \LfVkGL&Za W fnLBR".B0npǍ;8'Ɏy"#֤D@ssssF D!ޓP b.9JbJ_5%uK@-RK9)nj{=ao88nzF@h=i|I,}=<+~x0rV;X #LJ?NK+IENDB`mozilla-vpn-client-2.2.0/android/res/mipmap-mdpi/vpnicon_foreground.png000066400000000000000000000023311404202232700262710ustar00rootroot00000000000000PNG  IHDRllfWIDATx휱J@V[G mTF_@l} k l-lDt.rwf$lfg3'L!I$I$I$I$I$I$IOFLLd&`&`20013==4^ XMLL=`~MK&''Ύ0I=k644dMVq Xvxxh XjB&`e[V???loo71z ܜx?8gc Xo766lnnZa,+NOO]ȏs2ㇿKc Xv~~ÿzb,+ҒXV-//[g355~qΦ+چˋ[3[\\4 1K qsR X777.//ࠀ1K߀ )"nd7"&#(`̣#3::] jl\sc9Fܕm|kӓY__7'''<87;;5̴'z+1333?KriEиVbn]-ߡK0*B ~F璯a:IZ[ޯ]YX;lI⪥{[`@Qj AD(0pZ>68SGVַU[y@[j\__ח5rnwK`t\PRUa.BIhzDlw;FM4#[H\m G DL,.Oҷ.1Ϡ!4 pQ:W5疣a=77θ _"7qS.c+5E:'DBaHo4 G_=+-,U7 eJqRy%/X*4<6Iq׃TdAi 9<Կ-}hG4_=KE[Z*ܔs{{[vSNY"0ִPfݭ^Ҷjcjծ0 0=n$` =/0=20=0WjmGJd.i뢊e:Ș*dž,iȀiȀ3*n!K?t.%mcAViȀE`&`&`200 $I$I$I$I$I$I$NIENDB`mozilla-vpn-client-2.2.0/android/res/mipmap-mdpi/vpnicon_round.png000066400000000000000000000031541404202232700252520ustar00rootroot00000000000000PNG  IHDR00W3IDATxZKL\Uff̠E$8Wj j Iwƍ!hPF#;c"q1$B. 11m$o!Z^2MÝ L.NCCW#ߥFxE N&93srV]$\% 7 k{m=~v\5Hܲ>}^ gJQ -\@䭵fDFC+edoZc|טÜL %W(o0zlIk $ep*$Ӷ-f|bi9W%v*j0[%=ȼȧ;/>dOT >?1"H⼭{%\Tp։5E(b7!B2 2,w 23๗d!ŴDSx݄}KxM?6vxqFY9YCأKB5IENDB`mozilla-vpn-client-2.2.0/android/res/mipmap-xhdpi/000077500000000000000000000000001404202232700220415ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/res/mipmap-xhdpi/vpnicon.png000066400000000000000000000051151404202232700242250ustar00rootroot00000000000000PNG  IHDR``w8 IDATxiM[oM JU4yJtl7,ΖV R uDz;NHRRRD-DϞ=E~DǎE:u,mۊ+V[nϟ?_*|EÇ$$^zbŋL|݈O>ӧOa 7;vP3x=z% ԪUKlٲEh˗bX1i$Rb1k׮ XEŃF%b9"v).]$޿o|9sTd ڀK,غuhӦ#G{iI}]% R]vi~c9p@kq;wD qƍ F$ݻIGESTTTl5kyԩS,\ o޼͛{;m4K@e}zƎ۷k 8q% o>%M4hxUcE.],`ܸq4tJ(w0bq SJfÇšCFܹsyٍX4HKKSǏqVbɢ4n㳂(X1.(f^~ܝ~`<|53q$ CfsM"'w\|>np=gΜ]v4|NIJeĩSē'O۷oųgԱ+WT@DbB@d7܅?ӧXt|r*4Snn"ÇFB=ݩC5*YSٳg\7Ɗݻwz۷M4Q{Ӑ!C#ZI.= 6JbVBw$?<СCkУ܎;AӦMϹܽ{Wn: `JBs0SCDP5}j6lؐ%FERFDXC1B~ !g̘!ڵkj2ee& a KM!2E@P|6޽;f?~\kLVA`'%]GNf 5\2ͺªU<ŋk'ŦM^Fe>t/!#U$ԏ Wݻ ;vLR=(%*PNõ.vMƞ/^>ĉFnݺZw; 58MSLȞ@wѣGE=;߾}٬ԍ7MZ7jHYF+^3ƨS(hA)GM2(E[d0aꜸxQBz&7D~~YKՌ999ʕ+qJرckDAO1KϏ-[TYKDžm۶-O,9zԨQJEK,A ҥ@٣deeռ 7GП"#yp1:ArW^-#8t>,xHm֭[u5$ϝ;ט` {VP5MA۝\/OnvZw^=b GYL̚0Ac-Yd ,PZu DHg |fŠ[2{lp#ǰaÔZM(#Z 5̙3Ö-mcVH3sKBr; = ۱qQBaTN$[,O9R7iɠdxKۢHǚN>}g[X\M'ePDu,š% FZVBH/) ^ǎ?^Ky% t] kb#M% {u1Z8t .h `k 𡛍vteD*Y,t6ŖP0+{Zh(;Roj!Ũ$NQvx531MJ-#S֯__UwHj(ձX*kn%+b%vGRYs pkZR}<4~ u 薀X@6U5iKcj2|r@=fK@ @?nW@o? aŖ x'W?p=0&>gc4=$P,9p HTvRbc_2x*\Sv?I< x_K 58?3}}$L, /+7A$ \{E?l&DZUJAF)EW'EMf"ױMf/zY-$َ֓ dT7$Gz;;#΁9ZIENDB`mozilla-vpn-client-2.2.0/android/res/mipmap-xhdpi/vpnicon_foreground.png000066400000000000000000000065171404202232700264660ustar00rootroot00000000000000PNG  IHDR - IDATxMhW#T`bWVD!)vaAtnХRD]%d}WɶP`[Uj+XxYsf灗;wΜ9oO&FB `)a( `)a( `)a( `)a( `)a( `CP03 S4lٲv088:FFFɓ'?LR@ n wX x<ϳLp ˗!+sssaǎ` … Z 3336lؠ`n~ xav^~Woc?qDoH_)"⋎belWѱuᅦ2xy}SFʕ+12a+VP0k^=z4Tcǎ)5+{0==Ν;$G1qȑh055udX>` ֜oî]R6(5"snO>$y^tKRI2KKb/3mQ})>Sv~511` Vzjٳgsm秅` V\޳gOmAgSǽ{R?\i})>޽CGֶmRo` VvZrmkuuSr\i})>Rg)ܞ>}z[/SF݈ Yb})>8#x"j 3EYϔ `˗/G]$˗|]bg_rɾ w>쳰f͚{=~7o-?C%R۷ϔ ,n߾] .]Զ$\O?%Er)X[n]~Ws"n߾ģ \1ݻw:[sYX:sN(XuԐeQ"Xd8fBTuƍymGfY@{.DZV1}HI@R̈`3"1W, _dzg©Sy/fLbhG@V$h'4nmq/M6ӧOǏgz r)9s&emn}Q.k괪٧8^ V#}}}E~&~em>*Kj$XL\($͓?&;D]łҍI}psodM&kZ8^ V#bn>df8ȸ$؟mS% 3}0`w]E䮬w/dc0uqELgr(XQsC\ՋeEd.dB\Dr.nu.bMcvwf3+|U*lɸ 闺c$q6}M#fgg3?Xߴx=g=뢲:2Z֥eup?ga-^9׃P0VVaE3+YI\XYݩ4yะ\j,gr? 90 rJ)`ۍL2JW?"ǣJ,Rć<|eE$`'J5q OeA.E}([$;e[\UL^ EvuʶVI.s7\EU*%*8·**YTUZ"XOQ_:[yR`-l >!"$!\PAؙ l*e iؙ*RAJ%^&KQS1wA cPR8O?q2j+w<|Qpf=E iڏv5쵕XX7>>^\ܻw/ܽ{7\v-a066|t6_fPu'~,ϡ=hڇvHڭ-`EΝ;34 gQ.n]Yr)5mrq*E.M0.'rK-|s)u! sD.k`+%O!@ɸbr=϶x~L S+~B!XN#vHCUASe;5LMT9cVI3ױ jLbkbs)~%dWBS~SSS R1AN2f-0?? E,d\ ` HW҅UE'b 1p;_Ar"*' `'+s)Tz,vV mvjw0;Om˶*U#An(=)؛Rt_r)/(p>77K044KBSc`` &cVoy` q#nҒ$/?V `)` f `)` f `)` f `)`P03L CP03L CP03L CP0 `IIENDB`mozilla-vpn-client-2.2.0/android/res/mipmap-xhdpi/vpnicon_round.png000066400000000000000000000106431404202232700254360ustar00rootroot00000000000000PNG  IHDR``w8jIDATx] pV~,@ Ye02Z*u,u:RQӢ3(`EQ-(k.*AeP2CKqp}%?3?s=wνOnq&!q]"xN Zk8`Nٜ~S1Q}MX D|Ǎ"i8 4|Nr0Ӝ.pHt>;D|Hg3BvpLN[9<"Ao9&heBc̃y$jK>҄a5y_JsѤL:ZӋuYY:T)$ֹsg{e=N>Ͷnݪ q,9eVJ4S?MN:uظqɓ'MM6o)`B~999f?+ o٣> d|xf'33͟?]|9" :u6G(B:t'uev!2|b;vԙfK.ǏCgZ^[QQ֬Y*0_Q/;Ο6mR̯=c[n}z9}t櫄?ҦM?q$F#rHZj隭bׯozo޽فBx饗X\\ߑJjWFK`|mݦd_~JJJC͓ׯg~CZ¡ ư|aÆ)0i$A[ݟvez={X-}NYog}V)=zޟ.\̔[nD]_ЮD3S {`9s?1*B(S$S~ШQ>|kf֭3ȑ#:Pu g]9\N'~P^og/^4ƍaÆAUHra|;3v^8X,XG!Lt iWGV6c ݙ>==ۗ}q.n е]Hd'N`;v`6lkΝd>rȳ9"{ xi(|L"!D?R ɬNGlڵ1^)SjqQ@cm~{Sz]xh`LBy I|2&!@H֭k;N8B?f6qD1h o 8\s kJU9R`ND5A5QnÅͷ?c+\pA&f;I&ױ# *vlĈz4 `COv{ҭ栘ⴚAXi>o4o\w˖-c۷o}2=zٝwީJk=1sZoF''|Rjmm;{GRvxQ5Ҋ+7l shqzFi\7V[v[Ẻ̇fNlƮ'xBWΟ?|Ay.T~ $IfH[GT6fL! =zԓ1>|Xos/"͐N> 6M-Ȑ {9wyGA\?#"gS` ]Ъp*Y( 2yGtT j@e BLUxWuܵkWs䡺t# ]Ή4] :p:jƈPMks/iӦݻY>}X||5~رc{,YF956;U*-Vu/͜9S9|W~DC]jyAAX*xySJa*9"r۱SSS-ǙM3 m"!!V9#FӔ>EAXjEZ;;ō9l@hDǚ 4zꩧAg tL6ݘ Nă ovY40%UK.nLP{+p㄁$~#"1eX3//Oi*!D oK; CT#y t.|rOhժU X#SFk;wtC0fr:8+M"lu˖-J&Q ~-зt/BC@H{A)))l֬Y : "LKKӣʪPBy&b9fNXԁks(bѶv ŽÂ[ LX4n!,={]vnLI"x"⼬d! ujy(;疶m]ljЎz] r5`ċtG9jxԩ^ 4K#qCe-ݕxj"-t9-eh!u)i%E6`:5~HvnԐxlQLҮ5+}ؓ s tD(Q_~Qs,'!|n۶- <J_8RD@HV^0]XLM JH~/!^bB(-['Os*# V%0mx[jT#8x9i#6ٮ1vj9 & ͊/^CrҠUĖT+BikfeޭjcD:dHu#p#2qbpa}-og~O ڠoE!dLxg%D;mO2feTiLqWLR>C9Td#ц.PJaJp#VF4 #7cٟnj>KYɈнBBMD;:_݈In zVdK@[Gr # P i!.%c*~fq~@?;09SRҩJנN UwʿBKԔD* vK:3>QRu^@@ %^nmj4Xpxb[lϚ3gœ$w^=Y($z5 pU|뭷vhUѩY!aQL 5bcOp.(覞;w%vTgī4{ }1EgHd(5bC{d(Cs{OF'l󲒅bvd*dk>'"85`Y'L݈vm!nvpV 7N*i7noX'֌ڮkhaD69syLo3{VB"m(o*xu {BRkVu3h*eksDMi5nBu)̴&qծsFu\*ȷꉽsG|5O-G5bU&\(iNZ ;L d'>k?}gpU &K9+35\z/0I锤@JR565Zp0KGsՓm>=]yLR%4V\=dI}GfɎykvt)I4sNrL/[G]Ahg*Ti7ۿcH_ ZY$A2/Lk`w8lN80 *Σ? <߁wRXOcO%[ R(n$0Sn{CC9#cڕhW*s}Kq/xIL/!x{QaՊ35v09 Lԗ7>еɦгӳww]o&A&ȠFc2͈yDEftoczV=;+sg$R.ltD:@otm*ݛx DElg-$>kϟ m/"IENDB`mozilla-vpn-client-2.2.0/android/res/mipmap-xxhdpi/000077500000000000000000000000001404202232700222315ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/res/mipmap-xxhdpi/vpnicon.png000066400000000000000000000100001404202232700244020ustar00rootroot00000000000000PNG  IHDRFIDATxUݽA>C}ATE-/J%L|XcIiP0X[K<5*OnHYo2fܙs&Gs9|}}!C 2dȐ!C 2dȐ!C 2dȐ!CFѨʊZkԐ޴dZkĒ5j4iM[}dnek4j)( vY69'|8ȃ_矫#<2XºqƩ:g#~4o<5m4j%pEyFIȷ{o0[syN;4ay.>[_|qJAv8P~a@ q8Z1/rucC,ˠϬDH೥@tCyPJ@.aTʍIstp 5c̢+>/L*ĮRX}U<^by1@^wDյD ě>̮6-VBv@妦g;ץ9DU*xz, @Ü "r)tMA |$T268n௅ꆘ];:ɵV.(ꉸtυyFn#p> $^ .Y4,ad<JuWP !*Dk"op[I 1uQAIfd׮UA |NqhZ4'zTR zĂ~'APݨw|Iw JQËzO1? ӄ/Q.':䓝]@@A BeLx9h|B '3o$MS+Rڷoo4_]A#\uUՕ &t$!6?D["YiD} R((9ő~"qluM=SX2x痝VETj& r<䓱ţV\i)eh`h"95U/'_r8=<( j@AG]H~eՅ\6q|dw :L^vE{r/&HϪ?3'a"Of J]i4 g֍mĈ+q4wfC^rB|@:| $(П1W\)|G҅CW8rN@!.GW}T03s9٪'(]w !90SCIbBD`|ZԪCU1GMIGTbE%O1IPp-_(C&`aN#- \yB KjJYrQ)T璭ʱBraDPHf ਞuMʍH RN z+DFƕSC(Rq+{Pq :I'BOOif)ʍ}*FɽI ČA>G /R֖32wWN *JB!P¦OԜ匞cEs'bh~(z>P j 'BP47/X=!J/DRʨ)Rݖ(T'b4%B/qX[X.J8n*G6z4GT,ڨl/4wʳ?(suW{;: PHFf/4qdxK8{-9ӇG\g B !H$H$@$PQۄ@'v֦q(c?{b!f֦uqVF7&ARf+5[E@ @'п-;E5e9cbtتfPkFkXTolycT?Ϸ6nmGcPmmS%HbȣmYu6@h ͷS| mVfvݬ-kqD_cƈ-7-;,w ʂ=[Rm#b>#6sɞphcNd234f ʊBd,FV}g( ,eFdkV]2}Z#Q'ֱnJbumzX[<-ID"9vE/AؤQ'Dl{Xv 6,{%nD7Vk$=K 2dȐ!C 2dȐ!C 4LY&wIENDB`mozilla-vpn-client-2.2.0/android/res/mipmap-xxhdpi/vpnicon_foreground.png000066400000000000000000000134061404202232700266510ustar00rootroot00000000000000PNG  IHDRDD_GIDATx[OFZjKMRxi418Mі3F@ ѯF~ص{52̻B暆o~GDx{}=!B UviΝ;gVVVMVWWMYY3B씗 oTTT#D@J^^7'믿T o1??~@ӟo,--tSB>iIVVikkCQIJW5_ "I|ܜ 2jI!DH%''ܹsDŽS"B$N GGGMP{SDXO{{{ ST?|  jO?"DH|,..0cuП!_4QD$Qp}.9Uz|+BD$^U֢۷oE!!"Dx>3KRRidvmn=_aaٳgill4SSS^D.!"DP4f ~'q=ii~VZs/BD$dgg5I/0O<.>!"DWϛ۷j=yKJ!DHNoohdm6+mTUz}3BD$,,,njjj1G?#DH6Mqqe-hu!ꫯ| GJ}Z/_5"6I#DHbٳeⴍ;A#DHb~-*mԞF|7BD$v^K4o޼0-8z?\vF}.**rF=z7BDq}}Q'W<~뤿"B$1ssNۨ!"D@s߲immuƣGnVoI>|طln߾z|:tF޽{cvUU8M|!VpU/}QJ8BܻwJlkkkG?#DH矽-2,//[iׅ gI|V;vj~_cb6n"B$q'++Bl~}M>!"DwN>m; }Y|yy6_"B$ O?5ϟ?~7[ܹc?󔔔x_o(xA%![\6Sּ%"B$֣Q\ThWQ!"Dp|h[If uʅѷ!jرc-BD$ad(._&lIf ŭ[G}D"DH2{dx K"DH2{Pw02D1)..*4665N'/t7t"EO62Ե---)3ㆺvAg%c˻Q:DLvvW,,,/_&|VWOUO"HFik̓RFWՋheqU[WW7B^tHggY[[vWRWAFϞ=3'O4999 ]Orv BDJa y_\^9r6Q.]r~6{\Tyyh!D8!x#"L}~}۷oFnO<:z]KQtOc㲭DO蛂>2BZxqd$jW ===ݻ _'ǚtkR!?!ŌGEs}ޢΉ'LVdŋ?\ ?:QZDFGG@ 0lS?j>7BDT2?J$:2 n޼[/qt|lnn.r2P€>G!8q9)͋12 n36ASᴵ1g=z4ts!TBD02L]=\!B632 "ʣGB'KKKA]dh1#F6#D+**̋/"#DM uY!#D 7ox_ΟK$DpE~JŪ3B@ QJ\,kinn6^&U-hN[UX\RHU£K|?YUUU8ܹs'B QECm& fB־:gU% BTg UJLg%P;w!B>ZNImmY^^ږQ-T(ViKMMն -|~~={}kUz+BTmmwa۶m^?B? @#[sv;vF26_O?ڨFz_miڪBF_Iپ(I_vrܬ"Z$B@Q+6}v!Y'r-BDR__ZtѾ]vY_44Ӆ`.Glݢ!B Q255崍Q+Pjim!Bڅhrcc6QHǏ;ퟦ&m ft!ֲgmܻwoj3Tl;ڽ{\1B qrr۷i5OQDB쟭[z?s!B-Dm,))nu>"1Be5a1F!B$w4䲍6e"54!B_貍Z%*.hBHC>4!f>DCe"DB6N褂y* @'}tE脒k'8ΐXuUDgp? _t!nXFOlKYncIGՃlQ"'D[Uoq-C E=DսU9z(!ڮ~Q1;=*fCd;-t-4rɴEw}kKT0Н5~}]96ۢ|S%DE^Wx7g{E9*uDsPEpE>Bot?wu{uvQVۚLw5ŽݏE=ϻ"V=wBF1噘pک^2ΟK#(].]=SRqv\ǀr+gϜJu9m@]QQ!Bbls8qDdo߾H-!N^^YZZ ?6mɉ )j:??!B'Dz~X_fdhw!!:O{{{jZ-edikk {JɪMNfdLǃ?c` ?7nDJTGBHA1EhuVeP}BB!|TKs.ϟ{[jtף*UxBg]P_Yu7%¡!BB#F ֬T(΋4hO#7?ex?o}SPGsdgg:kii$s=h*gbB^s32$!f|tO砣WIr ׼yE!"DpTEUQI|UѱD=OII7(NWv]@#1<fǎڣتCG?#DH`yyٻNF8FI?#DH⊊X#W-z}3BD$6V]mqQU.BҨ_4+++svTaFČNۨ]"B$1ArQmlmmF"B$Ώ\6VWWn"B$1[nmFپ}<~뤿"B$1+4kI]^I#DHbF ^"B46UVV:mcUU" D-fmliiF!ill-YmEFlz^FX~u>Uq!)..6/_-'6>N!"DigֹhrY~Fĕ^+u>}jm ζm̓'OK~F]Vy'W$U=uuu3BD$dgg5khdfi|u!i;)Ҹ_Z@}iWW"IŋfjjgSXX=֭[ݻMSSvr'V_I2.|۷oyˍ_"B$EŻ%mft"͹E͉ҟ!_)((ʛA"DHħ{Èm A1|ŢM!DH&++ˌJZUV?;wBjK!DH%//ZjI!DH'77׌nNQ환G?!DHR:fZ@a!"DRa{Nv=+ܐJTB1x\ֹd=Oyy9;B씕ϛU"\YY1Ν3;w}F÷z}yu{}=bdRTTdMKK7822b&''̌sxx؛ԟ!D@ D@ D@ D@ D@ D@ D@ D@ D@ D@ D@ D@ D@ !!B"B$ DHA"!!"DBBD !!B"B$ DHA"!!"DBBD !!B" DB"> QIENDB`mozilla-vpn-client-2.2.0/android/res/mipmap-xxhdpi/vpnicon_round.png000066400000000000000000000160151404202232700256250ustar00rootroot00000000000000PNG  IHDRFIDATxՖk 0䜇 &U1b^[Cѧ>EiEրO]QgkgA`j}SӷR7SUuϿN>:N(ţxQ< (QT*Ty%e6Aʅ"$jE]Q4DhEFs @UTtR4A1f*OESE+ڨhZEPF9S"UVSဦTqnU>P$E\c[C^`Z(U阬% UV. 綤X~(2W:xP=ݤhEnE$)Sf9+^'E𑺸3yI%4Rmy>i^p&)h} 3S#6V[/4x_=4[ 4hk0:;oha0p j<[he9+bEnr̓HF#M6Fg^+kRRFm}LISUUQUF|*@*_۬ k9j-IO4#Y;g[ @+B5 k9E4'] E9SR{M׆^{ZUQQ8m8:u;찃ۥKfEq] 6I<%.nlMexI'yvJ.palZ_XZ*S\y_cǺ}QNmݺս{f͚lԿO3+ 8!}nFAi&/矻~j*/TaTѶ(O:kbT%%F 1?x7ݳ>=zg;B'Ovofo }^~,'.6ӧׯw:[#J;7o/h,;6fu~yUV^Tep gLR Cuz-cID۲}|S5=z/YĭSL1Vgw}mY|ͫ>c$l-#[nb<#&eK:t mF[,zƢ0Ԇ sQEamiӦDŽ1l4~P4iȕۇ  裏.x&RPql.0/',@`_׮]=<5'?}-rbP l>_@П}%Q3fɣN0o/5)`($?I)ϣ6+2)@_UUu]FB/,]B~- D( }q+7Q_ ҨC!H@<F kb;&hˍ#єؾ^>O>ra^xWn ={8͔ p̘1s=g .= κ!= Eh;ta` QӃ>hKEbcz'0eRK[PCwٲeyÇh7Z9 ]1IcȐ!5.ufLƴ\H ڢ'_l JEMԮ*'x0`DLG&B/:L@xLAzAVp;A ?l|-_w\,vr=33f_|W衇zĩrPySXFkSLj&'|I/HHϣ0~aoUP1d"$ߛcզM בI0s_~ec c^}9s<ƅO{-aٺuk/K>[^$Uǎ4QPDsP_N>dc4RE9jwҜ!`ͅ0vڅlEq[pWs킅 c`q#:^u,_7+쭃:+o<EIF<)xoPCq?gyf2E5~ VAdjJF:uUWS`uQg}aK4rO!U=}0R_7e@3~"Hc<sbOahHS[w <1|3?1Bp!ġ4OLT/C}pITc0竤C#7;v=.5v`@b@M+ia Wݤ0׉'ytz6VQ2/0̜Aphojp~vOҙlojQa|<>3'|X196l3M} SW\N~) c(A(QVzxIACf8vґG(\BDzw$795`E]8Tu浵Ӱ-Ah/҅4l\y҈$5xyc|>쓵?oTS x~S?*<+_&'uUET1}Szl MDb/,z.饗^ x6!s:`[O-XPIf^QTj|qvbc$Ŀt5aI! ¤{欳 ODcB ⛖2y^vA4R("D$M Mk9B^V/xql,ڤh'Fy bs% ج@PFa&m$l+2thvV! i:cI,HhlTrh'. Na6Is ݽa:†!wWԼ(*yĭ%0f1 vVVcI[oBV!J[Fa||-x^Emm "0޽{{ ktMasbla1@Wb2S7)"ǔ S`T4I)m#@IaqH,(7PEEDhi1133nrJN WXan!k"%=X(7],3Seraίw ]v%"$5U]ę1!F eqmy-NR.NW\#CuɅUf˅Iw?Q9˦U=ȳhd%-:'_BI&XRb@-%;LN"wlͺ>b# w|$O>CvmW{/ 2֎7-[W` f~O]3b&v{zsLT uNtHF"Ԁ&9 Jp lo]oB3'2qh,*ss,4?7b:ʢYj5Ji^&zGn9kaęjt]xbO R.o/)&;A T;G_EoR %&%9DoL/m %AT.YY I b4,Mz:+7jF7pA>Ֆ;6g8BxA:of 9F1\WVi͝{jw @!M1ҷE Ab5T8OhCP=k%nկ Di.YriqkG c o ZG{Oa+cԄ1;Lvc)X8-LTBme \sY@lPkbc/Q_B$~h/Cm}ȺJ`a5ʴ1{#@".ez f:IەЮʡ.%}8 c[r"}I`9e9H ́t0ěZ"rdt0;|Zo*< 9AF2PScV3 ZWAmI m{j:]TW"W+*ar`LW1{XmDu UZu5:ˍ62jƋJ'nEu=" ʛʤʪE f-m oBDR~@IEUSuB:VK r[xO5|FX,|˩ߪi/]QXrR^^g- bFu0ި-i>OX`gfvz_mJw3!.PdOYH cgjT@I [lՋ-Db!!Q#u.eIɪyVx 0@D|6V\|ꀘT beDLYF1ĻG6:xTkgU.SqZ )6N|ݢ13R z% 0n&I <ϩڞ5"e CXDGW !+`5UEVzg YFͷUc*ڈ~-"=Z4UT :AxsVvgG֢MQZwUqrO"U;ܐA%Pu)e $Hy>d-*R'4*QH!T7TE۩7wbmdm:3*)PK&m#ȽwhU5D{_?\s>>Z D"H$D"H$D"H$D"H$D"H$D"H$d\)44LV J$I<)2`B]cՅR-b`A48kpFKpOY]8hu1g=b`p 2ph4oŝ.?+$H>%.GEn8hu%o/ qf x80][JUr!BZğ7aQ fSI,{t#,uuB} ijJsBw6cB%oGKx|O8j,OuVׂZbpZ<յ!`%YnP ĺt`յPAV<VB#Z' kA;Z{(hV@I-VC1B]pGP(BcDVצBBE#zzzދoO>-c4v$@շBH=Z*z駣ɓ . ~o9c9"RhHYJ?8G___uL`"`ԩQwwwtرJ)p·~؆ EE/rtԩ+ K/D`r;."ڢM@9-[555"_[YAq`=EX m /POMZ~z)'<{l1mڴieo9c]ڢo9!-:|R'˜.m7(^0o޼br I5kRw뭷:o x[zj{n'eܽ{w6y6 x]q͙.mG@l.ʸaÆm_ީM(^8)c%bt8KQ;tR={d˱>Swu ڵk7,e {7ڤ"s΍k'ƍSDZ.~FE \{뭷fdf9s81?}"-'t|ӧvbDXzs4([IpqAb׭)TwIq壏>{hŊߊˣ`e,(KPeOHGK,Qvh/XEA".̋,Ac:0B[`yL%r}\;cX0FcYcPM#= $#xB&I$^ a~miiZyy c KƔeI(FbhiI7_D/b߬9}hSËecRCf *aa,5-fqruLI7 K7~1{-99?βJcWii$4R!(G۶mRܟ;eMCR (37~o9&ra"eWjvAt %ePZ(ǣ۷%۬Y/|W)~JcY~\А(HW\)wL,>&՜ ' F?p̖ QO9lōk_βZDVKC <o`J"i=/Zis+9 ٙz`'Ypg"@fFKS~ M|dd$M $%&r%N `4niΌ#16cEfkƎi=މA _J q(01VAf&\$rͽ/mK 95pc1lQ;k&NȀhhh%/w j9!T!%}qu^q&b,SW#{(DWW2Ypt 6&Vxw}7zk T钨ҥӘ{=Ria=-Rdir+#Z.ROON7Bi-Z:$GGk.ٹE,푱-$g'טec]rM),g-ڲ,1."@ǭ z``"C4tfa v\0)"@ud#j T%MZ%n_"@̠貘0 7ajJ⢐8iH9(0}8lt!LӄBP/} D S0&P^_ЬC &4W84!儖+zncsGrC ?eCLڠ;`sN 1MB 1l"bҎ6TpK$BzdaHUY؞Qmx;YVz J$զ@ LBZ;H9Bꑐ,!FiQ<$TY+GbYWRIErmX+gDeJ11uR#09.3?PҠ0J[}Ju%g,eOH=\9+AQ Kd[ oHk~Ȩ!޶25K eSzBٮ-_KkT!dlHB9!@-T8% D (;a H4˞)"XZ o="@O{ZS'/!-rXEpX0xBK^2gK"M_p"wHr)Exْt>&[M("]EuQTc))#A\-K􉾉"W3!39x\Q[qm^Eo t;&j(I嚳U"E  fsɹC⩴qK^FE s MKQG2m-ecb&" (#;9\ڤ"5rɳMZ$D".H٬mr6 x.HmڥM(^Ae$I 9\ڤ"u[ Lm.i/"@ Lק@ZC;[֔."0f,A\5Q*ȥpײDM@"hPbI{HAdOKZ) $R>4I3açq1)gEPAj,088 J+$5d^[ $9l({K;@H+5kJ"ELE! tPS[I'Bq @\S5k%kry.K$Bb5my,H'Dg1oMOHE (Mt`UZ9gww""PqQ___422iYı33ǙD<r C!ñ_:::7/PHX0<<\1; O "  " LXz#@zS" nZ-#; ' h -s  6^h6f _<=`ulչ0v_2b6beulFH(XmpaBFwձs7ٗ %i|խVךB#K ~xB忈766c! i $ǒG rbkfu14LJ3x$ ^~KnM$AX,3nҤI7G/C?`Fgv;եyV[ep #_' Θ1BсVЉ?5[TSTk]`@ nnnn^Դ` u:n#XY`uiխ gR@k3\kFKߛ!FGnrmBYjYc4` ]cMY+F V"%VYH.wZ]NRC'@) &[L$Ì`eRK.,qՕVw&O$/%Aٶ5Lj.peB] [ݘiuNDSK= Zo>ڦ3@Oj<'B5ed{B!-V'Kf "Bcԉ)~9D"H$D"H$D"H$D"H$_UrդrDIENDB`mozilla-vpn-client-2.2.0/android/res/mipmap-xxxhdpi/vpnicon_foreground.png000066400000000000000000000217201404202232700270370ustar00rootroot00000000000000PNG  IHDRN0*#IDATxmlO*m ٬sOPz4~V ڵk#  cڷoo7n2ŋm۶IIIh"V 6k׎e̙ԩS6)BoOe۵+  Y;@iiuСѷ_YVV|RK.·-11Yt#+ FB':WVVz n୨p͒ 0@`$$Y~}kÝ }EA`HMpŪU|}n***r: ELs玳A,۵kWg;  8> T\B;  8yaÆ.}+DYZjXݮ*(( 0@`$YE~Ϟ=k.E$ Xf۶mN.򅅅mɒ%Nڦ}FA`Hٻw9so[NNw 0t\!Nڦ}FA`H lƌMۀٽ{|nnnms63FΆ \51趭^I6nHA`HY|ǭiӦK}1'm[b}#AGC}. dwkux 0@`1c:[r)ȉӧOw.3FN^]5ؤIۤ3  Kڴicϟwzrep}%nݚ>0E5\s?~| g7Λ\b F F|pfʕ+]v $j!Ç h~̙3e-ZxmmٲM6 о ri,gDE(Dw5ΝfA8{)&sx?[UUe~駟G 0ִmdgqE;|m޼[I~ժU… oG 0|_7 0h.''ǗBEEwgJ@`H3sLuzO>0yU,"o#  zK={ 0@`$Q۷ocGϚ50y1l8_u.wE 0„ ;v#  㝗 zJڵ+#ap8Zޣ 0@`$6ד|}0a#rwߥ 0@` w}g:u 0@`ij۷Cڔ)Sm6+))2/wM6ي+l޼y6vXKKK'aƫEEE^)'$$X^cܰa7{z}g͚5h"O }@,v;ںu묾>CeA믿z\#I㣏>&)T=۷o=ZlI&ٳgCO>B5k/|Խ{w??~ܻpwWt'ZZZcWSSc^F`4)))uVo 6z֭ O^xw,++V^m'Nx%3Y̧Mf bhYu]|9^+PNNNF`-z&@Aa÷z&71rHO>ݎ=mU3F^GUEGZҒULp՗ԧԷ IKK`EEE2nɓUu5YU'HHxzɺ7\B`|_†(%$$x1DXJ1)BVuY}-==ݷ.: 0 ~Z+/%%ѷ̘1*.0XMNNrۯsIC RYY%&&:]tX/D;ud1+WNGWt=sIC ЧO:8oǸq㜊XU^>9EiS >|ى^}[Ed弐\ҷ[s:CQTYKJJrZkѢEk׮9sD:f:v~ŋ;}ED˗8JD򽟴m룮ت+T$"|ʶ|_%DόR߿~aGQ/0ԩS֡C?~ݽ{Y;4,^,ЬݸqӧO;:":e"R`wy Ζźɓ'SNQիޱtOʜAR>B`JCD喜?zh|׌(E̬X3ѷ=%%<|u.B`|Jaa/9tr7ʳϷ;w8n+Խ{۷oS[x6:|p\ KKKs>xzo;ēQFyCz~aX۷Ϸc"vjEEE%9yGqq 6Zj̻2-#͘1Î?l_>#Xkiӧ{}yK/Oo|EID\1"mϞ=˜9slɞrssm͚5ٶ~[y}ǎjy}$;;rrrlɒ%k./'?-"0@`yv!^Hz*aĉYtB`01)"Rд sz\93rH?-0=,߱cWdptb?3oxaN1/0GP7|9om+z+Q@ Lb_ezv/yF:o޼h9!0ͺ}0i1z o%<Α( S:wMo55ze` 9E z?U֭[#ńQ/#0@`TcKA]ШbFÆy=΅SFìC-*ztD)s*--{gaC(/ zv?&MG:ZVJ .&l< u-}  <ت"v(믿PjEjm={CWZm]џw7ݩ0aɜ={1K @lLINN[ZuQYf!j[iŏ,Dȉ'^_}>KYN%QVNIIpҬY3ox&%|Cߴvfv~N޳gYimC&lRkZ߾})={;h§-]>2zc$͓tf̘,:vGoڵE`^l9*=sPYomUz8p/^l֦MдUXbqgg~/:S)Kf-*>>7a0!ф ݝj͚5m6ۻw7@ٽ{mذ/_n6vXի%$$B oQQQDVװ_xihO︥y^}`Ŋi&o'%%%^]xAAק' 0@`$ԩ}w Æ 0@`$J;z΄ Ek<~aCFBWُaC 0ҕUoA`heb̋>ϦR,Æ  qtfu_RfFB={ڕ+W06 0Y+|aCm 0|'  ,KEE= #a /èP(}#! .Y5>l7nUVիm]x1 $A % yF>SW_}e =iNeFTe^Ms>_U#  aT%X̙36m4kٲKok-,++ о CkG~ը v6F{}ʕ+})}}D_A`HR\\\W^q5>}}yZU+  Q|u˗/[FF6'sh_g 0#ÇZb̘11zh\#g 0,ZK~~omwD 3  A4Eɓ[{]NW8pU9 0{+\8MFr63QO!<%iiiV=qWyqgի}#Agر.A~TWhw 0prss\cǎK߭mp}#gŊN.ǎ t%}aD$BA`HٴiVmZ96l 0@`$޽E>///jG;  8eeeN.Dt\M> 0@`$Jx&O)))qr m*B邽{w 0tm"dɒVXXmgk:ڵ+vi={P 0@`њ' .XVkI۴z?}#gʔ)Ζ\6lX`wB3tPgڥv;  :۷wVƍֵk@AtU"FJgNrvRTTkH}תUGJd. 6|pڒnڣ}EA`HH2asIEE8oGrrUVV:mĉ3  Kt=r-11YN۠}}EA`HHF7h :43KKKoF^ 3g4?8}߿DЯ_?PD]vv5_Dpuo5W޶mŋ}f 2}#1.Ⳗ?u͚6mg.\_? lo߾v-}#GlٲeeC ޽{{J!%tR;zh ۧW-4M4-[<̎;}CA`HI._4O> =n: DHnmmm˫RSS ݻwcV^j{vv6}#f͚֭[cV`j}#-{٘WUU00}#UJeDI,Q//$=i$9F)yyyv޽ڦ6r 0tG&6 0[`AT '-ZPy!0@`$V:uݼy3*6mE* 0KȰGmĈ #Ν;۾}"깘U۬m"0NN-l޼yUF~};ݻΝ;I5z" y򳱑#GڡCB1mжy F͛[ff"2$_ZZMжpLF^:iiiVXXhN˳.}' 0@`Qo ;|pGv l} 0@`IIItfϞm˗/;wډ'R&.]+WxџUEΚ5[1^-P%.. 0@`B 0BA`!# 0@`BFA`!#B 0BF! ! 0@`B 0BA`! ! 0@`BFA`!#BF! !p2B 0BA`!# 0@`BFA`!#!#B 0B ˾yZIENDB`mozilla-vpn-client-2.2.0/android/res/mipmap-xxxhdpi/vpnicon_round.png000066400000000000000000000222731404202232700260200ustar00rootroot00000000000000PNG  IHDRRl$IDATx ՙ_@44[6 ( h MhQ0A0 L0sF 04:-.&&AY{UUի:; ݯ_.]ٕ]ٕ]ٕ]ٕ]ٕ]ٕ]ٕ]RTFDU˳+b r@[gSg0HU^NP4R8Ehs&gF=2Gʮ[[3]P4C\EV#EmTUџSt@!EG)rw3䞭uo;f;WmQv<ޫ}Vw;Qy2EcdK3q Up{@u[d7[z'?- V{eȘt𝲓!3zwESExXў {a4=|#UlGQDKkLE%ndڵ˙0a1,?bcO h,k!UHh%)UpR Y8{EӺ_Ul_4Њ+J/=F>q01՗{kIe뮻dQ1,?x=ma`\{vaقRCs%qn_c`)dc u;+z2S?)gP&߿ߙ2eJK?ci4b7Owzrxy<Smh(U;gi.]8wyd}r{ƜŜ"_@SsS=Hw^k-'^ f&(rnjR^}JV'_ X7Yl{ʘӾ.~b2ٟ* W-x W!rI5%-s\cYTӘܮhM1H?Š'MȓőTL:' ԙtKڴ0;17Q1' sf-X23yAUƃ4 );=!ӦMs\66lpo>թdKA!6kY2dؼM0[4BTP9'Qh7baÆbe]*~{yA,w8݄bQRuF ,O7^Ź-~gРAO\s5s kY^?g٦M"iDZhčL=` kPй]qlHO>?J+4w|' neD6C`>Q"06NH;Z{{l"Oq9NG֒5wu~g,4q^ {lV K|iXЎeih #r̙b{~)e6-RA5kV(㚂0V Gw,m9 J[nu:"ExRHQh:I&! 8ڵkÜCQR0\GF~ꔋт [5uUn\Jyi]JU5@&C&>{GqdC2%a?/^l34HO$; m,Pi1?hĜ2t^}HQÝ0:@WESh۶mSi"ǮGMXgOYqdaR_3)3A>uM2wyfN[昹 `Pkl%t(): 2Xb,& G r1q9Cl3$cZbS7դyyH֕'#@mZ~]ݣ1T~NíHoH7sܔR̗g X b!hNcE pI 4P0d ?eu߻$11~t/"s=X-[ZK0BCo՗%S) P%Mc%2ǧؔ;v83f̨CT\R7QBH @ a%lW @"Z|-d0=: bH : a~Bb?+:0Wj ܾ0`ݺu,6K P9>,b:o J_ ŽhelzY#?A!1fa> q@`? zm:NȎ2A$nDjm:6W\Y8vT rR\{ OҥKV>6rŒѧG͢b˖-d#9P9z`X0{qa&&l6M*F p2@gq!O9*"ǫM1E @"bcH"LrRtFgJp•s ~TE{33hfM\{~E j숓# g G9a8m8pRGE TPY(DB!Oa(ğę[2t*z)np^ep #@1pSt`s) a*ڔCg ~^ r} '̄!$cp|%Ĵn1Zʆb e]0ʰVJ$ǿ7%4>H#4JHdAS"KH#eH+%ԛɮHJ)^eT0-Zd-ɚmO"7 ݶahx?J3Ix2@R6qC)Bi$Ea׳(B $E,wÍc (~\ftxW\j:Y"(+0s\{P{.OyAdḃRm,wE?Ut`[ !J('>(~C q易Kل\tM 0kigzEDQq)A؅V .<ECE;LuKiAXo~ؓ24X#m, u5JarlIIMi'D[=q-L|.6 0;̰q`-ƱCL l$;}GMp $?bk$&yf&\n~7;>*mRFLPQڤbD쉻˚66M:.bF"聨oМnm;F深K, Q6bv~N`6',3( :#h`IhTy }$JgiMg!Jlذs8wM,Yĝ ɳ>Q/(m W|JՖ^MYLD*y`B1;ӍbիU$nT?\`anb9snZ&M GSrl CBJ'C(4$S`a] իW;VrVX\wuܹsSb2͘V11\NOb7)Ss~\YƝ[昹3g;A>Pk?U0U$vwDž@<8O0jD[[)^+Ŝ48D EaYT,fdbq``&h=mY*S?@3,{g,k_ثta-\ZM2?bOw?lT?[݂^=<=rCIX{RQZ=)?oM=@ e7o=c,C ğeC+1q'WmLb̡%<Tɓ.523$ I G%__A̡͟Z 7 )bQ#񷠁1Zq)aClKA%A+/Ƀ%\P#6aj3#' & F?Ġ~i{J`dgo G(qI~`m'5pŔʉ&> 1P6~Pý~sʝ_  6;9Ś)!S0"$Sg3){y3cNB%??!X`US eE=f0)]LϠ[w9Fۖؠ%6Bѥi6G1g =Ż##ۈ)E#11~J٬&dz1ϪcЇ0w|pE䰶4IR*LYq[c;,u$1Y ,v 2 64L)@6T.5m*~-{Jw'e"[3S/PvPTD*`#Omxf 54{w g_?yk~:HQQ~12ay#c @xS*M旟:6L)7 V??+X3w(5^b(,M 3T@F} [cY~{^~(kUOh3i pHH&MaeU";A0)\{ċBԾSI҂ ^'1Ѐ\X\5Bֵ¡EHPɏFwC {!e*QhS9*aS#c5HVEtU(7,Qr}/cIŶ6ߪSCc%51BSLj(t ',~3ܺ{-[1@9~^&Xjl队X"E{Jh*cef%qT;v8˗/w3(H!lwQ:+(H<S=Î\㱤B˘~jҥβe˜+WQi@<ƚPaqR|Q)f(z=LDc_ 7ϙqR| )Ħ(4PLWBod}'Qk |.PX'}F玥:jѧ:+(tp]Kp r:}ؙ18CsXɽ?+֢O!QH[t8E+*A7ũcf 鬆:~Ew Fp{ѧPvgI ޷qa˜R"ޅ*KMQE/S;]|:J1)wG{X"Bmm4J52ۤ2{ fQ\݌!%5_$_e K)pɵ75Nl̙3ݘuVg֬YIܮ0yQzDP|AܞT&@hmmuyF,g {4wPq~I0y* i@86L% ( aIuW{QJј=Ԛ0C5U\RdbLpxȸ!*8,^ٶm5E%f,h6]ui!&C9RbY~[&V|{w޼y4u^d  $<:BOXN^R ,YdvѸbݺuK;yhܓPëYZ'd˚?yw +»L{[qS_I0P1GL7@dݕv&𯑵 <௩$b&P]]}5&]92]:w@@+1r5E/H)՚-0?6wE cm"*q!DW2Ȓc6; Ckjjff1ԩ= 0v &uYnUdAyw}m|GEnaYӊ0ur5J\5 Gj Lv ^NhwƮU%\X澆7 aăE Hݎ$&(v u]?^hYYi o fXPFi@Nyj!w,g=G7k` eI֮S~L`Zz,ETrI0(#cߩN`c?yZ ͤZ/aDM+Vvv"o+x-c/k0yz~f A/&f0s"ZGpv")ڠ̭eG4˚thD"m*5O]nf1j~p%Mji(ϮM< nKk݀SaǰZ>{49mesbX{]?3q4@-ևr4`,$' !60(_ O)s;@G뗗 Rm)+G0D>vW EH +(ڦbsVA25of/iADuW+T]赘3J`I gnb--S$CD2)0Mʮ.D(XfzCg O>"V41H8D9L@:`M2" g gw w1^^+(3c}B4X]S̾X( ӟ/ggwuw7;|>g.+e|LY81L8A{w㾮Ȼ;=3gWdLe u̒Ϙv ;9;`|. wU~L^`IENDB`mozilla-vpn-client-2.2.0/android/res/values/000077500000000000000000000000001404202232700207435ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/res/values/libs.xml000066400000000000000000000010471404202232700224200ustar00rootroot00000000000000 https://download.qt.io/ministro/android/qt5/qt-5.14 mozilla-vpn-client-2.2.0/android/res/values/style.xml000066400000000000000000000006111404202232700226230ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/android/res/values/vpnicon_background.xml000066400000000000000000000001641404202232700253410ustar00rootroot00000000000000 #000000 mozilla-vpn-client-2.2.0/android/settings.gradle000066400000000000000000000006001404202232700216670ustar00rootroot00000000000000 include ':libwg' project(':libwg').projectDir = new File(rootDir, 'modules/wireguard/libwg') include ':config' project(':config').projectDir = new File(rootDir, 'modules/wireguard/config') include ':crypto' project(':crypto').projectDir = new File(rootDir, 'modules/wireguard/crypto') include ':utility' project(':utility').projectDir = new File(rootDir, 'modules/wireguard/utility')mozilla-vpn-client-2.2.0/android/src/000077500000000000000000000000001404202232700174425ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/src/com/000077500000000000000000000000001404202232700202205ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/src/com/mozilla/000077500000000000000000000000001404202232700216675ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/src/com/mozilla/vpn/000077500000000000000000000000001404202232700224725ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/src/com/mozilla/vpn/BootReceiver.kt000066400000000000000000000036741404202232700254340ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.firefox.vpn import android.content.BroadcastReceiver import android.content.Context import android.content.Intent import android.os.Build import com.mozilla.vpn.Log import com.wireguard.android.backend.GoBackend class BootReceiver : BroadcastReceiver() { private val TAG = "BootReceiver" override fun onReceive(context: Context, arg1: Intent) { Log.init(context) if (!canEnableVPNOnBoot()) { Log.i(TAG, "This device does not support start on boot - exit") return } val prefs = context.getSharedPreferences("com.mozilla.vpn.prefrences", Context.MODE_PRIVATE) val startOnBoot = prefs.getBoolean("startOnBoot", false) if (!startOnBoot) { Log.i(TAG, "This device did not enable start on boot - exit") return } Log.i(TAG, "This device did enable start on boot - try to start") // Queue Backend start Before the VPN Service to give it's ready when the VPN wants to start context.startService(Intent(context, GoBackend.VpnService::class.java)) val intent = Intent(context, VPNService::class.java) intent.putExtra("startOnBoot", true) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { context.startForegroundService(intent) } else { context.startService(intent) } Log.i(TAG, "Started Service") } companion object { /** * Only devices below N allow us to enable the VPN after the OnBootIntent * Devices higher then that should refer to the Always On VPN option in settings */ fun canEnableVPNOnBoot(): Boolean { return (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) } } } mozilla-vpn-client-2.2.0/android/src/com/mozilla/vpn/NotificationUtil.kt000066400000000000000000000114741404202232700263250ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.mozilla.vpn import android.app.NotificationChannel import android.app.NotificationManager import android.app.PendingIntent import android.content.Context import android.content.Intent import android.os.Build import android.os.Parcel import androidx.core.app.NotificationCompat import org.json.JSONObject import org.mozilla.firefox.vpn.VPNService object NotificationUtil { var sCurrentContext: Context? = null private var sNotificationBuilder: NotificationCompat.Builder? = null const val NOTIFICATION_CHANNEL_ID = "com.mozilla.vpnNotification" const val CONNECTED_NOTIFICATION_ID = 1337 const val tag = "NotificationUtil" /** * Updates the current shown notification from a * Parcel - Gets called from AndroidController.cpp */ fun update(data: Parcel) { // [data] is here a json containing the noification content val buffer = data.createByteArray() val json = buffer?.let { String(it) } val content = JSONObject(json) update(content.getString("title"), content.getString("message")) } /** * Updates the current shown notification */ fun update(heading: String, message: String) { if (sCurrentContext == null) return val notificationManager: NotificationManager = sCurrentContext?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager sNotificationBuilder?.let { it.setContentTitle(heading) .setContentText(message) notificationManager.notify(CONNECTED_NOTIFICATION_ID, it.build()) } } /** * Saves the default translated "connected" notification, in case the vpn gets started * without the app. */ fun saveFallBackMessage(data: Parcel, context: Context) { // [data] is here a json containing the notification content val buffer = data.createByteArray() val json = buffer?.let { String(it) } val content = JSONObject(json) val prefs = context.getSharedPreferences("com.mozilla.vpn.prefrences", Context.MODE_PRIVATE) prefs.edit() .putString("fallbackNotificationHeader", content.getString("title")) .putString("fallbackNotificationMessage", content.getString("message")) .apply() Log.v(tag, "Saved new fallback message -> ${content.getString("title")}") } /* * Creates a new Notification using the current set of Strings * Shows the notification in the given {context} */ fun show(service: VPNService) { sNotificationBuilder = NotificationCompat.Builder(service, NOTIFICATION_CHANNEL_ID) sCurrentContext = service val notificationManager: NotificationManager = sCurrentContext?.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // From Oreo on we need to have a "notification channel" to post to. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val name = "vpn" val descriptionText = " " val importance = NotificationManager.IMPORTANCE_LOW val channel = NotificationChannel(NOTIFICATION_CHANNEL_ID, name, importance).apply { description = descriptionText } // Register the channel with the system notificationManager.createNotificationChannel(channel) } // In case we do not have gotten a message to show from the Frontend // try to populate the notification with a translated Fallback message val prefs = service.getSharedPreferences("com.mozilla.vpn.prefrences", Context.MODE_PRIVATE) val message = "" + prefs.getString("fallbackNotificationMessage", "Running in the Background") val header = "" + prefs.getString("fallbackNotificationHeader", "Mozilla VPN") // Create the Intent that Should be Fired if the User Clicks the notification val mainActivityName = "org.qtproject.qt5.android.bindings.QtActivity" val activity = Class.forName(mainActivityName) val intent = Intent(service, activity) val pendingIntent = PendingIntent.getActivity(service, 0, intent, 0) // Build our notification sNotificationBuilder?.let { it.setSmallIcon(org.mozilla.firefox.vpn.R.drawable.ic_mozvpn_round) .setContentTitle(header) .setContentText(message) .setOnlyAlertOnce(true) .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setContentIntent(pendingIntent) service.startForeground(CONNECTED_NOTIFICATION_ID, it.build()) } } } mozilla-vpn-client-2.2.0/android/src/com/mozilla/vpn/PackageManagerHelper.java000066400000000000000000000161441404202232700273310ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.mozilla.vpn; import android.app.Activity; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; import android.webkit.WebView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import java.util.ArrayList; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.regex.Pattern; // Gets used by /platforms/android/androidAppListProvider.cpp public class PackageManagerHelper { final static String TAG = "PackageManagerHelper"; final static int MIN_CHROME_VERSION = 65; // These system apps will not be hidden to the user (if installed): final static List SYSTEM_ALLOWLIST = Arrays.asList(new String[] { "com.android.vending", // Google Play Store "com.google.android.apps.chromecast.app", // Google Home "com.google.android.apps.maps", // Google Maps "com.google.android.apps.walletnfcrel", // Google Pay "com.google.android.calendar", // Gcal "com.google.android.gm", // Gmail "com.google.android.music", // Gmusic "com.google.android.videos", // Play video "com.google.android.youtube", // Youtube "com.google.android.projection.gearhead", // Android Auto "com.google.android.apps.magazines", // Google news "com.google.android.GoogleCamera", // Google Camera "com.android.hotwordenrollment.xgoogle", // Google Assistant "com.android.hotwordenrollment.okgoogle", // Google Assistant "com.google.android.gms.location.history", // Google Location History }); final static List CHROME_BROWSERS = Arrays.asList( new String[] {"com.google.android.webview", "com.android.webview", "com.google.chrome"}); private static String getAllAppNames(Context ctx) { JSONObject output = new JSONObject(); PackageManager pm = ctx.getPackageManager(); List browsers = getBrowserIDs(pm); List packs = pm.getInstalledPackages(0); for (int i = 0; i < packs.size(); i++) { PackageInfo p = packs.get(i); // Do not add ourselves and System Apps to the list, unless it might be a browser if ((!isSystemPackage(p) || browsers.contains(p.packageName) || SYSTEM_ALLOWLIST.contains(p.packageName)) && !isSelf(p)) { String appid = p.packageName; String appName = p.applicationInfo.loadLabel(pm).toString(); try { output.put(appid, appName); } catch (JSONException e) { e.printStackTrace(); } } } return output.toString(); } private static Drawable getAppIcon(Context ctx, String id) { try { return ctx.getPackageManager().getApplicationIcon(id); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } return new ColorDrawable(Color.TRANSPARENT); } private static boolean isSystemPackage(PackageInfo pkgInfo) { return (pkgInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; } private static boolean isSelf(PackageInfo pkgInfo) { return pkgInfo.packageName.equals("org.mozilla.firefox.vpn") || pkgInfo.packageName.equals("org.mozilla.firefox.vpn.debug"); } // Returns List of all Packages that can classify themselves as browsers private static List getBrowserIDs(PackageManager pm) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.mozilla.org/")); intent.addCategory(Intent.CATEGORY_BROWSABLE); // We've tried using PackageManager.MATCH_DEFAULT_ONLY flag and found that browsers that // are not set as the default browser won't be matched even if they had CATEGORY_DEFAULT set // in the intent filter List resolveInfos = pm.queryIntentActivities(intent, PackageManager.MATCH_ALL); List browsers = new ArrayList(); for (int i = 0; i < resolveInfos.size(); i++) { ResolveInfo info = resolveInfos.get(i); String browserID = info.activityInfo.packageName; browsers.add(browserID); } return browsers; } // Gets called in AndroidAuthenticationListener; public static boolean isWebViewSupported(Context ctx) { Log.v(TAG, "Checking if installed Webview is compatible with FxA"); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { // The default Webview is able do to FXA return true; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { PackageInfo pi = WebView.getCurrentWebViewPackage(); if (CHROME_BROWSERS.contains(pi.packageName)) { return isSupportedChromeBrowser(pi); } return isNotAncientBrowser(pi); } // Before O the webview is hardcoded, but we dont know which package it is. // Check if com.google.android.webview is installed PackageManager pm = ctx.getPackageManager(); try { PackageInfo pi = pm.getPackageInfo("com.google.android.webview", 0); return isSupportedChromeBrowser(pi); } catch (PackageManager.NameNotFoundException e) { } // Otherwise check com.android.webview try { PackageInfo pi = pm.getPackageInfo("com.android.webview", 0); return isSupportedChromeBrowser(pi); } catch (PackageManager.NameNotFoundException e) { } Log.e(TAG, "Android System WebView is not found"); // Giving up :( return false; } private static boolean isSupportedChromeBrowser(PackageInfo pi) { Log.d(TAG, "Checking Chrome Based Browser: " + pi.packageName); Log.d(TAG, "version name: " + pi.versionName); Log.d(TAG, "version code: " + pi.versionCode); try { String versionCode = pi.versionName.split(Pattern.quote(" "))[0]; String majorVersion = versionCode.split(Pattern.quote("."))[0]; int version = Integer.parseInt(majorVersion); return version >= MIN_CHROME_VERSION; } catch (Exception e) { Log.e(TAG, "Failed to check Chrome Version Code " + pi.versionName); return false; } } private static boolean isNotAncientBrowser(PackageInfo pi) { // Not a google chrome - So the version name is worthless // Lets just make sure the WebView // used is not ancient ==> Was updated in at least the last 365 days Log.d(TAG, "Checking Chrome Based Browser: " + pi.packageName); Log.d(TAG, "version name: " + pi.versionName); Log.d(TAG, "version code: " + pi.versionCode); double oneYearInMillis = 31536000000L; return pi.lastUpdateTime > (System.currentTimeMillis() - oneYearInMillis); } } mozilla-vpn-client-2.2.0/android/src/com/mozilla/vpn/VPNLogger.kt000066400000000000000000000035331404202232700246410ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.mozilla.vpn import android.content.Context import java.io.File import java.time.LocalDateTime import android.util.Log as nativeLog /* * Drop in replacement for android.util.Log * Also stores a copy of all logs in tmp/mozilla_deamon_logs.txt */ class Log { val LOG_MAX_FILE_SIZE = 204800 private var file: File private constructor(context: Context) { val tempDIR = context.cacheDir file = File(tempDIR, "mozilla_deamon_logs.txt") if (file.length() > LOG_MAX_FILE_SIZE) { file.writeText("") } } companion object { var instance: Log? = null fun init(ctx: Context) { if (instance == null) { instance = Log(ctx) } } fun i(tag: String, message: String) { instance?.write("[info] - ($tag) - $message") nativeLog.i(tag, message) } fun v(tag: String, message: String) { instance?.write("($tag) - $message") nativeLog.v(tag, message) } fun e(tag: String, message: String) { instance?.write("[error] - ($tag) - $message") nativeLog.e(tag, message) } fun getContent(): String? { return try { instance?.file?.readText() } catch (e: Exception) { "=== Failed to read Daemon Logs === \n ${e.localizedMessage} " } } fun clearFile() { instance?.file?.writeText("") } } private fun write(message: String) { LocalDateTime.now() file.appendText("[${LocalDateTime.now()}] $message \n") } } mozilla-vpn-client-2.2.0/android/src/com/mozilla/vpn/VPNPermissionHelper.kt000066400000000000000000000023701404202232700267100ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.firefox.vpn import android.content.Context import android.content.Intent class VPNPermissionHelper : android.net.VpnService() { /** * This small service does nothing else then checking if the vpn permission * is present and prompting if not. */ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { val intent = prepare(this.applicationContext) if (intent != null) { startActivityForResult(intent) } return START_NOT_STICKY } companion object { @JvmStatic fun startService(c: Context) { val appC = c.applicationContext appC.startService(Intent(appC, VPNPermissionHelper::class.java)) } } /** * Fetches the Global QTAndroidActivity and calls startActivityForResult with the given intent * Is used to request the VPN-Permission, if not given. * Actually Implemented in src/platforms/android/AndroidController.cpp */ external fun startActivityForResult(i: Intent) } mozilla-vpn-client-2.2.0/android/src/com/mozilla/vpn/VPNService.kt000066400000000000000000000134031404202232700250170ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.firefox.vpn import android.content.Context import android.content.Intent import android.os.IBinder import com.mozilla.vpn.Log import com.mozilla.vpn.NotificationUtil import com.mozilla.vpn.VPNTunnel import com.wireguard.android.backend.* import com.wireguard.android.backend.GoBackend import com.wireguard.config.Config import java.lang.Exception class VPNService : android.net.VpnService() { private val tag = "VPNService" private var mBinder: VPNServiceBinder = VPNServiceBinder(this) private val mBackend = GoBackend(this) private val mTunnel = VPNTunnel("mvpn1", mBinder) private var mConfig: Config? = null override fun onUnbind(intent: Intent?): Boolean { if (state == Tunnel.State.DOWN) { // If the Qt Client got closed while we were not connected // we do not need to stay as a foreground service. stopForeground(true) } return super.onUnbind(intent) } /** * EntryPoint for the Service, gets Called when AndroidController.cpp * calles bindService. Returns the [VPNServiceBinder] so QT can send Requests to it. */ override fun onBind(intent: Intent?): IBinder? { Log.init(this) NotificationUtil.show(this) Log.v(tag, "Got Bind request") return mBinder } /** * Might be the entryPoint if the Service gets Started via an * Service Intent: Might be from Always-On-Vpn from Settings * or from Booting the device and having "connect on boot" enabled. */ override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Log.init(this) this.startService(Intent(this, GoBackend.VpnService::class.java)) intent?.let { if (intent.getBooleanExtra("startOnly", false)) { Log.i(tag, "Start only!") return super.onStartCommand(intent, flags, startId) } if (intent.getBooleanExtra("startOnBoot", false)) { Log.v(tag, "Starting VPN because 'start on boot is enabled'") } else { Log.v(tag, "Starting VPN because 'always on vpn' is enabled") } } // Go foreground if we need to connect NotificationUtil.show(this) if (this.mConfig == null) { // We don't have tunnel to turn on - Try to create one with last config the service got val prefs = getSharedPreferences("com.mozilla.vpn.prefrences", Context.MODE_PRIVATE) val lastConfString = prefs.getString("lastConf", "") if (lastConfString.isNullOrEmpty()) { // We have nothing to connect to -> Exit Log.e(tag, "VPN service was triggered without defining a Server or having a tunnel") return super.onStartCommand(intent, flags, startId) } this.mConfig = mBinder.buildConfigFromJSON(lastConfString) } turnOn(this.mConfig) return super.onStartCommand(intent, flags, startId) } // Invoked when the application is revoked. // At this moment, the VPN interface is already deactivated by the system. override fun onRevoke() { this.turnOff() super.onRevoke() } var statistic: Statistics? = null get() { return mBackend.getStatistics(this.mTunnel) } var state: Tunnel.State? = null get() { return mBackend.getState(this.mTunnel) } var connectionTime: Long = 0 get() { return mTunnel.mConnectionTime } /* * Checks if the VPN Permission is given. * If the permission is given, returns true * Requests permission and returns false if not. */ fun checkPermissions(): Boolean { // See https://developer.android.com/guide/topics/connectivity/vpn#connect_a_service // Call Prepare, if we get an Intent back, we dont have the VPN Permission // from the user. So we need to pass this to our main Activity and exit here. val intent = prepare(this) if (intent == null) { Log.e(tag, "VPN Permission Already Present") return true } Log.e(tag, "Requesting VPN Permission") return false } fun turnOn(newConf: Config?) { if (!checkPermissions()) { Log.e(tag, "turn on was called without no permissions present!") mTunnel.abort() return } NotificationUtil.show(this) // Go foreground this.startService(Intent(this, GoBackend.VpnService::class.java)) if (newConf == null && mConfig == null) { Log.e(tag, "Tried to start VPN with null config - abort") } if (newConf != null) { mConfig = newConf } // wgBackend will "DOWN" the tunnel before connecting, // we don't need the onchange event to be passed to the controller. // so set the current expected state to be down. mTunnel.mState = Tunnel.State.DOWN try { mBackend.setState(mTunnel, Tunnel.State.UP, mConfig) } catch (e: Exception) { mTunnel.abort() } } fun turnOff() { Log.v(tag, "Try to disable tunnel") mBackend.setState(mTunnel, Tunnel.State.DOWN, null) stopForeground(false) } companion object { @JvmStatic fun startService(c: Context) { c.applicationContext.startService( Intent(c.applicationContext, VPNService::class.java).apply { putExtra("startOnly", true) } ) } } } mozilla-vpn-client-2.2.0/android/src/com/mozilla/vpn/VPNServiceBinder.kt000066400000000000000000000221041404202232700261410ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.firefox.vpn import android.content.Context import android.os.Binder import android.os.DeadObjectException import android.os.IBinder import android.os.Parcel import com.mozilla.vpn.Log import com.mozilla.vpn.NotificationUtil import com.wireguard.android.backend.Tunnel import com.wireguard.config.* import com.wireguard.crypto.Key import org.json.JSONObject import java.lang.Exception class VPNServiceBinder(service: VPNService) : Binder() { private val mService = service private val tag = "VPNServiceBinder" private var mListener: IBinder? = null private var mResumeConfig: Config? = null /** * The codes this Binder does accept in [onTransact] */ object ACTIONS { const val activate = 1 const val deactivate = 2 const val registerEventListener = 3 const val requestStatistic = 4 const val requestGetLog = 5 const val requestCleanupLog = 6 const val resumeActivate = 7 const val enableStartOnBoot = 8 const val setNotificationText = 9 const val setFallBackNotification = 10 } /** * Gets called when the VPNServiceBinder gets a request from a Client. * The [code] determines what action is requested. - see [ACTIONS] * [data] may contain a utf-8 encoded json string with optional args or is null. * [reply] is a pointer to a buffer in the clients memory, to reply results. * we use this to send result data. * * returns true if the [code] was accepted */ override fun onTransact(code: Int, data: Parcel, reply: Parcel?, flags: Int): Boolean { Log.i(tag, "GOT TRANSACTION $code") when (code) { ACTIONS.activate -> { try { // [data] is here a json containing the wireguard conf val buffer = data.createByteArray() val json = buffer?.let { String(it) } // Store the config in case the service gets // asked boot vpn from the OS val prefs = mService.getSharedPreferences( "com.mozilla.vpn.prefrences", Context.MODE_PRIVATE ) prefs.edit() .putString("lastConf", json) .apply() Log.v(tag, "Stored new Tunnel config in Service") val config = buildConfigFromJSON(json) if (!mService.checkPermissions()) { mResumeConfig = config // The Permission prompt was already // send, in case it's accepted we will // recive ACTIONS.resumeActivate return true } this.mService.turnOn(config) } catch (e: Exception) { Log.e(tag, "An Error occurred while enabling the VPN: ${e.localizedMessage}") } return true } ACTIONS.resumeActivate -> { // [data] is empty // Activate the current tunnel try { this.mService.turnOn(mResumeConfig) } catch (e: Exception) { Log.e(tag, "An Error occurred while enabling the VPN: ${e.localizedMessage}") } return true } ACTIONS.deactivate -> { // [data] here is empty this.mService.turnOff() return true } ACTIONS.registerEventListener -> { // [data] contains the Binder that we need to dispatch the Events val binder = data.readStrongBinder() mListener = binder val obj = JSONObject() obj.put("connected", mService.state == Tunnel.State.UP) obj.put("time", mService.connectionTime) dispatchEvent(EVENTS.init, obj.toString()) return true } ACTIONS.requestStatistic -> { val statistics = this.mService.statistic val obj = JSONObject() obj.put("totalRX", statistics?.totalRx()) obj.put("totalTX", statistics?.totalTx()) dispatchEvent(EVENTS.statisticUpdate, obj.toString()) return true } ACTIONS.requestGetLog -> { // Grabs all the Logs and dispatch new Log Event dispatchEvent(EVENTS.backendLogs, Log.getContent()) return true } ACTIONS.enableStartOnBoot -> { // Sets the Start on boot pref data is here a Byte Array with length 1 // and the byte is a boolean val buffer = data.createByteArray() if (buffer == null) { return true; } val startOnBootEnabled = buffer.get(0) != 0.toByte() val prefs = mService.getSharedPreferences( "com.mozilla.vpn.prefrences", Context.MODE_PRIVATE ) prefs.edit() .putBoolean("startOnBoot", startOnBootEnabled) .apply() } ACTIONS.requestCleanupLog -> { Log.clearFile() return true } ACTIONS.setNotificationText -> { NotificationUtil.update(data) return true } ACTIONS.setFallBackNotification -> { NotificationUtil.saveFallBackMessage(data, mService) return true } else -> { Log.e(tag, "Received invalid bind request \t Code -> $code") // If we're hitting this there is probably something wrong in the client. return false } } return false } /** * Dispatches an Event to all registered Binders * [code] the Event that happened - see [EVENTS] * To register an Eventhandler use [onTransact] with * [ACTIONS.registerEventListener] */ fun dispatchEvent(code: Int, payload: String?) { try { mListener?.let { if (it.isBinderAlive) { val data = Parcel.obtain() data.writeByteArray(payload?.toByteArray(charset("UTF-8"))) it.transact(code, data, Parcel.obtain(), 0) } } } catch (e: DeadObjectException) { // If the QT Process is killed (not just inactive) // we cant access isBinderAlive, so nothing to do here. } } /** * The codes we Are Using in case of [dispatchEvent] */ object EVENTS { const val init = 0 const val connected = 1 const val disconnected = 2 const val statisticUpdate = 3 const val backendLogs = 4 } /** * Create a Wireguard [Config] from a [json] string - * The [json] will be created in AndroidController.cpp */ fun buildConfigFromJSON(json: String?): Config { val confBuilder = Config.Builder() if (json == null) { return confBuilder.build() } val obj = JSONObject(json) val jServer = obj.getJSONObject("server") val peerBuilder = Peer.Builder() val ep = InetEndpoint.parse(jServer.getString("ipv4AddrIn") + ":" + jServer.getString("port")) peerBuilder.setEndpoint(ep) peerBuilder.setPublicKey(Key.fromBase64(jServer.getString("publicKey"))) val jAllowedIPList = obj.getJSONArray("allowedIPs") if (jAllowedIPList.length() == 0) { val internet = InetNetwork.parse("0.0.0.0/0") // aka The whole internet. peerBuilder.addAllowedIp(internet) } else { (0 until jAllowedIPList.length()).toList().forEach { val network = InetNetwork.parse(jAllowedIPList.getString(it)) peerBuilder.addAllowedIp(network) } } confBuilder.addPeer(peerBuilder.build()) val privateKey = obj.getJSONObject("keys").getString("privateKey") val jDevice = obj.getJSONObject("device") val ifaceBuilder = Interface.Builder() ifaceBuilder.parsePrivateKey(privateKey) ifaceBuilder.addAddress(InetNetwork.parse(jDevice.getString("ipv4Address"))) ifaceBuilder.addAddress(InetNetwork.parse(jDevice.getString("ipv6Address"))) ifaceBuilder.addDnsServer(InetNetwork.parse(jServer.getString("ipv4Gateway")).address) val jExcludedApplication = obj.getJSONArray("excludedApps") (0 until jExcludedApplication.length()).toList().forEach { val appName = jExcludedApplication.get(it).toString() ifaceBuilder.excludeApplication(appName) } confBuilder.setInterface(ifaceBuilder.build()) return confBuilder.build() } } mozilla-vpn-client-2.2.0/android/src/com/mozilla/vpn/VPNTunnel.kt000066400000000000000000000024301404202232700246620ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package com.mozilla.vpn import com.wireguard.android.backend.Tunnel import org.mozilla.firefox.vpn.VPNServiceBinder class VPNTunnel : Tunnel { val mName: String val mBinder: VPNServiceBinder var mState = Tunnel.State.DOWN var mConnectionTime = 0L constructor(name: String, m: VPNServiceBinder) { this.mName = name this.mBinder = m } override fun getName(): String { return mName } override fun onStateChange(newState: Tunnel.State) { if (mState != newState) { mState = newState if (mState == Tunnel.State.UP) { mConnectionTime = System.currentTimeMillis() mBinder.dispatchEvent(VPNServiceBinder.EVENTS.connected, "") } else { mConnectionTime = 0 mBinder.dispatchEvent(VPNServiceBinder.EVENTS.disconnected, "") } } } // Tells QT that the connection attempt failed. fun abort() { mState = Tunnel.State.DOWN mBinder.dispatchEvent(VPNServiceBinder.EVENTS.disconnected, "") } } mozilla-vpn-client-2.2.0/android/src/com/mozilla/vpn/VPNWebView.java000066400000000000000000000123501404202232700252720ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ package org.mozilla.firefox.vpn; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.graphics.Bitmap; import android.net.Uri; import android.os.Build; import android.os.RemoteException; import android.webkit.URLUtil; import android.webkit.WebSettings; import android.webkit.WebSettings.PluginState; import android.webkit.WebView; import android.webkit.WebViewClient; import android.util.Log; import com.android.installreferrer.api.InstallReferrerClient; import com.android.installreferrer.api.InstallReferrerStateListener; import com.android.installreferrer.api.ReferrerDetails; import java.lang.Runnable; import java.lang.String; import java.util.Date; import java.util.concurrent.Semaphore; public class VPNWebView { private static final String TAG = "VPNWebView"; private final Activity m_activity; private WebView m_webView = null; private InstallReferrerClient m_referrer; private native void nativeOnPageStarted(String url, Bitmap icon); private native void nativeOnError(int errorCode, String description, String url); private class VPNWebViewClient extends WebViewClient { VPNWebViewClient() { super(); } @Override public boolean shouldOverrideUrlLoading(WebView view, String url) { return false; } @Override public void onPageStarted(WebView view, String url, Bitmap favicon) { Log.v(TAG, "Url changed: " + url); super.onPageStarted(view, url, favicon); nativeOnPageStarted(url, favicon); } @Override public void onReceivedError(WebView view, int errorCode, String description, String url) { super.onReceivedError(view, errorCode, description, url); nativeOnError(errorCode, description, url); } } public VPNWebView(final Activity activity, final String userAgent) { Log.v(TAG, "created - userAgent: " + userAgent); m_activity = activity; m_referrer = InstallReferrerClient.newBuilder(activity).build(); final Semaphore sem = new Semaphore(0); m_activity.runOnUiThread(new Runnable() { @Override public void run() { m_webView = new WebView(m_activity); WebSettings webSettings = m_webView.getSettings(); Log.e(TAG, "UA" + webSettings.getUserAgentString()); webSettings.setAllowFileAccess(false); webSettings.setDatabaseEnabled(true); webSettings.setDomStorageEnabled(true); webSettings.setJavaScriptEnabled(true); webSettings.setGeolocationEnabled(false); webSettings.setBuiltInZoomControls(false); webSettings.setPluginState(PluginState.ON); m_webView.getSettings().setUserAgentString(userAgent); m_webView.setWebViewClient((WebViewClient)new VPNWebViewClient()); sem.release(); } }); try { sem.acquire(); } catch (Exception e) { e.printStackTrace(); } } public void setUrl(final String url) { Log.v(TAG, "load url: " + url); // Try to Get a Referrer and then Load the URL with it m_referrer.startConnection(new InstallReferrerStateListener() { @Override public void onInstallReferrerSetupFinished(int responseCode) { String referrerValue =""; if( responseCode == InstallReferrerClient.InstallReferrerResponse.OK){ try { ReferrerDetails response = m_referrer.getInstallReferrer(); referrerValue = "&" + response.getInstallReferrer(); Log.v(TAG, "Recived - referrer: " + referrerValue); } catch (RemoteException e) { Log.v(TAG, "Failed - referrer - " + e.toString()); } }else{ Log.v(TAG, "Failed - referrer not available "); } m_referrer.endConnection(); // We now have a referrer - Load the URI final String refUrl = url + referrerValue; nativeOnPageStarted(refUrl, null); m_activity.runOnUiThread(new Runnable() { @Override public void run() { m_webView.loadUrl(refUrl); } }); } @Override public void onInstallReferrerServiceDisconnected() {} }); } public WebView getWebView() { return m_webView; } public void destroy() { Log.v(TAG, "bye!"); m_activity.runOnUiThread(new Runnable() { @Override public void run() { m_webView.destroy(); } }); } } mozilla-vpn-client-2.2.0/android/src/debug/000077500000000000000000000000001404202232700205305ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/src/debug/res/000077500000000000000000000000001404202232700213215ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-anydpi-v26/000077500000000000000000000000001404202232700245015ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-anydpi-v26/vpnicon.xml000066400000000000000000000004011404202232700266720ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-anydpi-v26/vpnicon_round.xml000066400000000000000000000003741404202232700301120ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-hdpi/000077500000000000000000000000001404202232700235265ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-hdpi/vpnicon.png000066400000000000000000000045731404202232700257210ustar00rootroot00000000000000PNG  IHDRHHUG BIDATx PA9PQQ."B6eR% KVq5x[by(/$fe_ 3CȤ.8z"z9::ҸqXzH:5hԨQM*1hYbTyy<zUT2 H$nʕ+5 v/em:t 6Li 6-iJ΅ Pee%u.ZxOw999ujhlkFmn@ׯ_W&:th刮_ڤH8"@ +WPjj*-YDev->\ΦM!LHH< xFJJJm^N ]s iov=g :|0Qs2cǾ<e !KV@2huxBwVrrrW7C3PwUyWgǒ ;tQcYv" zxnUɁQ|AaҥTXX ͛7S\\`U'OPFFGmw2Y CF( 1GCo[2d~ӧ 1DI_PZLE?R:ˌUyнGؐt-m6 6xw@^;R~ k);AsWdZ(\Sj[5$ (փr@$>-9MUߐM&P THuLϮ4u')d i9&Ek>ؖ"ԸlO% 4zn9q~\Kx;S<ȇVAS%;B.|KѯRЉ2$49hҪL-_:ڑsZ<_>c73fL~rLy|ȍS/R ?ӃRKޥ_ߗICGӔo$v.M#a'4EZ3]b3OJ6ʗ|+VIZSؕ"\D&clf'لz$3@-mB{{_U4Oqd2Y}Jv$9N~^<%NgVa҄|ne#=#y-}2wTg[~9(~YI%^77;ӈ%8!.|vk-g}tIokg<` Տe5.؅{taK6 -ɓ7.\7DR]&W zş:7%ٻp&fcoӃkfkm84ӯ&bmUl-Hڡ/:(y[VIENDB`mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-hdpi/vpnicon_foreground.png000066400000000000000000000055511404202232700301500ustar00rootroot00000000000000PNG  IHDR 0IDATx{lgO/ R.إV,l2](P:t3d  ƐP.EYP "a'$\D2.>9={o[|<y.b!B!B!B!B!B!B!B! Lwy"D@Ā1 b@D "D@Ā1 b@D u߾}ͼy̺u̡Cɓ'ٳgO~z" 2SSSc^j\r6m2Æ D@lwn/_nnܸaґ>ϫ@Ĵ\TTd8`m}<`s1TD@tr~~9x CW" ULZz5 GeCQ8I'kkk`uٷoYtYpn&1*8t3&$]9uuuxq^hQJ>lzYWT}XyyS]#FHٺxq>~'8?o۶m8q̕+W(-ŋMEE{V2D}i WgZ%V)EZ˽~]4e4Z%{ٱc4hѻSIvRS׫L@c{W4]?#ȑ#wϨ`wP\3#$[>.NJQSnn?~YfLt~oEf}Sy{ݐyb,P_ɾoӹڟFhtPCbۖ# \lY/ `4:DMe 2e `4CgMVV( T0lܹsgע<馆7FѴ<]~)# O\G%Q"S6Ia?tuu]K:?L:~Xi`URz D*8"YD&]6Fe*"]V=j˅e՟J**\2")8յuu ^(?޹uJ%e3 ;a{ή %MOѝY~u^vzO:t^݌&(-"-"-"}Dd5kTڙQ}f:"#fJ ZpIޙVVleE+Htf }52ATI7jf͚IjE-ݯ Q#*>00r5v!㠞h߱-B( T$;! iϊYX{.4 l !5kGkYS'5k4r_Q ut?14] dSeTp]Lc"fzg4 ~2=3dz QnZ)Qo\rׄݢ (kF9{t\>.!M'1֎l`wBe% 1ktT-a'7 Fm `EӞ3D?Oϐ;Š.mwo @~U=WsVYtsVORЪדd*L<i?O.sg,>@LhM Rt˗/{֧Iv@D <^ l.(3.>/xWU tܮpwm"D@Ā1 b@D by'kWyW{ܫu{̐5#SIcjju)}}̌Lh4_X`!ˊl3vR[.۫GY&'?/E*3C7@ۼ׆w7@^Iw ~fܻ<s<_ Cͫ{,mYԪ?^sܧ6{ڙ&Uz̹1 f4r_8degƵ~k/ۺ'Zn|ap bo䪯[?+pXSh@p)qZ_ ,()2Y9/0m[vp~) f " :/>bgWNu1IޔOq\mc̢)O9 ݁꫻L~qۏ|f@T˾≠yw+-x~¶^M j0 ɍy~l^{>}m_@jl ?e6̺3ou{ͤ?K'޻?j.-2Ag^ hL/GLZ}ow1Akk>\>ۺ߄qD oM=l_춸k=n#q1C@q~bL3vNq<洄eoā8fw{/J^̈́3?챇_"k1 "D " b@Ā1 "B!B!B!B!B!B!B!PAIENDB`mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-hdpi/vpnicon_round.png000066400000000000000000000077751404202232700271370ustar00rootroot00000000000000PNG  IHDRHHUGIDATx\ PWdQ (AQdQcqfee4Ҙ7X:F2KJ[T4KTy56?nn=^X bb+f#8ēIFBoHw]`C&L$-&<@/ڠDqOSln}&[`Bf "Rn1re5":&e2 )WNm"qXzF$2٣PD$Mqm1=W&z`-^E9g}OŠk-k>cR3ǕIl-a3IPS&eҽyJ cl@/Eb5 cMi a[.… t9_iǎ4ajٲ%ٙs ֔%Iv8Զm[ڴiNӓR:<͚5s,RWKd+GƆ[͍7TQ[RPP%, :I1vN:ѥKG&a .1qSh@"dʾ}TAx =x>|QDZ1cp qբrQ2p@ 7)==ϟOz.\`uP]W &eY)?SXXX%˨]6 }:c]7)yZ0 TM)vYSdڴi#qFΑ|DF3DI eJ!UuBUwu]xWs;iȐ!&D-Ee 4k.9 k$ȭ@gΜkBT7T7oCB)$$[82w]~"mIʀFV)6VA=GS8!*{7ob-[yK&)y V[@%L 4y0ʻI-oSL6W#pձ"4d oprY-R 0c´ Q&jܪ&:tyzzj^ɒ%zG@ yUbXԓ_&V{$5 8_k׮hĉU=&MI1$OQ`Wt L%A߾}222T]P}曆H:H>I93c9X$Hv9Qb7, z뭷LAё#GTI_YOUzc X@"'ĉAC"ʱ$.m6^CF@q҇K7,P!/iĈ4c )c![nVDX(GQt&9gur$݌kŪ III" ZdƤa- T*Yhee˗q(U*r999<Ŵnݚ[$]XW&et՝i 2Td!ʺJGM$]' T7WY`FV6 F6`9Jp,qKl"{$eʆTCVsRD.Cn@ l6 \zug V@VQ0҇'OYk64s鹷"㲊.Ku{tt{镐<]oGVebsD)MD㾟>@*{5uɺ8 :S:\=ۇ/&XSeWR&FB=I-ɯ~rqe//H;ɯ }<[(IJ̇^B) rMʂNB7?!ot2ؤ3O(a=K5So3+)L῵27Ibۛ CpVØ.B':{"ܠqcsuDmP`C1rZ:#q\$P]),lGȣ]?>*C![gGj,8ٌBqnV5׸faĴ_Y[H*lx6oDv+[/9u=mFq[ߓWה\Rk 28k%M&si^uwL9MAG0m[TK Ku[5&({+yuFuAN6=s;u<'Fp@U6wS t1S6) v. eB2+W^txw!Hj' FmxfUF vS.Tc+J: MUɬcX0s=߁bqk(T]gtswL TN=E 7r (hK@O|E5-*\s.Jan%`0 9w-c}out D*@n͍L8P,=)~?Ws"ɯg^72;bJCE͊{v*nD`RQS3owUC д|/o ,LO.􉾅45qm]B:5 EG͜}:;8z9ys6&[# βh8[ UJbz6;+ \Bxܞ|zxDIENDB`mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-mdpi/000077500000000000000000000000001404202232700235335ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-mdpi/vpnicon.png000066400000000000000000000026401404202232700257170ustar00rootroot00000000000000PNG  IHDR00WgIDATxZHe7m l:/܊t<tb?Jomg[ kmQ0K+ :ﮜy>|9^=}|?l>~t(Cw'gU('ǑPa9TqLqï˶]1\Kt9,u0lK<A.KNG X PCC ؘhm6xFN ''FGGiqqc,$ (=??O3330y$`XNOOjBѢ/Ia& tvv̥eXkkk0O\[[d }y;Immm[w8/ޣE_a& fJz^r:!Z~jjG`0P__|%S n@D#–a8y( ӢfJLLr@7yL$!qe\`6n7566uuu$ZHBH&`N7A.2P *3k0(߈.2T.D'D)DQH- #$-/v\|YN2>cFGGU_ed Y2mŋM^ά}|~ӰRf4yHa,n#CPy,,$|%55p~wq8 26^"?80J4 . eizl4{P@DMLo߾/|uQa|6A PT"hls8`B1`;79D00+ wVƜfF-V.-AFIZ=j4!sϏXja^5+oZRJ"C7⻽djmzEPU VV؛J-MMMI__)Ԗ޼XRR)Ĩ16!_lQ @cB7 tZKX,cIܥ\pZo#0[C"X֓!(҃Fqt :#$7 ذq'x-3vF 9ט+5tYXHk!eV>5e6Dm{ -a7@Ec+ŠJLQ\4oƜ B ENa,`1DZ"`tGaN3[D B'27H-.‰K}%sK +>aN3+Y˴-a BY`K2%uL*=n+wᘅR;"_  h#0#Hy2aw9웷Y<_h-}jWj[^1ɖcP\:-MEԾ r%w}>8آ>発S$+E` |t항O;}O|v9ɚןk3b]^[ʞ^"Hmbm6;c_>`$IY}.}o=+Ry<bɑ/ ,O=rxGAXiT^;m{R*Tvݷ6;S2/{?}Cvy\}" vq֦dTU)G~g@Vp^IhTgy"-UEׇ9HwSm.מ Y`QQ0[_Z Ežϯss!so {/+/J &0#0 ,QEQEQEQEQEQEQ IENDB`mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-mdpi/vpnicon_round.png000066400000000000000000000043111404202232700271230ustar00rootroot00000000000000PNG  IHDR00WIDATxZLUU#ѩ$iSV~8(#0 $"EҔf%5!AD߻yw}?{8}λ_A@@/ CCv{-=x2y0aq,h21ahf*pQ;",gn"!+/fchg .΂Ɲnxܲˢ7+4 g,?ļIa alW:$xmb+X,āe"vJ(l.BI8ϧqϋ"-uG P1TGW .PWWiƁ5-g A$2+gAC %@%`@PxUhC͆­!"qn#1RHt$-H QDF33J-J1|,Mbt E6.;,FCx4;yMF[Ĭ-۵nv8 % q@ب1ćQBkiÜnú11]f8 8!Q~WO ~1Oq/kY! /\^}b" Px|>uI0~.϶6zfsM{pϰ{q m!%D|2 YB* IemIHg*O29U9fL,t}ײuF~c ڳ/U_vId||?%"k^%F@gJh]x3Ќ݅ Ed .:-;A ͝ɟm{R¾Wi~ǔzj|QJIG6R'c750]\")+<]jdh6/ZNȤ %to2]Nmyьu4|#uwq7EΝBRn< #!۸wwPUbS>ߴ3+^KQY3iyH*uKgC|ϔw:nӰI4-uM%(Ȑ|08K魟tbS*-˄GXB7=FsNn_`,C+9I4UQu)>aܚo(0643AxPPƿLa)4~ã\i7ԧWEe'2;,zn<OSbW!^|>~qFR lk jY5 hH u Lx8s xa==<8/ %j0BꮐAlʏ6X% 3tGe6Zi o&"MIZ6J=UєvnW~i_i?lք=؋3i޾@n"~7ϐLJ13LgB@Ccz ~#٥w}BɓC@8Νh>3IENDB`mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-xhdpi/000077500000000000000000000000001404202232700237165ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-xhdpi/vpnicon.png000066400000000000000000000063211404202232700261020ustar00rootroot00000000000000PNG  IHDR``w8 IDATx] PTeYFe_UaYDDDV1112jRY(kMFwe"j\"訕*-eV+⎖n,ft {U߿9{߽i>>>hSH1ѻ$^|^L7.vJL%8ҹ8)ࡀrO k~"Gɍ>fJU_(G *5m\Rkj8t9T/ o@^ (Vx '[*n nġV֏_|ZN$T 33S4|VZEZXXPjj*]pATgϲp4׭['J͛%̘, SSS:}t"(22RV5kֈ 7f.СݻwOԕ888Ȟ;~xQf͚Dw6!ܹs({ D9s&@.󌍍 g wӦMdmm-zހDgp`` @9R4 EIbE&&&,ŚaСE4TGU,,ʕ+m6V':|0K>wƌ|! `Z!b^Дq|̠h^k\ p;w y95nUq%$j;l^2>Ch jxRPP,~~~4o<:p+=xn޼]`˚G2r,C G|ZΝKǎgϞbݲ|fr'TP+**$(Գg϶):Yhh[:ĈS1<<<(88Idff&*$W^duR=\-dI oݺZbbb tMzrnG䣀|S.]Z:,u!` ྜ U4O:E׮]cKũ%K~oGRaj>j;RY RU- .DL<MΝY ˗/ V+T h#uPQ >{ƍ~cϞ=~=..N:AJ*vTU߯K mh34Annlf3gYl2ޯث)ܹSb*PX{Wŝ'O49wl*O %w%W3[_TTz.e,vV(U |W!'%[l+Q_.e1KIIX6VCPm2ԱcGؾ}n1n5 F vZ2ڈR]v1WbEK.m… ELy Ry=h#Fz*7!7kCR ѣ֕#GH.N8\ \-xIea]qZ TjG6jAr4ShK;Z%qdlUfrT 6bs,U BKFo>_PP@vvvm!/G`~s_+R|4HF)"V(S rJ],#gG4}/^,-$Z {sĄc8^@ܬBPEH-Z[a԰-:{°\TlG1 j>JkQ:u*s]p[uРAٳgӴi())VKzh#hA)S$Hme *ŋuR|734H% r*竑BrvuD,%e*PXCʉ7lR>)fhg#UDmGlop};&MMUQv쨃Y?r碰&&p1ǭǝk٭MgJ-p\ʞ>or+ϭƪKe'[ oׂ@E 4MnO4य़s\. ;(ߑoc)b|JÎͬ?B(x%{nc?Ϋ=xy@ W7eW'.#!dliAN$E~r &.<(q?\!&-̨s;x )R".^fFxf?9'J }b |sYz8Rd 123!_I%[33+bלQE:up~{sqIQڽ +T7?l?L A>5)Yl̍@1<(w Oζ׿a3 >:. f>?Lf " J$)d`dHA>,f]PTT=_ <]gnMkD}{m/ -E=" r$H)Av~,8}5_|R_>g'GL}5E)|rK&䠄Gf,Yϴ &֟|_a|J1>agLj!o՛w>ϝ w?熽.KHFΟlw*v3߯:|Tk|*Ĺ5\Xj=7(b%n HYDŽy‡*b}nҞy,dg>K6F;.l5D&xES|0f:c@n=g&4|8n'|ş\~{j(peecP2:&6=\[oM +&TχnmO67LG!7$4o 1]1@đ$dK|0ظ:'Fɑ.aFdad+#Aegpn_"([ ,Ek;;%'|)!~δ>>C+XڬIIENDB`mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-xhdpi/vpnicon_foreground.png000066400000000000000000000103701404202232700303330ustar00rootroot00000000000000PNG  IHDR -IDATx{pǓJx `$"B2-D A1yACEkJNe"cKZ'mZ()5L3"D^vZ 3ax^oH&g9 KC` C` 0000 !0 !0 !0 C` C` C`  000 !0;c Lyy?YhYlSz]^ ٰ:2j*{n矛/hz]x/77vD`J0***L]]9TVV"4"VTTdlbΝ;g>gͦA ,;;{&sg͚eX H]+Ld2εrJs T;dE_`A\d*!@ۘ1ç~jҁU ҺvjyNvi!O}ts G^ JJJEv9uꔙ0a2tgGB`6o'ڪC`7uIq4~a7P}T/m6[Tz)ӱcǘח/_xE}L2#LXdH~M~,&%*ڵ+M23+D yY`'[\@VKؙ;ҰPV<'mvi@0UզD훒k\$>#i%ĕK KUh7y|RU{]>j'M Pfv?irWˍBCOJrŻ&*ɓ'O?*DN:/dO8 n j|u@V[=y.u^e:tu8r:^[z%xD.ٳg}nE2~ D/>y'ٳR{u2z)[t=/-JcVHByҕB(o~~~̡y >܌?L4铰-r>|8ip@[Tf3,%T~'KUGTݹ tɅyTfuH R-$]V_KyNaj_a#?X ,YKebt:2C*CdQfJMp ʔfXLء̡j_2\fhVNa79lNT\bEYjnr4+59XU%%%f޼yf͚5fڭo;vX4Kig ^`Ϟ=3S[SScP}Nj//K_!"N2uD>˅Jqyv"mIA`ɞNef^Y'XB"']q 80 $. }5,D`&W&~uҡvdy.3!.j\㊸ `/۠ y42#NS|BS.R} iHNP3Lk/.^W8'ѭVhx| !0APD[R'J+,R}! ֭[xwDɐa""k hfIH9D`_N澴Myh+ XC7'My jc+sE;.H|@&wM9a>6=vn{)OR+,Sg0,!0!0!0a a a aC`C`C`~13oj`n^9Tb6Ff9;zMSc&<C`^zsCHDmس-:{zqӓV9b?7뤆ߘ1A`̳ܯu,kxUA/3_Z M6m++r-eH_Q-]o C`=JL ^8S߹{\eټs+yԣNׅubs!4E`̿ Y/ӑc&vVp͈˺k]"0euef ۉ-ϙpg;f7[ jv|sT񿭮66Ka>s6~ΚۥBg}wy%hN9UڵcZeظ7jaIrP }6TEgJ^gBt ;@\cF~c/b"+guLNl&vǮ_"N9soP_5F`̟N9`cSNe,J_0QS 3N_N|w7|)،S79Î$C`"rx5D -LN=e-#6MN^_nfO4y=;T{!0z[yYS$:^*L`J#˴fys(=a'H^5ֿ e(玝Zx b/b7-GeZ k-ޚI-|hr GC`i7;F/i^5UZ?U~6ܦ\ A#sv{צ4Bbx!0 C` C` C` 0000 !0 !0 &ev+TIENDB`mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-xhdpi/vpnicon_round.png000066400000000000000000000131441404202232700273120ustar00rootroot00000000000000PNG  IHDR``w8+IDATx]tTUI螺FIBBB  N@ (XAVQDP %=PJ&"Bo}ë́hrw&3ʽ_o ? .\Y!8*q7u\k`UpepFu%1J$> Ǩ1DG I9R0h)2: n )>;#./"iʮjgiQd4~F?1"'%ACHxFdt^FweT"ԌJt>U\L/3cGXb@wt\+C[Z|0YP-]k\ebtsU${yl\e3QlB$$/s1"J=.zf$1'Ks3/;25~XVͥ^jX.s3( >PzJMM!CЌ3hŊfZh7rrr(::\\\eymchAI~]2I]xzzRvv6-_?Nnݢ*---'r ۷ƎKqqq(fRU+]&_8RĉŋW#0cԻwoG]B7GvڴqF_5/k׮ѓO>I^^^d6rw$uکU-]ܹc]|~aG2?^ft͍ʂ/щ'E+H9tHˬgɓ'UAZp9ssln6l H<#wYzጣ9sݻw\QOn՛*4h蔶(K>ȤIЭ[7׀Ͽl2EiӦΨ+o6~J83`ʔ) رŋzTL*ꡯ9C%=z"FaڱcϞ=˓yNuM.ΐ~R*1Z3X%?BIU5(ٞ@ >.O?d7oɓs;III}vE"&^j \T?!/Bo!c`Ϟ=q߾}]Yݟ =裊j_}}wэ7TS!( j Ud^Z bqWF]HFuWmݺU7̙3 |_o_V S[pMvE![kVzH?k A|$_O1͝;h4RJJ /K/ј1chܳd1GpY㚢*c/sG>^YM%&Cro-gÇ'aN:!uԡg}玔?9HXࣦ$'[M @,oGMxQQPbb"7V{ұcxȑ#Aaj@ek.G׮]v0Mzga@_{*^=jz{W0 {ꩧT7j?O ѵW16֍*g½Cߎ#jjGMnn.`=ƈZ4lߓy. s֭̀[4kQ0a=ǹi&  9\D+ FZ_J#2H)# 7QJX^dH9@e ~iVGn-QɶM-yBHKBZ5yPH)WpZK5̚52j KoPc[J.:ִ-ϯ* T?^:=z- 6Kڢ3E-W^8SPӦM-AQ+HC< WA#̡%ЫiMchB:^mpN+9 Fn4bO)$]HbjBk׮U ~Fn8%K,Ԓ  OgbK YI%}ǸW F};H{A)0ªsA4|Հ HO#95J6/EZ2R8QV@Špab"&@ (9k0{XCcq:uJa |T[rAH`g% +DϭL:Uw.aY(66Z| Q$&ҨV2ڛ_7w QFs' Tz{jMGv} 2`zBFDke\BеpmUi܉ gRE _dF"U2ZIÆ K.4h zGx iqQ)/ zlCFFmF,|foQZkE*bk};L?N>]! #ɜ7o5Uu"g / v0+(zLOZPpl,J1YZaQ?F Z&.!l )ZW/@E7?= ׯ Kld{[O O ѣ1  `&"G-}`ez(BeSƍkΛT#cW,oF eQ)W|t/(Aoժ$ߋ4]pglIU3<.ځ; |9vDSK{xjO7)-/s3#SM pB|UmxT(x7H>@"#F[IO)ȇr(5* 1fR3=rGj"P:ގVu[TCHeRЃTJ5t.2j*p55e|o'RPj4 R`d)%`?Tj?D+0RP肳6jʦ4Miٓ`|Y%F^=%%Nc|EhX- HK4a?Tu t:5je$K,^){@O1O#yhоH-hG~B%wZCR~%ec>uBx$$x*ڧ58p6QڎnF%JMVSf!9-xf{@Lh=GԿ쓒sL mV3] 62 ~V JkT>ǂv޷ZV*CAݻI5K>Kk2Sz_M~~p`$FxDt Nz%{ P> #U|6G/]†[`Z1U&t6ĆO3>Gn>^캕ٻ\0q/k&.& (53;z Ij8DPÉ.-┽vgBʸ {q-zw5dcBuNG-Y ?2R^-yvRH~m6"YYW`ԟrzr 﨤&42o1lԢW$:JʻD _?pԑ Il@ zͼ:?|C OVlf˴չ8$X cHޜ .>{8 0)&Fwr09{M H(kW᫓GH%/XϽsk E= UV ~ ab%XܬV)1 \%0BZMtHocRALƤ&o/}S֊sjU&_b4<]Hݿ[Fݚۥscm;󟆫%4*:&8˾'&3/7=*7b)$0R&вpygh 7!͓+7`5:w2yԃR1Gp.88Ыn$  MMS~5V¶sFpEPNt??$MTV6}5y唽n*2C[W+vpJ4g7+/!3$3/1z-VE6%ƢdOUO^M:osC 69a" l@ѡ Eɦ_$Fٜ494J`Z)=ધLU86}Ɵg9&ҤnLG8'9q#Ί!-[,3繧F2 no-*|_~ܕ@]Oe+?q`A`iߛKXnm@oy3Ǝ9`.Um~^j'VCZ3E e+POeZW{3{{{{ZbL-W[:-GD1vC5!O%U'OJoܭg0v6BDҦjyN\%<2@TZV~=yRn0VYH|ooD`iF1+ed]}!rp&ĴRLāD;+[t0wNី7C !I(t{uޒm0rF0*%TB㠤:N<*{͋+:덽ݏ.{~M)lGSETdɅd^W[[NϽXT=p/SlLOXֿ{bi[Ka<[ܔFf-|fvS՝]ݏ,>?+.^yww]WwfoZ ;k q5{ {sa>>E.TRRBvq ꫯ瞣(ruueGhт,YB*i*4bX,LB h ÇW8quzd99@4k L =j 4vrss>}I9pQ 'bz뭷45-[L6Ӄ(M2^/^lD;K1uȐ!6iӦQݩe˖r2cС.]TPE$(( ,B z[3@[>4zhnp5lP悌mfV&V`(]myA ."kE8dj#Fe 8zLZ&r(h@=iȑ(_|Qf$Dzyy`Իwo]?RQƦqo; ''G00P]IB8Qlwu{B} A5^E׊QE7P"$FoP&>յoڴڵk'Ν;%)`*ǾH_]t=ZiӦ4c IX-3ezz7B@ĩ\͛),,LF۶miذa4}tZ|9۲f„ )Q\T8p@7q_~ɓ':nBv>Hm;0ڙV@g86fǨ2AVU[Sdhy`<|+.~&MTԁ OҥK *;ˇք#: dAW۷!H{IJJ 6ȖV\=s}@0ȅNB\z5 僁^#|){CO7BmG|!yS PtׄC?uTe '‡S3c@~5:O2Kf ٳgeTO2V5R GPh[뭎od@:9G`bcciْHZltTZDi>t=:o^{5mi%Hf@D;믿Us]<<\_\<~8p Yi5;,-= N 3 KrZ{Cg`Ȟ—ԟ@&<@ Q|ā"^u01ZC(TD >T֭cǎ0.}Z̜SU"4ָqeUR T&:P3[J&PU4sEH"<:h$I% -L GŘ3R@foh-0@PPkB[K$T#$lƘ@Zbժ~pV`#4H+9(=hC/vd@&'0QU$P&9r;SXNa97c*:X 36a}#uمk+x H|.x6["PBqJ}cJ)ts7)@  wf]q´% f1@ & b1L b1~@]7<oe kmqzu& 2,럯u@Ω5e߅} y;,Nnw3'k{E> _ x57P+})˟.sI/yzŃge@IIsB/sJQcsχ<Roh,)`f!7qX$<7\J]ۿgL yRwEeO`;݈[Ro@f"Phd!m-Mj '!PҼǭ}"4S@rkiK)a:zwmJJ}k*L IKGK͔ G@JReu^gvܽ;+0E\C?vB&Y؏&PpD ֖ ZLyRd .$^oEpDnn_ҼAڷ@+|} d _' Z' %"߇ Nז!|PanF0JYw},ҩ)WQQR#r-R1aIj"8qn #y!Phut~ @0J?rNHY|_w@ft/K6oN7vuتm;JYS?|-.R>y]/ e7w;*{Mɻ/ȡn5 J h>-#.c+(x&!<">[ ?NuE3k̡اoI9$]f+ a'bb1 s!meS}zrH?\ܴGj]%JL7G0;쿉}~ѮJ &_#^[IȭdBL !PQ}@<] "\RPnU%r[K)Kd28(DG{^!;)(c ܳA=xKB w ?#K7I/?:継e>^>$b:]ؿ|3?Kz/ !瓨,LV n,LqzzzШU=kx:z 9QfB&!HU3Ev^Fk[-䮨?\HWI@[KK;21d@FRVdե- k[2BB1R&dȪSD(*,TM3 )L)28 ylId%bW}ma(W"wy9ܲ^wC*%u ^xŋ/^xŋ/^^B|tĮIENDB`mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-xxhdpi/vpnicon_foreground.png000066400000000000000000000167261404202232700305360ustar00rootroot00000000000000PNG  IHDRDD_GIDATx tUUz!4<#!"(0<@]#/[%]Gx<`qF:(mJAkqv"t77qso>ɹ'ZE}>}o_tTAB!"0D !!b! CDa"BCB"1D!"0D !!b! CDa"BCB"1D!"0D !!b! 0D0DCDC ! 0D0DCDC ! 0D0DCDC UTF"` G&'' 2L8<ؔuٟ˖-uzCLzj盝;w>|g/4={|}zq]1DF767/W_}e8:F:~&M"`5||rsq''N0+W4{C ͬY#n]mۖ~CQi^/v`!7a(%%L2=zcɦy!kO h3--~1D俲ƍMQN Cf~z/Ld^Q1D -ZKϛd@T{nChI̹sL2D1D 9bdDVG CDi|Mݻמ!bSY.exE:ʌa"za+ԄW\Ab"] E*|);駟y,Y !ĥԇ~ܔ9b{ヲO>]v&##,((WXεkسgOCQbsgҐdp**ư7Ͼϥ!3!b(!iƼ[LHh ^%3wvf:/!b(.R.p۶mv=zu^:?CQ\sq2۷oСvu:?1D ŕcϦsIӯ_?mk'|m:?r1D 5mΝ|@GgEu7!߿¯ZsvOACQL7#6k̗6lF'!b(fΜ8ĉ}m <y"bjŞFx_m9^y"b.Yf'9|޽˳ㅵkײCQlC,))d4y~SxA!b"}\G:O CQL=3v鎟mxaѢE7!ؚ>}(.g϶e\!qTJ]T@_<3P3"0SN ]:z5 @"EA%4HTJ#=)7/3ԈSs'KM{̟?_F͵y};8ZnmU.G$޽{C8z9rd#|u9MiR_2&bz^Rk ɮ]ѿQZI>Fnv&M2'N9rČ=:/۷c2IV\iQ=m4z^_QcI-zʚL'_lw@ۣdҥjFaХImҹs}D{X_>4V#szLZ*3HO"`+--lڴɷe}a$f-r gƍ~!) Q˗0+lܤ"W6X! D}!B0 Q9*8u9|ͱӧO%be?sT"HȰ_~>-~ (0į(" 6l0&L<[@?wh͛] !*<2@]F{o?}Q; g*`zn޼y1gT\p Dsna0D`Fzl5kfK{͝;ל[%!=j~g_8 bɫ+t}uNPѢ;",՗d۶mv /ܹ-qKA.Y,zzE!B` Qe\ܪç.Ws߮}KV.֭9M !B 155~\Ʃ(k\Ot]ĴBm݇"Ի!*u+z hd*`Dtbo-m?Z W군F +Jcǎjӆ`~A uu|MWۺu+]??@E1DwCt{Z4 GQ{E!n󺬥6ۗyDZ;gb KJJۯµɸQ7/F./>u(}!ǡ˗[= x!kO(<q&XT<ϥ9K_~ϊ+?!~+=z_%(8p> /29.\}֫W]gGfuTP("97? G|,b,ԮO^uӦMPLcȑ΃M/m60ĪV_hKѣ/Be C8e'WT^vf.R?'OT`կ=S0YPtѢE ל42ZE!V4qYy]fiٲeh(a3i&{?[YYY\ZVZe 4GQ`ҥK͹seSaQUJ - t$bbjѢ4i&]4CPRRR57پ}{;v6m5+WիW۟uz}yG}DCLlW_UZǦ[ _l߾:}P/uu=]{ٶbHCt7bTҲ̙cva+ěi$2[ul?4eE+ҹzjNSY#xGGW m!+C}7GE7̜9,^..))?891c IKYR, I׺ZtAƌ?"vZ_7S;*?0D AH_ h^j42q0D Ց222( 5g1DTRFQt }!b(.)E$a{L*0D \ -0D EIl[!0D ޽ 1Dua(@1DTkuAe(731DTʼstcJ}ߟ t4!"/RAF6cǎ-[بu~LΝMvL.][׹6H?S CQBCUwqYJwqUvTRI,m΋Q"!yԕQ\%ũ^0J=J0D[*qd0tUB!b(esvuc3!vt1>'mʘq/vC=Q4߷pBkt<-r'Γ1DSh+GÇ=O&!b(T+7orequ|CQ%7/ :yD'Ko0D 4Dm5_8''v/<1D 1t,| w gJ~>3ht_8h4jFΓv+(25n!"0D !!b! CDa"BCB"1D!"0D !!b! CDa"BCB"1D!U5}Wh{'0D aV&nr~84.+U?ٵ̌T{k>U#̝G󃡵>tU$3?V{\'*!64]ii852M[N߽ *j)Q%f\Qϛu{+՞̽iQIh8C1x"կƤ5D5FMkVC!fa-VFMG{L33W5D;-ɼ{\. ;'K[rMVQ+H3|Տ2KTi7^i>!6dCz]FްLnz}FA~b)מ1#>9Nm+sWa;len{U/\UKڑeVfo1ĆlY>9}ɤ*{}[!;:m;o( gnL#m##9QJGp`"XMT̯M^ߺGgsf}. v#&-yf_}!bbk\({V ZNG95~j !6TCW[o̭F7jZ:>"G^*i滛Q&Mnn޷Fݺ$GTx˿5ۤ}}^FÚ^1h2f-+Ɏ };rsq*3;*.Q`iZj1xXޞ9F#ך EqenT$ Or`"_O ; 5wG!Qz]aN9|`a^:j C !6heR;\\YS> P~Txs3LfM]wyu~Ũs_zC!<}l0C{M̴g~{48)Xe+!j.ה[j\*t4m(ϛ2"Hҟ}N2F y]p]&;nӗ*i/zm.5{C1 p>oJs%jzOFij3Y}/nQZx)3"!VV9=w5|r -oP% JG1D }X(.+#EG-t._%C!z?l$TR]vJ%8*Z1r3A%mTVx/G)|?2šMu1+" C !4?XQzDVT⨩]YQѕ!x驨_GrפgQjѲ CdavRjL(A2D-xvu}7EEf˷QSz 1İ͛(jMRU{_fȰԽxU)}V\(EK]eU)^5v92]TCC)q馽Ecul>gk^ejWN۳쵷6j[x, G"1D!"0D !!b! CDa"BCB"1D!"0D !!b! CDa"BCB"1D"B"`a\LҢIENDB`mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-xxhdpi/vpnicon_round.png000066400000000000000000000213701404202232700275020ustar00rootroot00000000000000PNG  IHDRF"IDATx] Mه̾c4`̘.J/ER#k(d+KR U%"le}q3sϽ\|pY{yx>x>(ysv^eJQ:CrJS9 fg#?h~\~ƛKAfRF(E8Gfs7>B=)?P-(M,$ S95NwΆR~-Z] y>N6`@i``( >f/B#^\#(}iyP @Y_ϗRl<$OiV̨NT~wSHGŔM**&cI5H9J j9!U]w6V>N*-H!uᔎ[zr5H#۶1KUU٫vX6S 7\=Q^vA|(m4UG[ڙ.2lߏuYabm/y{gG&&*$%k /Lir힚)_0"L C,///J233IAA޽;իҥ #*cJQq ~o?0חk̛7|СCرcĉ!Ǐ'G%?3ٲe yꩧ"""$RʸT$Ɣ <$..9l߾>}KQYY?ի 1MDB6tHoFgYآ3gΐ7|8@O-ՙ@=\<`pll,;w.SIGIΝc$((hԙp#)1INN&K.[TE'6l3`wFpշ <07n( F>Jf@Rу^K/29{z,--%>>>FJxpac]u뭷){ne& +*k޼9 /\(G%/3Q T2uT%u͏9B~'_ä( :kŨV=[mԨٿ4?_ClRvm\aƌ,JF -X(7)R{HV1x }|Ai3Fd5f6]y! dlʔ)VZR&M]vI6XU@䥈)|lĚtoI1wߕ:wL~w#`ڡ0\5jAijb.2HAhp򢢢Z6RH LJ=1bQؠA UTn]MyJEr1JLr9U.˗3i7&'O#999F.]h0)s,5j샫B|s вeK iZL(]ndV9TàHõ PShd#nΝ;IFF&kիgV!H)$[FJWTEzaT\[n%ЇQRHiw@uI7Zb#@ }@k֮]+<}駥l! O?TJ}a[n( U)G&> Fʅ=#ҹ0]U1 dsaR0^ ]uCb "}^l;N6mJIpp0"( QWB9R @hD9bSNE{V#c+ 5E%%%Rv>mxVJ3nՅQ-YĈвtgaKU}x-[I $W_&Zyg 1]rID\nT C!3YahVcB}yP73P_5&^6]$+~z9p)pI}t3 *AС3UGb-|nڃ*skC*~-y5H,;>ď;31?KZh Q=`NSUy/w @r5̈T.{aFq+4Qld1Cp]v%'Of1(檺.Ab98XrR3-i}](hC1;}jێ=⪃Q'N$ƍcCBgL2 V" vW~}=g8?H>r]w1$x>c&9B}AU <P9j &C9Tŋɷ~K/Lt\`1 ?$22R5`kˈ\oMZnw/ j 7 ,D{Օ ϖ0{$KQ0Iуň/A8ZFCnIϢ5K9p!5%Ö?~KrSt(G7'x!U \H/ O/;8B t k[8&̲†롾$tM$T`0kAV\Tz#1! 37a@ W WjKͨFGjfYVJ{T-1 @-#Q";eIMxm6`{G^us_ԩ>H֕b ˎCwd) !A^GcZv ܇l2LyE`Տ111LnCAW EGUVb;H`;l!BMoJ0Ӆh^y,:dSYo@l0䚜I:HiT'-VZ-o'!xd[+7=M)EtW O7?$3?V>Eƺ"ik0ud慯n YP18~+i~H ]wuRRvi"I<9n2 gnՁ/kcoBUxPq)zG-j$Gxq|W y\8~գк9/ |ZOxc!H@낎:jF\d1^G9Di0hMlC@!Ox-Ds砷)ַX0IN6`#'BB}"xBw :ԊllŠ+\1 Cjy>WH4{ǘDuNh C28~wX6:8PCrňd?1w>mC{g Ec!^҆;IZ<cǎ U81 -="IP?3.5d Lq=1O=Le +թ- VF@G*]4h֓agGÕj y #Ϸ6ǂZ|bV[QYO*e`式鉯C]"x[2g W/ta\yQA:1U3W9F{my઻ڲ5#ߤwRI/F,i x(A\M?>bW(Ԋ4!MlYe)@/Sx}o>֓­S~oQɡQz5vlMR3Z/9c+I_ w"{XaMF4,אys~9B 9B0QGS7m , z7Gqމ-N>AQAY)yW~3@< 2(xJϬ%;WLZ @PmxumMzYkifm Γ#=5ChFqC-36qaDP+I}{i҃vezEpF<݂J&DZ.yI_+I,f$ׇܥ>%_TڮOz.xwsn/1-3ʋzE9(vOMՋ0Тg@uwj24ohϘW}f%?|[JB'2r*ur5I޴Xj)ZK_WRzvߤGx\&#`Tv F7>K ODz}:5ɀ8t*錡wP$ ^@ۦ:L5s#i0Zz L5vu]iq8xq+ę =Ak??!>/'w_D,wf]PT~R>I_DCT-#Pa$BRҋ(g=6%,R@EG+G7,OXZ4}-|c8f4zff~QF-y ԢI#;@UW aYiZ bץSCZRR74^0>*FyV뢲(17|9]+䱬u6 D1$y@JPڎ& #Qa(# RuOša4R`#~_1TOn|΍j<o3'?a(3yUţfO @H <:xv\Sk@-y죸nʢk3Y^JjK1|pa銦?^_/<5čl$b\DHxN}q[RB }>I"Z7І,m 1IXf {ctr?L=T$>J}2^[",bUEUxX~]Loz`֘^V_TӅ N(4 q H`3FIZ+Egť6jڀQCog1Ibk}):EgF?@e*˾"d`p m:V%lb(x[U]sB0/+زy8J5+wU> σU@*:JUŀCm.xy6N,_KQ+{ʢOm:McShhfX]jԛ#2vp )tWGM:l &tɗ5pF~OSAl,*sxÖu-H$֮ۊ &9ϥ QFF4 ׵Ml3WW)|-jy:({)s&<!7?OnISk s *LL|1S7 o[!q=^9pb_ζViEu/c}*I} L:w> KV*{m(11ח[)\#5wvaKT~<:I$f#ńgA1Ltڀs51v?SB]0B`*p J X HOl5xƖZqn#@ \`XS߈I%YH%0('I+ 7^[[`,T< jwM7mYq(H}#o8g'qN\\ F&m5,{$da0\PB2UR{ջYnb[6juܞOh۴;f.ђs|9XɮR;p'~0姂w'mZcdMucs]m4@5_xɇ{$B*R 4C0 T9\BC #gtնqFqB]* 7-RrMJ5Z*)xP,ޜT .CB*$Rr)7*`a=L2?w %h8Si47TDHNġ,ԈS&G O?F?f,?G?g(hL>i~8 1n? `Er %ߢ cssZ'hPJP `98԰EY%XkAr+IENDB`mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-xxxhdpi/000077500000000000000000000000001404202232700242765ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-xxxhdpi/vpnicon.png000066400000000000000000000146011404202232700264620ustar00rootroot00000000000000PNG  IHDRRlHIDATx] xU=$P&D/0Lwtڢ(=CA?V/ J8̸0*@;׫ ///ɫW{W֭:s P( BP( BP( BP( BP( BP( BP(JHQDHáBx((~/`T|oi SAw"邷"4RYG|' ! F@ $?ã(诿^0{ +u-p.[@ys|9Hӱ(Y 4}L b)'`$a>P+cLSw_)TfSkPoI&@5] iBKe k#7(\k p @Ld#4 l&hMפ"K$ 0FӵXY .̝Jv`kBI" Kp%\.WcKBMd!@H * C=$l"?.>3qiễ Vk.Ibɓ… LTTT cqΊ+D߾}"H&#֭[(}]d8s(..VM"%%E̛7O5iM+7[ H鐑!6l .^0~H@In=īj1bZ١4I&dEněoPĵIѐ.vՠI\m H+WW\ipׁk h @4(ƍ'FS~?h0$''cǎ5x6$@`Μ9˦T|ȑ#bǎbg ߙMe$ `?ѣGM)'@7cccD8|ڵ;wz"M=yyyG֬Y#:vXg:18~2@HKVe˖Ja8(ж M#{nѢE S1={&h# @؂mr}E9p@Kچ6$-2ݻ۷Pt HΤ)S2I~K98ahc&ɑ2{1#G|QF&H{ )A,_ahc#dxu]q̘1;vXCD l!jHxs mF@R=x!/H/ `[[!75"k$iӆ !UVs q$,@K,1j*ѬY3cqѬP@$0`$5qFѾ}zAq 5s 2!&&Fۦգ>*Ԥ5933S,ZH|~mBHvqҥK9ԩSji7xC.L>+HcI+v⭷$ `)iӦgJΝ H @ l`qoVOm#Ht:ԩS7|#FmL6#HC#?-I$P~ l&g#Lʏr;wH h(|ApPHF$Ɛ7_SLxadg{ƽ }>C{e#GI( X$^oGgB0 i_3gZv|DqϸwA]}>D_OѷTuV'JA-YME@ ~~+N祗^ oXcqw0 `[%%%97Ć%u)-ڎ7CBB6g )Ja<=G$l{r3A[٩.R b>=˰Y `#=(vHI1ˤp"NPI<p%`_l}6?Y.N 4ij>6l_۹ oݳfͲg/l\Mu5,<\2Es όPZCqqTvMJA `,gS,Do߾̙3j (2:(5jk A.Y:P U7#,AYG$Uv'Tbw Qu P̦t` ?Mpj_#rˍH;]bIN5KKK#Ӆ  jk׮5C!*1DArYcLH.=Wy[&pBC5J iJj6}l 3$L[l14cigT2yΠ0>[`́f<~eHrMTlC`% hfd(a\ơGe(uN Uڂ7+k#&% `bsӧO۷o(TaVݱc3I gGXW5$X r[% 6$M @4cI`88 j% EnPz\Fܠxt6@L)v`c ,S!7:`*+fT$ ״H4B:4Rx[W:4F'HA%fӡ^W:4RʑZtFZEFb}A y b@.XY4AX$/!}AECX1᰼N& R BwcL&,c \X.Cɰ@_J\oCY, J,G@QE= P†eQl(KU Ŧ`ɲ5L0Bq.nB2 ҈x(7Y(( 6Fl(ڐq 'U嗥gq\.I0(mw L(1GGy l`ǃŦ\> Cd# 2$" -0c;!l+d%mC-›N+f BER#s6I^H.Frȱb Nb @ol$ogu 6:4[b+S& |h#rMj F.R 0iۗovs #/(E8LaԩSG$@.҆ܐ_\F= H \Ȥ0{0sD}M4\^A4 lΝ;' oLH U~ ,Ey?)$ (5}[G*TiCB$3f6m7߸$,@ T5yP*33S\P7I $@!8xP.3kf$f-?H3Rs̤2RmC @$`9E/#& lICƤ֬q.&LJF .lDQjuF6DÑ,U The~cP`Toh#L}_KQeY*nXyg^J}N 9lEω*1ӭbAQ1"5q ]mD[KC=ZqxӆC^fgqå+*&UaХ e=i$A @$ @$A @$ @$A @$ P/6<0E(A佶LLqw}Sd= y#뱟K(>t_E?U>&gb%$ `5Q!><7"…3%^tS(~YZ~\פ潺LDDGu>g xe~昪cE$ ` VdE;EMY>n #Rg$&Qe4wP IjE E ! =/Rzw]t +"˵վ{yF~AĴJcEѥ$ ` }^L8 g_GBBqT'cY}68E}>-"E}Ɣkjߏ;T~_z@d?}]J %H P̙aOG$uo^ lAk`rcEĈ<oޝ+okpGKIeI M =V8XqzPti2]e *]}WUDt)f⁊_j"٭6 WnG 7"uu9>HEn mm[]_-HMM^n^gQ4TN8o]qAv<8J{<5'K{6e`d:8Y,F&1]5<+ۆXOowsj:W ~):h~/׫E*Y?MMLRSCt7mn璅aNZo{7uA!2WL[ ~}NJVu-##Z=)h7# "Lv'tL5TH+ s4x (C$,ֈ'g:<8'Y&IO O7?>-p)NFåg;Hѭ5o@b"{th۶׋f e¹a􁣠gE~+V\Vt蚦sR &,))VqNjG}V$00_-)USuM"xcbQ^AM%/kS* D JE%tHU-8]NR]%j!4WhZLzYA4//_i&* (]:o=_Uѡl蒦SiE2i2:-oE(Y|Y0\ r f@.* K^7mYtSt[ҙ? 8]q}-` _.;֖;= =\с7VO/87?J.t.i:%@.Zâ;EHAt8Z;)cVs gw$B;:]N@7#@gQuȭK nI9W _SU$ s:3'6O rt2 nP]Qu'j:YuDxxrݣrҒzƶJ률7Pt!:[Gi:xe!jm -:5f;!.t+)D7%&Aui6\j`+孉5.t#E;MMk{8WYDͭ"$̓4]t#FGSV|ox+˩ޢ&Bwj:FBNC "AP( BP( BP( BP( B.$sTqIENDB`mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-xxxhdpi/vpnicon_foreground.png000066400000000000000000000272751404202232700307270ustar00rootroot00000000000000PNG  IHDRN0*.IDATx tUØ2"^ Jˠ c@=PFQwT^z!-r4 ^ y9K? ^IN$dW9[tsNU}q !LB !00B !B``!C! 00C! 00Ba`!B !00B !B``!B``! 00C! 00Ba`!B !00B !00B``!B``! 00C! 00Ba`!Ba`!00eZ RJjgyլYjժߪU>;;WJJ5n.r?~͛7V^m[n_vavro7}tw4i9E``ȋʗ/oճ}ٳ}va;r}VTY}G69smka`L2VJԩ3_~3w>S3b`DVZZ}.0Qбs=ظ``(Ure:u MO4tL:6# %ʕ+g3gΌ`c12'D5jTB 뉍=91 00ի}ᇖ,Xu\{ 00jժ O6t:vӠ ?tcֱ+>m Y#F[w}g]w:00P<]vsNKvTZ6@+V z)<o\600Pu饗EfO{ 00eؓO>j^=>|Ur[׮]0 00euɲ裏l֭6w\XY/b}ژ1cܿmٲ}6hCSċ.6:5@{6 ={[jլL2}7}CvSԹa.  Etq /QS^=_ Y˖-i+``(j[=zԞ{9ӧ+vYRb|GMjM܄ h+``(JT_{HѼyR sECZ8=/Wڵ-Ѿ|sf 00)8nۃW^mbڗ/ttNl00P@RǥKz Z.S>m)Ѳe(z x=.5ssFC!I&~o![n1oۜΕm =zx%KRD5,]Ϟ=i;``(im@ 00S6m;cM6 شoQ (exᇽ<׬YcUV تTb?cS$00P Ure[rYBhJ32r``CSĉC?I&y96# T[nرcC?>eΙm h`^ƍ |sa` իW{y׿})SY* ,镒@эֱi ,rlʇHuf 00r"߹szygeeY5B;6{^M猄``(D-[N/> *S6moɓݹ a`5tP/99:.7sFC!cǎ7xl۶-h=s֭ފZv00PتS#rIuL|}iڷtj׮MCaK6m2_$4dO?;g 00&XnUӧ;P1ccѹ"=T+;;[O?Ç{?CΑ'i3``("[5ݻ222\r!C}d\f00PD$XhpwQFj&mkȑg^ a`SРAE6o|x 9أGkРhBKMveϾW_;5|x뭷2|s9[_{֤=./_ܞz)۹sy㡹zF00PT{Ǿ[ T^΅ {10PZӦM1pefh``(|Ӎ7Ľ 4 2#CŐ .BgV^jQNN2l;)}Y}a`7aB  Eռ4l~@ Zjh'``(Ær}g8V}jڴ) Eu73lXyּys yin?DZ Vݺuc  EqPk0؞+#600P{^Ǐgذok׎:TI *Xʕ][jj3jQqK7mHF~;{'u֑5/]Cv66b`y1+=iҤƌ *T{ʕvZ[neff˖-sɓmСP!Uq ΋PݫDKTڊq:ucǎCmAmBmCmDme͚5ԖԦ4:ijkjsXVlҥ.!LѣŞQbݏ>ȕ5k\nݓ֦RON66=z뽄Nt u-hgvXZ׼Y~MmL(jsj{jjܓ``'5VZ6lp"%-r zHeggmŮĽzg,wؐP?]OFCaԾu"h"gyj{jׯwmRǭ6ʽos6|۵k9r$C\{TpQs&=*Fbߤ(ex饗kuHag`d۶mx] ݻ9,=aj /jN$ag}.b7|ϻ^@Xh _U^=yPe:uv0QoO+\rI4ٮa|'W ΋zaCY SsW_4DL>7.Jhݛoz/<:Ojj CURŮ:7TtPJhخk׮6}t{K}L6mC s0׼t>VZj;w#F| 0U˖-](swsRsȿ-Z l˖-e@sꡜoaCmiQ*~޽{꼪mc`%XF^\ƆҌK,OÆ(@ClZץK7vX뮻xI}V]m#J4ץa97Զ~m10\Z5C{ kQ8!+JK·~|K 00NQ9s& 3ldM' W;vD*TPT~ϋ$gÆ"sR8)%Q+}00hKy"Ұ!=cteH4Ewߝsbe`B_цɠ6mڸD5ÉԽW^y۷*0l y1lxLblɲP95h ڶm2&@~*ϰalZÖlDXTfMA>чJ2l傏AKF 5cXظq[y/-MƇ҉ YGa~)hCɊ"U'A4Ħ3J˰alNGKvt$P"@|lX3L( JTV\igy&Xꪫ\rX9dZ6}yi ?{&## ,,)kxVVO<(Nb0+T*ټy00BȢ0lذmh/wyq={M4= ǍIEШQb^Ǥs /26~ئLڈĉm֬Yfז4tCP3@| Sfffv=zU%^d 8Еo(0TQKںukAjƘ1tZK-M4 -Mm)|PuR; P:u@ntg־}e2HMMu(pZn]UysTKX5tK2ԩ|0`&^ pݗ_u)EbŊ<䩡ӧ>Qok׮Io`*s]S][]SS[۷k{jѽիnn C _KsRJ6|pWzz& 5Ե5-[ׯ_=_%S``Hoyծ]1 2{~RJǮsݻw{ GWiRI10М9sZQ|*/oȑ^:4ߦ ,svʫk鳭{sy^=Y))).QPo 07o^ҔH$tt ӧ׹tOފdc`F"OsF_L{/'̘1#ڨ9=c``5TWPڲeKM{x^G(>FVaQ_ALta`;wP6lAyA&V[XX``E +2yliiiSOgӦMZVF%8+r ?;va._<Pttm}ЇٴNG$-c``TV-/aȭ*amS]͖6ta`a`60=1h^200 0H !T5`!3``*QG'@ișyTRH%X&UT%J İJ 抔9v>H XSQJV= JQ LUW1?[, qg` Zh`(O.h"*JAK W$?ʷq,*G>/Gt h+Zc+oS 10Hx̙7{=\P\95jz\yyPuL: ePؼy#v]w6e~y][hBХtqذa& ) {z@/Rz뮻.(8>Ai?߿яhzWS{QFUW] LV\`k*0Ԭg}jyXbE"ķI 1Xly饗p<^QpI\_70a;]deǎn+aV\rMPU0I LRƍrH%U/ކ |G*G͚5Z$%[IPRd… 㭒aCߩ7|# t:ޯ_@R5E =֠Aa g߾}. v-H˝>dZAovLƗ4Űa,nD q Ӯ<=c6]q9ٳgk 8煁ArXX^\Ѓ 8| Wٍ(HJJ[׸qcW6i}W(!u^ g0K;Q00H>;ܴzjW"^PzEUF%k֛hFa=mٲv꼞h!ouw]Ѷ͢e#ڰj:7IH˝sΝ~{olժUn,!CWڵM>^~e7RO-mS>r:*~^ ”``Es*]rJlS(cM72/ խ[U3^n;OA>TXTUi6Tb0^UŊK/?O*Ys]aTfCob@z(eb ET*Ţ?1 '"Қ0KDqPY6CVfX%UA 10Pijm*;B*Lt.tNa`ࢪaX۹s+)|r[`v裏{3aj*JCjSU=bp3. ZZZa&I|r1곪dx+0 5* աR AIe V:gkԨ 46ʊm6wh+``(bRCy7ڼyVZTJߕh[ q΍*'V00PĆ/^uP޳g9g,߮m5-b EIuֵ۷{ݻ׆ ͌ hR6"ZvvL#F~Çwрh3``( Rٳev9hBǢ}L>\'a`!OR}⥗^:uv<ڗ I#HG}-rOÓmX\i_Y*HCF``8JOOw֭1iZ+7bǎi;``(l :K#GlĉפIoNsFC!'9 #a`<n:/n!{X/Ǧsa`;P|*l62Yef-V!=|m;g:wOnp ,CxOnB``!B``! 00C! 00Ba`!Ba`!00B !00B``!B``! 00C! 00Ca`!:JW'q00P+,_[GEOk :!g[[nO)NN-,t˧ؕ?VŅ>a`Sge+2-ʝnTϵ]k}?y{w(پJjZϷi1uxn?W媤%[ɨj4?}N.siNIe+_\κ? ~ GSW^U;R~NO`d{ kaҪwV*]3yλ+SLF__ya`bX*PEBUܺVj9XӶ]2t \J%Rfu;:]59&rz5Sm;\kUmQfX=,Jf\lmE#Z[YjbӪ<-=5u0D8l'Gg]k^̱g4Iu"9h݌" 00Tkx6G6+!]4h"io;#1NdžSvOj1*S N0 ʏV[Z13#/?ڼT?V ٩i{.6B20;wzT}#&xJ:3 R͵9=MC 00plT B#xU};ݚ647Z_l4{E;CAρ r ~POԺBSE6@00.9mfJ/}F!S+>Cd`dt^ۗwu]?҅nƵۦLZ&%Vߏ*;1.zy.nr"-dwmcsfyvً Ug X_gEdCA7Z+c-MP\{<'?Ωv/iye1ÐG) Qz3^х=եV:^xQWWv%_Y՜9>10Pӵ~3q``(E^K1Km8&-ӴQ 00W^+ {xv/\sVZj{a)u΢%:CYʦTpy rdžϪf.ywk`.Ow eKIz]<k43m; L:vy_E}S=^#ݮ։fG``I{R'@o˟sY%x :Tl[}H=q (1~.1ȴ_?9R_  !a0xPiv׀B``! 00C! 00Ca`!Ba`!00B !00B``!B``!C! 00Ca`!Ba`!00B !00B !x\qIENDB`mozilla-vpn-client-2.2.0/android/src/debug/res/mipmap-xxxhdpi/vpnicon_round.png000066400000000000000000000275601404202232700277010ustar00rootroot00000000000000PNG  IHDRRl/7IDATx] xEL;$@}%$`B .rx r ܻ"+\"}QYPAQnׯ*td{g}#tTWޫw+ż˼˼˼˼˼˼˼ˏ?ZxΟe^ehV hX% b2yyL;r5R*LJ-)Q*TLbY'?p9L0/]n{(5ԅҭ< R:D3.PLR9%©vs?s(.7+@ȼ|<+RSJ)=FJ)&n2yЗ>/&3CIosj\vL!ɯVAeNiMx'se0 ( U "fD3G;:UC=ɔޣtt5Q!.wZ͘9 }v}jQ\rf߽UT|X́+UIycTpEl0#Hu{HJ_}u _ٶ1 SUHJc(}jG_RFK5骝Ucu(S5>V\aۑ5A>61 l@=O |cg>i6YMWc?b~^LĚt,/Nb.RJGeeJ|T#|ͺQzU)3k* {sd^:<tTw sbD: ~-Z +W$ٳgɪUoz,eAke.SyG>c݁/>DW$PiMc6~V([w}аՋmx@s5 ~q72rB/UՉx{Mi B_'/}L)^%O?HB7/gJH+ K&/R͛SxIxxFElf|. 7K'Wb~xqVgjj*پ}j [եG(%}RUH`7JBCiY5e#X3<3|*3 `_KpIh?""۷%dΜ9C,:000̞= #0bz"r9={H=Hhhhݻwjc}EXƱd&1yGu%IӦM.ٰa}j!^aI|Ko?zYW4k֌a}汗`g0*$>7|^; mڴ!ǎS _~ 2ĥd5< &R3 pBIt?hРAŋGYBJҥKdTeciFfq!ƍSeb+V@[ϟW?0O;˵m^_*ȬYnvi61/Bi+tbCb(d&)`eZɒ%K> iiimM@0Lm\?#>|@-[xvt.zw}rWhUFQĮJ{}I]tb!+m*w` E",x,kw~-\P1>L7oY?_|;R-|غc/?bs;A4ib>}[~-軏xyZl =,_Yʲ( 4٧6Ei4}F}m>dc)LiRر#9sb{ gD%[W_)33߿_U$v.E,@[J}F}e٢I}YT5Ѕ ȃ>+]~b+Ħ$mMPWnVɹs#Uk(jPj3X];ct#JK/B-A8VZ?57"0 8t+$$?J*9)0S-ؠ1MABAuH>ԾAdzMȁV@†H aȏ"opu"$66VӔ c1a 1S-`1q LRK5H?-IBKQ ˖h Ց!ID R$;Hiif/c|' cU]wǏgsx8N.I)cPe7VmZE~~bΝ:> ]Y1bfRj ʭ` 6OZ U1G+I 9f\ F<2ȓt̀B:X4lؐuw['O3xm`uBZdwbClƌ,mB0G۶mcs& \#5h/ߪrjDLOX" Sk<ìT"vvx'm=֭cY44`VU #<50ZxJ%ˋOH)x \Yz5IJJ8RZlItBnf2l0F;܃3#U@ suDn+hw(g}kHW_}RNU]!N㉕sĜ[$0:cת,?RoС8YO'@LK\&7R]6 wX"/u1BLb=s:$0q00 ahтFW{`B"& !l-u*]c,1[(‹6m4\q aa^d-DbѷDbۦ["іHH}oX]1T"\?uTM b|S<6tk)NҔqMZYtpRRJ R1^RFPR@ {0(sYQӊF 0Q}(2y\z?(څ]Fn c߻8#0,hy]F<3daGAK~k?;8ԏc8\)t( FP0^G0{>E;J`Y" @"; ҎV.A7#NxDvoHssy(v:&eQ(s-{ zB8H #dα$&EB,IG$!7 w}]7:|D9vH0K5mQKH@N^H=dK:{H]P{`B7ڃCLIwʱiQq^4 ?j+J_RGm}G#D<]zL*rޝ=&I^3D<tUBC EoA8,FXZH)Hn0.R:()څ_ W'qP6 s$NcdZj@xb'h{4555۷c#r[g "Pݴ<mEjpb# ꊂ9As停)).嘍8*Uxb5^f­j,Ģ"2}t=~x9s߿,Yejy#ԦRc~:NR!16#Uu KЪ(-6;Z%!?~M?G86\u:r 5(5Fʀy3PM'̰%Psuo4|;Iԟ`jܚHpeJ"ّQ 5 E{qҍ}s Z^LL%pKg/L v1B-RlwF*܋gzW;yC ?6LZ 朆W[W>.Z NԈ۾ގt.uW_}*‡A"> bV/gr".FKijNv@-|h}՜'<9g-rõ닫y?jN_DeeԜf;THO$zJ{p Z4߰!D)PQeŵBk@},"\ث˱[1,NAp v_[{c+R)hSi?w-U6ǥ]0=_GheZ"MHB%Ek^!?SDI4׬h m*wTFqEBR.YGsg`.xKp0PM-'<[gIy5u~-r^E456@>l.,*i:i5}[怽ʮO10*R'@Fƨ{oɁ7W7 *JL2% .ZӶN\<X IJw_egM;qf= *VN;_nNI.R9$k]8(-!r@wK{@TH\]>q?6e[I4ITJ_'älH*] PXZa={2SʶIU6=s.7w]5y~<IAQAXv4>?*`  bvSAA<ͭ~g=BņĄ704v>{uXL`B+H_$SxZ;ή"G(!(5ڤaz|KW$7I;\̼o`b!ߋ"r~ !e5T; VOk^!R}@̿y`c)c+nOmB[*dRH@sk`b)H31J$G2U(Wa.=]٘lVӎwT93PJPJm3ܬQ^rid7iMߙcNLNB\`\ [:q7f$G̨ Hc+3ޠ8RW%.Z/W6FI,egHγcqDKUGr-}6?AW6&_Ңڼ3ǧIN!Q !h̬q笏Qܛ$gXށU}H)5SH+ܓp}9nCFs"@zeC= j3w*7YĈ7z)i?D4H).O^^2Fm@JT)]v)3>'Wz YK{'u@~~N{.?KWkwv=hVmRؚy5)yo! IW,(K{^>Kto>o ߽B VO&cD`r\SLO20)Zd ܿݹ5+c^m[(Xa$v ; "&5/8fyehz5YRBIe*']=bs~@OGާa鉕l5H&?Xh ^^%k3`f0!8z= J OKl'sd=)&2@ 1'HOgH=I@x mLkUo 7>ᐠ {z oVFco$6NtOɻwHWfBjU.GcC+Z3z)F MnR[Zl%p{S*$Q<.g }[2@@X0ivTnvOrM*~ ۪}緯ضM*V fnHzC_vú",>rIb$ LaL1Żf0&(7 8\Yp3TZ>]$4--*߅k}}yTKV7ui_b]J*P(ɲ "ν^~V=NudNBUU2@wf6ެmtQH3IU҇^߿Jo-0SwqA_DnXuUXjB+d_ԀH–]:lc/m*;b,e0N N r H!${1|RUR&}O)X*ޣpTN̶aub#} ס$?>o߼;$5;}ܣak4K"[ "$/X3e{-&,wjT"V!]HW^zzL:kXU2Pql~35g [# )/]?yஈm`BL+ c9j m2AM`ZPuyϱ]/&PY5!bL:vHUAl6(!J]ԊZ1@ϓf*}yF'WNf>i4v0xq6F|oi*9q/r]?{:hZS}u>B#gams*t@, ?;;n{[f\ެ]WxUo{ ԇ2g\fjw ʞ{k״➘I-#}7&19dsy c"g jlMnMur֥%S=,%ٽPRI2g)/SjZבJ4zEf$up![2aoRQ˞3k|t'qu o ~L e7ZAYp߂sٲI7g;#JLswz@sEKVAjԸzJY]ɶ2:ny.kr}_ &9NeQQ)1@jڨL2]utj),!N٦1~~]|oCWqj=%gۂ ~l5t$RDIs%wM*HJrcj=/몒ݳ{L@s]LWOkRt1|.)MOvA8%'qC%|5h9ߨtXN*=dϜyK&k$Ɩ1k9u\1}7DU*] Ƅ1&`kRF2[^*u8:w%5Ʊc1溾TGILMRj l *2 ,,/9`v/ݳ/^]+G5dj5pDr^zh~NCckM*t}7]\ wqC=5gBL5gDvn#l&wP2(E"n@xo,<JrCn ߷0)"^w[-U_bc* *~@=jĊP볍[15 cX 6za[X+cDy2cI)+S+s;4}t~ܨLc}BǯM{1#sC̥9 !<=BYOĭU]ϜEZ6&بUMrzTP$\dvXXw{顭YO`D1{TU9co!xGGlU`ׂk!rSkwJ?po:U#6s=c/)O FNf*Åw>spm}Ie8z* $^9I|,ؘqFƲpJWJ@ub&V%57"9Ԧ#{['g ?c_NJ~rҹh˿cŻfXg%*yw)]{te9qo;l~`x҉[r I۞߀:_${IX_7MeOI{ƭ Xb,Y2a؄J!m^%)S $v~> كݘ;fg^l+\P&>%5}ΐHdnUHZ&kH*ax"z! ti]cYɵsҒsШ62QF8mm6iیl{m/$uy;A$W\*V)3q!ɂ!R*Q3ш f3&m fhq ͮLs~x ֆ&F2o}I>97/!Pb3+DmV$qc]t&PF23yxi7o&> //t@*3cBr"0qQp~&Kx"oJ$=7/MA"1DbK$R#}0 Co=7/11sX%L(a{$Gl@@7n^cÿ fj 7@)7IENDB`mozilla-vpn-client-2.2.0/android/src/debug/res/values/000077500000000000000000000000001404202232700226205ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/android/src/debug/res/values/vpnicon_background.xml000066400000000000000000000001641404202232700272160ustar00rootroot00000000000000 #000000 mozilla-vpn-client-2.2.0/android/vpnicon-playstore.png000066400000000000000000000531361404202232700230650ustar00rootroot00000000000000PNG  IHDRxV%IDATx _cő܊"5SQWn+MDYHT*NBr%"2y(31+s~=kjs>{ `< @ @ @ @@ @  ^??/ҥK8ߥ;!l'N_3ݻwۦMlŊ`8q%''[vVZwwwpȘ1.\تUfO> >ܾ:d'O~N:׿N7)?G?S?[Ak޼y6rHر]wT:Ϟ=]wu#СCmnEQjs>{MKS[נk5tlٲqtu:#}'l„ ~zۿs៫ е׭[gǏ:ص^v(@Yc͚5aÆ={رc6/kе}wOyeWH{f͚e[lq>9֭[믿]{ r֯_?[hܹӝGi{?5MA;v%Kݽ .so S]V+w^wz b׫W//'5!0w\wQlY >nB\ l߾mc6el۶&MQ3@xΧ|ի۠AlժU.;)s@wqJ~%K\3fؾ}bܗ5t,#.]Xbb"Vjښ5k}zQ+Gs,v 6m{gzw|F=˗/oǏ'߳:zgzwz|/Z 4 ޙ1+V͛񦞚ޝ8s-Zʕ+ۿ/$pvoRJ_;>}UXѽ[qQs۷ۿ 27o.2k׮;c,>G6mۻw̷*TpdKT)p޼y(oefÆ -cjzzT @&M؆ Qw7pf̘Av1`O}^O;wd.]K.|<"E… ikҥKPB@JϞ=]8^mnݺ1" kکSX0* ۡC;rHλ,X`cƌW_}՞z)_b5;߭ZN:Ymܸq6|W;|gJ rɠ]?кvj 4[nJ*v?S  Bg ŋ{V\i}J*]6zYfu;*/Z#dX@ծ]?U/ZȞx +QDZtMJU\雨Yf+ PW~t_}=CpMW&] 6@ - gϞskTk9sx't o`@@ըQ8_dlrͳֵj'@Ev|tȥ@ ޽7Ajuֹ3n8@5{)0P .up% r'M#R=I+t]sW^q}'[!1]={ĉ4OZ =ɓ'xWv I{׋.W>!"]e6moDM(  'x8 mǥ(AM`}+f;up>﯆;j{QKzF Dm9(E[F.QwyDzg{ѽ4(ŀ*UDW@@%%%ٖ-["lzO>~'[nUo&qMQ6u˜9s0E3Dl۶ &([-SL>c@cǎA kΝ>#iڹVR6oigӷoߠd_~~'ftMaÆH;CZ9y/G> P)CTukkFL6L!|ԩS#N~+W\KgSD "l֬YcW_}u0N^y:) O)\͚5+̀T(攸#GD̞=};!@xǍigָmڴ "@{W U^ tֶm[˕+/ˎ;xFjԨ b MLLKzTpΜ9k=^tO>ؒ%KܷS.*W\qvg8q›>rH׮1=^^%KzkUwþ}{R㘆 TXytvީS'+X"@רk5rb;wʕ+ȗ/M6|m۶YǎcٴMרk5fɓ@ v} He~ڊ+ɘ]77ѳgO 7t߿|={X]Z9(Tu u{׼;bIXJ@\=uT:d}թSrqI~ZZƍW `{y b'Ooֺtrw1~~~T_*gW?.@…m޽^2&Md7ܹsyZ lyZ&MO?u[>EY@R\Fm 6ti.(/!'87K`l VjG8ѣGm…֭[7={ߵ-[6{6w\dϘ@ ًu'S5;ݓ|7.kEN(`OZjD^g;9?;{lR| 4حhO?c.^@]/^l?mر6j(LJ~hSLE֭[{į`, ]p [@yOGggտ=m(͚5skBq|ؿ޹Fծ_|y,2s3Z1b"Y/ȸaA?D)r? [t;vA{\^z1-YĪU!I?gΜ ?ў}Y˜93!9U͛73O0ϓ'=3qFa[XؠA: 4篕na=H@@H_pR|/_} +V̞z)[z5^0`??pŋwժUx@'Xڵ-} _r%^0PSɓ'8rgٽk3fdl ~WgaoO4ׯ@@(yJ#ڟ5jP*3vعs 6̒+`l PQ.&_r嗻ZkS'L駟l˖-kYrH;q񣱭1~p<^!k<4k֬vUWV pgJ믿*UdUV/Tb+Vtk%V{-wܮj,Y,C nEJW8.سSN[7?1111|M7/ǽvR457h՜Cs)[xؠϖ-ʇ\5hZnm{ T6nzdl޼V˗/ٳgmn۽e˖VfMV|Μ9݄3شZiDDKp>lfͲ&M8*UcMcNcOcP[kժƨrrr}G}w.^fÆ n>5hWE^KsܹsݜCsZ|n\9R, _un7o^|n +5ԩSi ۵kYfΜi b%&&9SL6)g߽{; 5`Uƚ>cn Jklji.QХ>9s4iN`K+]VQUϔѥaɒ%6rHk׮ۅ Boj+cǎn;:4ǒ1/ G}9_9֘K >Gb&Q@\k+M }M?sٳ7NZHhG k_رcnsc D13{ΦMV{S]5jRIf 7?vp#G5)H hg@g璶 &(t^crp*鬭mTڷooSN{9kϻ #se͛7'ڞ={b힒V:"]٭W^.@ rTYh}mP4!Cl޽rq |A7DZ#қ-[攳VBqLu=)K_<.i T!4a;zhɓ.UQBZ%~O?ucBc#߽4 hGCh4)m3uϊ`nѢK+|m}]wcjժ9!cj[䡬%pլY3:tȍP,eT>}cAAyղW4>5(B^*IzqAzNЙκ%z-gg)/%84ǯؖ  }!93aw(>H@JG`ӧOw@Ȏ+¸{zWḞ9sr\V'E ek;{6mdǎicCGb3fp,@ r4񩠉ژ±'NآE\zI: QN1GVlYkԨ"fx4랴=qҺ:S8^ iPB0;CGJz͛iRS^} s)"o]ڴiB y>i$EbhoV ?LSqa%%%̶EYE_ݺu4wEKrNrʹҾ!ЧLb?kM@W*Uؘ1cԔ♒O8>ꫯ~?Gtcǎu6kɮױ[a!ױcGz{ho֔F\ @G)0<5`W!"6}mٲe@O}Wc$i~s!<>;]iV:n Cew@<W6x`"u"u{ncbբdɒ ~vŵ;?w}8'w)Cl͚5֡C4෋~ԪUOxۅ+ްaCx\rӱ=c ^gD->3y,]5zɒ% c!@21? 4h78cjIZL۽kB0-5[/2}Q5_XڵkݱO ߟlLY?[϶m%َ;\1HO@}vիr.U@XoK̙mĈld)]:߼:tMVG~z[/^@MUh^_ܹm„ Ms|cRTӧ=3ֶm[ܹug*b6rz.vBB1Z8p îδilСַo_޽=־}{ڵӥ?Kiп댇oL2dɒح5xנf͚3bŊYBBknof̘ѥUGaC/QURڴicÆ s Q$\(_EzX:9Q4>܍ݤ$]8N)^9Ac@sDݜ!ʘݳҜ_Qh_EiQ'''[ݺutΡ+Y|.NN_5A͛׮zkժ5ʵڌhR+ ٿ-[bt?GvcnpcVcW{^1134w/J:ulb J۹s=#CDPWLd /حjyqyakbФhD? ݃&bVO?t,[,XxnjnK4\G ,1>+#@lP[\[nxvnv!uNDVE{dž vQ| ]Šٳg{l|v5hEmJV 9st13nϽs"__x7-`7m*pqĉִiSWZ.u@B9PwS-4i2y5濎444tO{ [sM]l\@^B*2ݴJA\m@]^ywRjU5k䪾箟ޱJcK}5jpcRVyAsv4':\(?Vtt:JК&) *_+H1g˽65^z%7ƢZ'xی*]?֊YU֟|ŏUR:ue_}[+3Nn}1UdHfh.Rp˖-]|o"@ TH@VU3fpg ‰R;ve(/Ni i,iLE9]MsƍW_}k]~}aݰA߿߫?_)LhR]vy5jڋ/誤kbޥީޭo[JYeC=k! ߀yVx>m*Fgb-Jq WJkҖ ܪGRԧ5UTe>RS5WL]u@L)\[MRF?* Tt^ ƍ2)>r|NJsIA>SD[>R)Rݽ曽nW*b*%J}nh:5gUTFŎ||| (Ɨq….F z|ϿV} R,K.,4)R eѥ.6P?zr%>㐛3L~v?i$7VRZsX޽m߾} *XS0nT.͛hM]jX}*a˵BT;æO]o @(WkPsή[aJ*:X 0 oS;ܹsǮ"2iQ QV-2_n[q܆Ҷڄ tܰaXv\n3gΌ{ܤ& QV=zp~ڐEks΍E?v K XPЯvZ^Ō nQ67#Je:\vwu` Kmӭ[7o]h:pmڵ~ @ĭΞ3f-[6]nF{cXjƀ"U0+p2e?nɓc-Ƃ[\( )O((Fu@,tK<]֚QVZUq(jO2ŋLVgj_ȿ @LD5jR+`EAcXoرAu.Vn]@\*5o<dO>@FB`A P28uQf͚Ų/0G@۟,dc08@ЂE W lhA xHE5Ƅ(PM߾RaCKTsԅi*D'*B! ,Tӷcǎ bg*zeP:ccLf(ʟR  8PƽʝyEƫ|< &̀A , C2BVa`2LϢ,8NBf@1jQڡB7VkPkS( W y@RO:O:OA]vؿy~7bٳʝ;u9^>BYd X&7ɓ'GZȔG i ݻם9}'lٲn"&N@ 80+εk׺*YqN ֭G>B3j֬id0sLcYT+ ѣG14ԤIXh./{PVFZq\rOZC& MT= x ۹b\/O?ܶmmذ֯_1@RԇӘy׬xhСZʛ- B/`xG:Xh=C,ݗj*fO{sRً/h۷wA.N^-iX4VZմO u]V>Uӵ~W.&gΜ>Hq ٲewۯZ-Z4ĉ fCKQ47ь3zƚ_׭[| Nt&L>Ru kٲed輲dɒ.Y[[n"qBT*KSJbڥ3gweM?> }:Smk:E,Y] *T^zb?˗/[o5VX Q~^~S}ucLc-* At{|H9PpkvU~\V_@Jr5jBKryk mC;6ޱ5l074.epV̚^y7':\{nZ Zɧ_:+ ] Mlӧg*RTL{G֟<6jKq/S&Z۶m=]앫 e-VEVԷzm֬ 4&h7@ݶl$TANHT˖-졣wwwiH`Ȑ!v=(B@(yqJu[nu ]0'}ȳf*7&Tnݺ[7UK-g^:nywmnϗJ ؽ{wꪫp1G瞋w{XWtXM-&QDsb(TLgqh-_Ǯ*?lM;w2VXa pDK.&<4)ebW bQE?vN|<5,3& Awwq1M6 1=q Ju:|g T@Bgh*'g+믿ʗ/*եR!R|֟G]QRRi^:Vg[o*84hP,;SjQhNcJ*,ՠ'eɧ{ z.#$9CU<ΝvR2aO:?߽{R -[j ;ֳgO~U%M]s=KQnUM.bf|X9;rSNuntTعsgH3ϸm} "ksW?ʕ+q$r?X9D,<\9_s?c`oSXX@&9tMt 4kg SHLᘶ8GA[4pLk`jW^}vFH o>H.oVcfY/v?C 'O] Vp!(VZva??4i+8@.A+bI![pP,9j DoX^sxV3J0OS7={]%8Tg8a, ~VxmƌTY**ӰaCW}+[>YQK vOb?1}}ʘԳK.vW2]*dɒo8G9Sǎ;3`󰜿Zfϛ7/[bb"pB"lo*oW7?qz-aoSZ]sr TJjb@(n^~M*m_UVj]>[oܹs'hPVZn#@ }!/,$tB?@xE={#GS@VgOPbCt,P, u]nU TTZ"0oA _+܎4 С$(#q5 )W'`9w*t_ j}]vB*#t@Aʱqٲe֩S' ܏2t6s0cQMWՄڣUŭ_~n {vm(rʮE܄E+'Nt)GYV TηV /v/gFIJQi,Yc=Cס:moEq:T|I|#N~ꘇB>8 N:n娼訟)IѮsn)- @O~cǎDݻ[Μ9K0VT9P D]<\UfMw1c A Z KʤIl͚5.fR;|W(>G})~ik;4ǯ O'tQ TCAnJ0_FcGcH:"ȑ#] K-4^e^57i\G@;m*\UzW4E [|,X&v+YkVDQ׸b$TD*߶~zSiU"@E3&g+A18sL[ZM%ZkGCsX54hA4'inb3btά 3ԯ_ߕbݻ+B5TQ&' dcoݺ(J c}w6k,W799Ukٲծ]U5YԾ^]R@ГVGlӲƚƜƞƠR Rr;㧏yy@kFj09Dsv!4hќ>"C ;•n$UUZW]+Ԥ$:UTqg_D .oT&қ4I[ e]̕QU9i]arʹq1q/oAse;4h.ќ><^jBuD\A?՘&^{W[ԜƲIc[cLc_b.Mx8n^׶Y! `?b)F"nEs7˨q9:U:bX )sR/?3i |ꩧXbg m50R?\SIg9W U=c۟?!/_kՊ;_ IK_ݕL3vPR@:/E~tz*C? A~aɓx-/ӧѣG:E? k\suj{ .!jڣG#slÆ ͛4hqFɃ@H$&&ڄ xl.#!!2P>k?#[ƃ}Μ9qOjlɒ%l{b? lٲ{GԩS}v{-{@W_E)`a/[֬Y5?~O|rW1@iѢڵvΛ5k@OfϞLV'Ncǎ .!wwoxbu{7nթSvߝ#Gw-}6_mԩ`1DRJnGS^{)b? Jנk\={|:PT4Lٳ[l]Z#k*V=Z}<x7X@̶Zvծ UצkԵ}iӦ`J?+'SXkԵuTX`@@ĉL2e̘1#GzUvذaR|{ފ еO?˙3'cqOLL%Kx6gY۹yץF^| T}}+d ًr߶m[˕+i݋ `FWN @ā,Y?Jm2eb{н8Ç F{10v9:4h+~˜9slAn:/ށB ̙3X}&''fE>EKVI.ٳRL5k"x6mdՋen/jF ǨU%6j(+ZhVѣ#vmkצ,0 |lٲesu}ZuMwq Q\5щZusqի#>y0)s綁F(M.o޼<ӽU0ꦀń |%6~;6m*l)רW7nvC… (ۑ#GJ*~~߇1))>w2k,0s%l?u1LnνjSgS\9e:u˗/g{i"N֮]k^{m :Xmܸ1wCy/9r䰡CFlذnFfo޼9Φ_~5k֠j37DL(qW\qUZնmigӭ[ 2N޽{߉};K@u^|I lжmX6:h߾=PЎ^y֭@.4GTkϞ=#NԶX4 RJO4heϞ={}w"N(4@@xHt\G٨WτRH:a„HuֹoGc :RJRQe˖Ybł){=G<JS)bϏ9x]wuA6^|7;wvC ɓ#lYfAuQooFc'Ou8q‹gf1@yO>|{+|< 5ڵ{&M &dΜEb.WK:XrH|O;, NF^jժ.wb;zZ|6Վ  P_|<={lڴiֹsgpG5MlLQ~ӝYDXN<%yIhG/=zXzlٲ]ƌ/(MF6!!ʔ)cw+3gz{r>? B!W\Ȏcǎzcƌq+u5A\%&&ov3e LU~R[nq駟?֮]kǏճ駟11b:uM up;wtnƍgwz5rz_bO=NRv/ Hqݺ]IצkԵK"Nv .lժU;ȑ#m޼yyf7 B9gm 9s3u7'tעkҵpR \r`v\['ڂ lŊ$j5ъ^N^wz `?լY.NJ GYd|Yҥo$XN:V^=k޼C?P/P;'p 9_rLx@ @  @ @ @ @@$Q_{IENDB`mozilla-vpn-client-2.2.0/balrog/000077500000000000000000000000001404202232700165015ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/balrog/Makefile000066400000000000000000000000611404202232700201360ustar00rootroot00000000000000all: go build -buildmode c-archive -v install: mozilla-vpn-client-2.2.0/balrog/api.go000066400000000000000000000143261404202232700176070ustar00rootroot00000000000000// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. package main // #include // #include // static void callLogger(void *func, int level, const char *msg) // { // ((void(*)(int, const char *))func)(level, msg); // } import "C" import ( "crypto/ecdsa" "crypto/sha256" "crypto/sha512" "crypto/x509" "encoding/base64" "errors" "encoding/pem" "math/big" "log" "hash" "unsafe" ) var loggerFunc unsafe.Pointer type CLogger struct { level C.int } func (l *CLogger) Write(p []byte) (int, error) { if uintptr(loggerFunc) == 0 { return 0, errors.New("No logger initialized") } message := C.CString(string(p)) C.callLogger(loggerFunc, l.level, message) C.free(unsafe.Pointer(message)) return len(p), nil } // ContentSignature contains the parsed representation of a signature type ContentSignature struct { R, S *big.Int // fields must be exported for ASN.1 marshalling HashName string Mode string X5U string ID string Len int Finished bool } const ( // Type of this signer is 'contentsignature' Type = "contentsignature" // P256ECDSA defines an ecdsa content signature on the P-256 curve P256ECDSA = "p256ecdsa" // P256ECDSABYTESIZE defines the bytes length of a P256ECDSA signature P256ECDSABYTESIZE = 64 // P384ECDSA defines an ecdsa content signature on the P-384 curve P384ECDSA = "p384ecdsa" // P384ECDSABYTESIZE defines the bytes length of a P384ECDSA signature P384ECDSABYTESIZE = 96 // P521ECDSA defines an ecdsa content signature on the P-521 curve P521ECDSA = "p521ecdsa" // P521ECDSABYTESIZE defines the bytes length of a P521ECDSA signature P521ECDSABYTESIZE = 132 // SignaturePrefix is a string preprended to data prior to signing SignaturePrefix = "Content-Signature:\x00" ) // getSignatureLen returns the size of an ECDSA signature issued by the signer, // or -1 if the mode is unknown // // The signature length is double the size size of the curve field, in bytes // (each R and S value is equal to the size of the curve field). // If the curve field it not a multiple of 8, round to the upper multiple of 8. func getSignatureLen(mode string) int { switch mode { case P256ECDSA: return P256ECDSABYTESIZE case P384ECDSA: return P384ECDSABYTESIZE case P521ECDSA: return P521ECDSABYTESIZE } return -1 } // getSignatureHash returns the name of the hash function used by a given mode, // or an empty string if the mode is unknown func getSignatureHash(mode string) string { switch mode { case P256ECDSA: return "sha256" case P384ECDSA: return "sha384" case P521ECDSA: return "sha512" } return "" } // VerifyData verifies a signatures on its raw, untemplated, input using a public key func (sig *ContentSignature) VerifyData(input []byte, pubKey *ecdsa.PublicKey) bool { _, hash := makeTemplatedHash(input, sig.Mode) return sig.VerifyHash(hash, pubKey) } // VerifyHash verifies a signature on its templated hash using a public key func (sig *ContentSignature) VerifyHash(hash []byte, pubKey *ecdsa.PublicKey) bool { return ecdsa.Verify(pubKey, hash, sig.R, sig.S) } // hash returns the templated sha384 of the input data. The template adds // the string "Content-Signature:\x00" before the input data prior to // calculating the sha384. // // The name of the hash function is returned, followed by the hash bytes func makeTemplatedHash(data []byte, curvename string) (alg string, out []byte) { templated := make([]byte, len(SignaturePrefix)+len(data)) copy(templated[:len(SignaturePrefix)], []byte(SignaturePrefix)) copy(templated[len(SignaturePrefix):], data) var md hash.Hash switch curvename { case P384ECDSA: md = sha512.New384() alg = "sha384" case P521ECDSA: md = sha512.New() alg = "sha512" default: md = sha256.New() alg = "sha256" } md.Write(templated) return alg, md.Sum(nil) } // Unmarshal parses a base64 url encoded content signature // and returns it into a ContentSignature structure that can be verified. // // Note this function does not set the X5U value of a signature. func Unmarshal(signature string) (sig *ContentSignature) { logger := log.New(&CLogger{level: 0}, "", 0) if len(signature) < 30 { logger.Println("contentsignature: signature cannot be shorter than 30 characters, got %d", len(signature)) return nil } // decode the actual signature into its R and S values data, err := base64.RawURLEncoding.DecodeString(signature) if err != nil { logger.Println("decode string failed") return nil } // Use the length to determine the mode sig = new(ContentSignature) sig.Len = len(data) switch sig.Len { case P256ECDSABYTESIZE: sig.Mode = P256ECDSA case P384ECDSABYTESIZE: sig.Mode = P384ECDSA case P521ECDSABYTESIZE: sig.Mode = P521ECDSA default: logger.Println("contentsignature: unknown signature length %d", len(data)) return nil } sig.HashName = getSignatureHash(sig.Mode) // parse the signature into R and S value by splitting it in the middle sig.R, sig.S = new(big.Int), new(big.Int) sig.R.SetBytes(data[:len(data)/2]) sig.S.SetBytes(data[len(data)/2:]) sig.Finished = true return sig } //export balrogSetLogger func balrogSetLogger(loggerFn uintptr) { loggerFunc = unsafe.Pointer(loggerFn) } //export balrogValidateSignature func balrogValidateSignature(publicKey string, signature string, input string) bool { logger := log.New(&CLogger{level: 0}, "", 0) cs := Unmarshal(signature) if cs == nil { logger.Println("Unmarshal failed") return false } block, _ := pem.Decode([]byte(publicKey)) if block == nil { logger.Println("Invalid PEM public key format") return false } keyInterface, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { logger.Println("Failed to parse public key DIR:", err) return false } pubkey := keyInterface.(*ecdsa.PublicKey) // verify signature on input data if !cs.VerifyData([]byte(input), pubkey) { logger.Println("Failed to verify content signature") return false } logger.Println("Verified") return true } func main() {} mozilla-vpn-client-2.2.0/balrog/build.cmd000066400000000000000000000044761404202232700203000ustar00rootroot00000000000000@echo off rem This Source Code Form is subject to the terms of the Mozilla Public rem License, v. 2.0. If a copy of the MPL was not distributed with this rem file, You can obtain one at http://mozilla.org/MPL/2.0/. setlocal set BUILDDIR=%~dp0 set PATH=%BUILDDIR%.deps\go\bin;%BUILDDIR%.deps;%PATH% set PATHEXT=.exe cd /d %BUILDDIR% || exit /b 1 if exist .deps\prepared goto :build :installdeps rmdir /s /q .deps 2> NUL mkdir .deps || goto :error cd .deps || goto :error call :download go.zip https://dl.google.com/go/go1.13.3.windows-amd64.zip 9585efeab37783152c81c6ce373b22e68f45c6801dc2c208bfd1e47b646efbef || goto :error rem Mirror of https://musl.cc/i686-w64-mingw32-native.zip call :download mingw-x86.zip https://download.wireguard.com/windows-toolchain/distfiles/i686-w64-mingw32-native-20200907.zip c972c00993727ac9bff83c799f4df65662adb95bc871fa30cfa8857e744a7fbd || goto :error rem Mirror of https://musl.cc/x86_64-w64-mingw32-native.zip call :download mingw-amd64.zip https://download.wireguard.com/windows-toolchain/distfiles/x86_64-w64-mingw32-native-20200907.zip e34fbacbd25b007a8074fc96f7e08f886241e0473a055987ee57483c37567aa5 || goto :error copy /y NUL prepared > NUL || goto :error cd .. || goto :error :build set GOOS=windows set GOPATH=%BUILDDIR%.deps\gopath set GOROOT=%BUILDDIR%.deps\go set CGO_ENABLED=1 set CGO_CFLAGS=-O3 -Wall -Wno-unused-function -Wno-switch -std=gnu11 -DWINVER=0x0601 set CGO_LDFLAGS=-Wl,--dynamicbase -Wl,--nxcompat -Wl,--export-all-symbols call :build_plat x86 i686 386 || goto :error set CGO_LDFLAGS=%CGO_LDFLAGS% -Wl,--high-entropy-va call :build_plat x64 x86_64 amd64 || goto :error :success echo [+] Success exit /b 0 :download echo [+] Downloading %1 curl -#fLo %1 %2 || exit /b 1 echo [+] Verifying %1 for /f %%a in ('CertUtil -hashfile %1 SHA256 ^| findstr /r "^[0-9a-f]*$"') do if not "%%a"=="%~3" exit /b 1 echo [+] Extracting %1 unzip %1 %~4 || exit /b 1 echo [+] Cleaning up %1 del %1 || exit /b 1 goto :eof :build_plat set PATH=%BUILDDIR%.deps\%~2-w64-mingw32-native\bin;%PATH% set CC=%~2-w64-mingw32-gcc set GOARCH=%~3 mkdir %1 >NUL 2>&1 echo [+] Building library %1 go build -buildmode c-shared -ldflags="-w -s" -trimpath -v -o "%~1\balrog.dll" || exit /b 1 del "%~1\balrog.h" goto :eof :error echo [-] Failed with error #%errorlevel%. cmd /c exit %errorlevel% mozilla-vpn-client-2.2.0/i18n/000077500000000000000000000000001404202232700160125ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/ios/000077500000000000000000000000001404202232700160255ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/ios/app/000077500000000000000000000000001404202232700166055ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/000077500000000000000000000000001404202232700225575ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/000077500000000000000000000000001404202232700262545ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/Contents.json000066400000000000000000000044431404202232700307510ustar00rootroot00000000000000{ "images" : [ { "filename" : "icon-ios-20@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { "filename" : "icon-ios-20@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { "filename" : "icon-ios-29@2x-1.png", "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { "filename" : "icon-ios-29@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { "filename" : "icon-ios-40@2x-1.png", "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { "filename" : "icon-ios-40@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { "filename" : "icon-ios-60@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { "filename" : "icon-ios-60@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { "filename" : "icon-ios-20@1x.png", "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { "filename" : "icon-ios-20@2x-1.png", "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { "filename" : "icon-ios-29@1x.png", "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { "filename" : "icon-ios-29@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { "filename" : "icon-ios-40@1x.png", "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { "filename" : "icon-ios-40@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { "filename" : "icon-ios-76@1x.png", "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { "filename" : "icon-ios-76@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { "filename" : "icon-ios-83.5@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { "filename" : "icon-ios-1024@1x.png", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/icon-ios-1024@1x.png000066400000000000000000000566161404202232700314550ustar00rootroot00000000000000PNG  IHDR+ pHYs  sRGBgAMA a]#IDATxMly.p(&9p`{LD 4 J:Hn@T $餢 &E'"܋LbQv[H @j#I@r$ |K:}Z{~uN]"zֳΥ'`X h h h h h h h h h h h h h h h h h h h h h h h h h h h h h h h h h h h %.\x韏TfiiiZYY}~yyy߻D~ٳxu.8wzD񾶶iW 'O@|#DqK+Q@Y('uW\}Z]<|`8@_|y5^0}/ v766FE|ma 7 OD9j?t0;̠v횢'] ` QNkFwL &#1EƍюLg~X۷ -D D-g+]WYxEG¿NCe{;.\HO/wNb^W n|8w\w`9B;w=x8X RG%ıq,Ew3-t0^W3݉IBO v:]Su֭@8{! MNU LPϝT$Z޽CA>5qjtc[ P UV8o%1@]f?EɘE @5)Yn/P2Ŋal !2Ĥ(]FFOOOD@Q1juxx^j8 EPf -AϢ; @i̝BEw+807 #Zw ; BV` jii)=x@Dw?^! T>+++ Zx/ Ν;imm-i^~|쵝gnJaM@nܸ'ೢٳgn>;{=OГ<88Hۭ}1t΁ws;}@o40X__O r@/b&Gfb`&@ WUC#dL/QCYZZJ=JlC+" 8?%LO@^W^M{{{ f%`fSv!f?O]@fWmll$?kkkikk+,`jpp`R9 Qfńr? Qf%`*1?^p(#7L&f?Ox+LB8 o`R:HGGG D|f~ ¿޽[\kwDƍ\~=$x888pD7@GG_ά'xDع?vkq]0_0 EDܣ}?K]`>x>;1a?˕JR + W.|vt߱ΆĕщϝyhouLw O9Hd1~Ů[\88HNb|uu?(L F `~E E q_+vbV{#s=|v{ҝN]|{E? @:::Yqƽ)90G"}hiO?.A8y)g'c88y]z5lnn'p  ֊|n޼hggg=%8y%h35vwM{%h# i =~xtf]*@vO&fs||<*+׃GVfs.^h"@hy\~]?(X{lhQ]jkf.@.]^ooo'shz,Bt>b` dzϟ7A:dl~z8 0;s$hW==m4ˉ/@g@hёԧ q].W&wM:Œ:Zˢ `z 8h1N'vLce{YSY@c,g).,h1c\ h1rE^b2:#hE1hkS>}&O(4D@COlj:Ԙ.bwtrz09 @[ ؟LGOF(@C,'ɓD]>|[A"hd01@;m&h!&~/Β scAm4D0> nao2$1> 4"2v{c2|A F&yq@;LFY/d|6CW|vƧ@#S>Nouu5Q'c0O& hg7@ ;˗/'tҥx|&E.uZ[[Kg@[ q `|Q;K^d|&Eg}'#d={h!}'sʕD]&-XO&Gb?D&wxxh!vZ'X@[Ν'-O>M/ /* w{:ΝKC@Cu `2QXnnn&ʦr:#hE䴖ƍjh1'''dy˵2z1'O$"ha.t?~h1 .$?3c@{ޭ[e30=h3S+(e_]]ME@ rYL?@ 2kzq܍l4H0;ѣtIР8~zzNtlmm%#s#/hQf]|{{;1=4J0tİ{{'hQ9 00qv:wzhӧOfJ:88H&\x1& {abvіn@{pA>AuGc4uyB0cnh\W%ۉ)uM@ P`:~xD~Q 8NQGp gիv` k@0p@@wٿ^|o=J@0E_QG7nFp޽F ӧnhuW6~F`1lnnvkk?:!È?rop=vB |#?6#$D6Êvtb,]1հb?6Dk b֭[%n%_qPm;0W V8]q{bߕ+WF ߛׯ'xψb.Q"(+ߗxQC#'"Pԕ) -€n߉˗/+  ^%t!n @`5ru}+++?K{7P"@dtt x-//vb>mKqNQӞ=DoG+8P6ˏ%x ;PUE fqn޼r`:rP Kt5? !ୢI@yp7RC٢?8tZ(۽{LDE῾.`":x)>0:@,t4Nu?v`Z:zQrpAr?T?h 'G r4@u]t&X`sEP(޽f, ?ǮW:-&7QC}# BuMPCn޼`0)N&04@P( IP)?IP!?iww7mnn&QC̛"*:)(ӽ{CP8?)omm%(`({ ")N7oLwMP@Pt%F9>>N룯P*3 *O\cYZZz.\08l2?ǰ?f=C/>_uт# +++?_t}Ëa@dN_[g 㵺B>\^^Y=W)]gϞL^ЄnA(I_ ^\6 (.QǤ/LWhC|P,T[tEkv3b*1{{[Ӏxnll xvTaK M t Am\gkksG7vwwG2?XD;{C]L]8" PaDc,>&_\iDb2m+ʥij4 )SwγG3QnѸ?)Z-9Y x̏_ ݢmŽpw4EWG=uV??ۙx,g)_ OnEsO|noo>kynxfܿe//~^:'vbG:l}}M_|YG@/8#'mEiV(~]{k]s2;UQ|x_-w:!sW7L PibL1E&bxF'^Xd X@r/r<`/b#tN/ `J )P+ WG?r,f# Y@._A`$/:ۿN::τtd0P!WEH!+W BqWѿVVVFz|iCq4`1UF/_63 bFſŮC|vܼyS7A?ԣub3k+y(iC`zH\\e{Z>vyKA@ꊭOs*^kbF%Pgw0 %)\/P#?bLum.իW !y?aq@QCYT?%~?uкϝ4(_O@=P?&(G}4Et} 0TFea7￟ѥUo%h4Dtqu.ǣn7%fЌ.b?EeRSX,(Z&tſxʤgx Ou 23V8BSC,ĠL5#|CP&?ڑXH>N(VXC,P'?Ν;b-"P|`CP5ݚrii)"0?Z'` Xq>qee%PCnܸ3c0(>wNPإ_'(FIE]ַN@(B}Phy'Gկ'ٹ]8Ce2H-nMP+Պ,P?+f۝c jd U2rŐ3?[*P?+flmm%`<(@ 8A]Id"p352D/qz(\^B@5,T.([ctPU:aPC=P8NuCfP ae?4j8@F@P(RtA%sfEGk٠dfuPC&#֬v9@\uPCܤq ( Au@=uL@(#ƍ(t0G(GGG (T: QJʦz sΝk;A!~P&?# LJ~={&(#?(b_ x񢁀MʥD[ZZrAP,R !(>R`b"2<(.JB666Pk׮)Pּ@\iS՜1(_! \bdwwwTA;q|,\A_7ow& ԕ'Qwys/_&\ *f pԮ.)@WCab ~Ëvߤ}`ѯxo0czg_fǼЮE"0<Bgbe:a*(:'ܟhO4b&b?]tڃ~czGǔ߫إkP?gnwG\{uM@TءI/v-Xa(φxF0={ k8Xax^7/A:&7  )g':}z/AO._'+_uD%(LܹNNNЏD>q?X`'5y<}Ԯ`&v)#TkC^NuVbvQq}p^8]c=#>㳟bkfg} ZyBekk+яH]eІn?fq\1ki".(P_P1`g<dmKq?`SwT˙nMLښd$_@*-G d:& 8?G@yj_Yg `:)Myt3Lcsd#r\k? FB9-qBΘ+I@6:&' /^n-<d#'٘R:9 bEHPы鬛#$ 8d.a?P6zbeKN^dS!bWٙ:u~P/j/ģG5It9 (yau \κXg, '.+} rTr2KaPQ]6]XEw[]]M,tLɓ'bkmr/_N9: &B0:Ygs\6B*9D]kfuLF@.8<YbE|BE"S:Q'T55}^bz͇ bG}0 -L.+a}+~tWmmm%.W1Bߏ(c@%?ugD<+vƍM@,VZYE;&gN Kg]Ycf{nBu w ~ŮE,/kK0yu>?]p~Y33b_]_a{P κų#:tP-aѯxo;:.>$v-Z^tw^wŮ ԇ;{=OгnaΑŮ, %Fn]x1Sw۷o7.Ǻ-"*6= A0}ttû baf1<0_suџv#Bu?C0X@,L*D9E/GzVAË# &.`K;.-]HqDWSBE,L]6Zڕ(םDv m,q€XsEgjYbaH l.F,\k߷7b$ x/(:"{/]4w\c̅.zEhĢ$ <?[!YQ,\^Uw5ɧi(Z:"`."n-^/[ELb-@a[c9E(.}1`>sgh|L#űŊ!)Xʤ ayr s"+-,u<2TIܸ ?S̓Bb} uLeU$?rJN eP%Lּ@E.(O,TC=fWBb+TkkkѣG (+.QsPx )h5ʠMMKKKhX\~=SC٢___P #u>:(|q3⟒8iee%Ë{{;eSCӡDc ̇ Au09@qb `Krʧ:hT:(R 80 |LEF,P )LUS*raeSC=.^P*G(ЯٶP&?C?5P<@?b::rGu?:Xd;{m'(X)ϟO_WO(.qN?5@5(Jf Guͪ/&jB(B L/M(?q ՈVWl\tTOO.E(>Q߽{7AMJH9eRC}Vdw^J@Y<Ϡ>S3Պbd( ?,虪VR(jn޼7իWP?)*!T-BsΙ?s?gQCb9AT/>wCu2HEa cgg']v-itYD1_eQCi&tq+UP?)ֆZ"]RCyPX*i>tP?)Ě0ֆВϝ4f:w\Z[[KPX(t[[[ Z$YٳG%(U%F'' (t=AΝ'hJzEʼnի0OL5֙@LD1D(ӭ^rtƍ'$iWv_?Ͻ`}1=99dUO1@W?$WP;w$ZwUn^gW.]]?M 9^gT Gz -#Eۺ>^QM%}Ea3? 2H}CѶ(ȯ׋@ iQh;lnn[nY]AC]סK JP\~zK `  r]ء`u-W\!X>}-aNύ(n@юnx;Hq.qqCQwLua<ct09L(s{ڵEJw.b!@ ɌP`??LGS2qY,(cPh3 =?Yf8^GkbB?WGi]+XSCL X0>x`tE]_,]([#PZ" @b_=BtLB͛7CF =v'6zMr<@_Y9GtA^;{m' -zhX\(]__O;>'?81xk =36^'''\p^˿f˫nsͪ~:+(ߺuӧOx߿?,~s9ÔuK|noo>kxfxyrLYb;N$1eߎh~: |W vY]0Xt-Z?njPCyU Q?̏(-Q.^WQ(C&)_02颃@ v0?;) (B-_]Y9azt?濘:(_AD_V]Ŀ0!~ocGt.螳gg\UC B&¦΁Xptx={?w ^Ǡ?Ϣo35>G}]@}";/<K`yq2"BkײϴC}Z*j0I0=C*uR8HC(Z=r*(Ϗ%(T ;88PCeeP;%_S |\L(&(Qooo'.@1ܹ?p(s:)"`P'?@}̍i^?n:)w<nܸ޽(AE8w.\H~f1-0Cg6m4b2c@CtO_(@C,ɓD@#N.r&c8(@;7%4B0%ŋmk("^O_?!|6C)iv:'@%Wh߳g2p>LpBn&g }O%~𿤿Jx[z?y>-~>Њsx4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @Ѐ||w'{jK0/Tg_?LK+_RH|҅k_?o-}}R?MOnG Xth"XZr╟O_T{O]?8>?6K{09tzgηFX>YNޫ9Ew~f0q wlcHMC~;_Wg/$&`f1.ۻ h¸o:lۣ`><^sN z . ! ovۢ^u Bg>c@&B?Я( ۢ@P'b0/v |ooxOTH@Pxr?% o{3s| x[v !?9{oü0EO]JC8=QQW{}r^qngn]K%J?=LZğ8>4/y>aK+_}W~q ?({U.5Gojݼd߷V~76O~GPG/|7(- 2VD ^umG{*"3"tW?y_]Nifw;3Q?? rzvg Mw\@Un\TN;<\Dɜ{w0\0m/}T&>KX @@`r9/7)'`l_' ?>v_z_lw p &`~wM_r⿄ x3cc0?S`O ŔKi x{V<!B\|0@WKru;nu_ہٜ] P#]?L{&Oz+[rϕc]x/PO~'[.?8[j)jZkXW- $>r\΅ n{| EfQώKw5"Ǭy$X C]&q ~r1 0^~?ݾL'i qե;(M\PO{f94 `d/?làU9M:U_C7d08cO30?ߓW0_f1?W-yߗ3u+s6`rfo]]]*M.\jnvfv|Viq3ȹ;k+)_008|{ۿY&Ƿ;ӷ~%s>r'C@VƬ&EqbrWItJ@Ћxr?%ݎw]ry!Лn>xd/O[| >|/f`2zuzgz9<(/lC t% K%]_྘w?HEWC𲜅pio+ 2@a}mhG~6j~d[نEx{߫)^9B|Π\ x7/;7Qa'LnĥG.x._gH߻Đ\] /370gZGOc q;2xB;pt<&ؕ_ZrB y% NӃ2:0h1AwrY׉?畀 GXb;0\Gk;')1M CDVjZZRE?-JZ #`"tߤYYӲO49}; X YYӢO~UW w=@L%XCk:\:9n{ q`]nxPyh>cw?,S=8}8Y]n.8ԾpҬ %~ 080?KЂ\U$G\Yӊ{$2SQ hwi߇r\ :\05[,д!8S9 BL\w% y q |; L쓽d;E|ېPL$ÛweiK ӊ /c\uG'.$XD>q}6';nOIX_wOSN?v)"rvy#DG;z)`E`p{wy .f_%XD1$3Q _K?}Z],`v>Q6X]Ƙ|l[Ru_>C>g* AJ#(LawߞQl2 µRIxfیyΙ\…S]W}|@Ali?KwMW~!KTP࢕]Mx?sw}id1f#`PQ^)"Oe?1@`bG3:>y Ǘ.?-,hyߢW W@,9 J@ gwr , UG*vsO̢]x,u_cЛ/)iߞN3B?s-ЋK?-O~?hAy{wU.GfkEk0O,&:r^ h dE; oѯy0@ IeВ?>v_Lsu8||&$`"q_,/tC;4x7DEM )(a x/kD;uqn7v%.})}ph~8d[)JM ͵{ '9@Z/kL|V-??/C~7yj% @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @4@ @?۱Aփ\ 0 `@ 0 `@ 0 `@ 0 `@ 0 `@XKPIENDB`mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/icon-ios-20@1x.png000066400000000000000000000007501404202232700312740ustar00rootroot00000000000000PNG  IHDR pHYs  sRGBgAMA a}IDATxTN@z.5zx.r(㕾Ay7o>=@ƟIK+$İ4L2e۝o:-"۶i_*5MSq,*IT.rZG9#oqQHe>[hEHt3LLaQ6qM:H4Up`=5THu0 9 c,H9VPaQ$5 -m䰚#lE!pPx,([Bwm졒kEFW:X%ʜ޽xR)bMOK:1߬ƢϗL^6MF샿맍y~"LIENDB`mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/icon-ios-20@2x-1.png000066400000000000000000000015601404202232700314330ustar00rootroot00000000000000PNG  IHDR((m pHYs  sRGBgAMA aIDATxX1A0ŝ0glD{)hl7@c j ka`I< -yCf3;.8#f߼o)"(F AS$eQ>uqx<ˆynS{m{)|E}7r {fsO\. cBK`:ˡrmu]Ȯ#%@{uPNh@iہD1yA5!PVA92¼OEXcNLيʁ,,PPG&sAIyK`OF+gq*fޡJHM䠜 !+)"`)bHɄ;j5^f4(JŷVELq^gNlldpm_B %gEhԙLzDx8- )R?'dzW+ z@x$Ƅ巊H$঳;ިTWZ)`"B;lmT"EU&(g #rȘ-N{!(vر^޷&?tHvb~Fxeu'Y6cG dF?othߦ[wo)?_X{-9';M~2zͯszϊ.C_2#r Wv&ԻTK:&hWq`Zcwݺ hArU(vY=@z͘"ЩA9IENDB`mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/icon-ios-20@2x.png000066400000000000000000000015601404202232700312750ustar00rootroot00000000000000PNG  IHDR((m pHYs  sRGBgAMA aIDATxX1A0ŝ0glD{)hl7@c j ka`I< -yCf3;.8#f߼o)"(F AS$eQ>uqx<ˆynS{m{)|E}7r {fsO\. cBK`:ˡrmu]Ȯ#%@{uPNh@iہD1yA5!PVA92¼OEXcNLيʁ,,PPG&sAIyK`OF+gq*fޡJHM䠜 !+)"`)bHɄ;j5^f4(JŷVELq^gNlldpm_B %gEhԙLzDx8- )R?'dzW+ z@x$Ƅ巊H$঳;ިTWZ)`"B;lmT"EU&(g #rȘ-N{!(vر^޷&?tHvb~Fxeu'Y6cG dF?othߦ[wo)?_X{-9';M~2zͯszϊ.C_2#r Wv&ԻTK:&hWq`Zcwݺ hArU(vY=@z͘"ЩA9IENDB`mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/icon-ios-20@3x.png000066400000000000000000000024651404202232700313030ustar00rootroot00000000000000PNG  IHDR<<:r pHYs  sRGBgAMA aIDATxZOA~W)&DJpiIcH@7=^<Ph=xP.& sۃ^д$L:ߤ۴@L;;3̛7!"NN CpM&8CM`0H>yJ&T,TFT[YYvvv4MqF@=քjV4a}|mmMI)3[*mP"2s9xWոa_s0$LTH1qZ.J5lOs!H0͒*(# S.,,ڡN$,Ps dذx_r)>Q*7xP(9ܜ2M’ShT5YQ39b`$ X,Fv1??O[brro/{@8@L7ifF5D~5ͰHWZC ^f)6SF+peY[Y;YaɲPe>68mM+ }m)a8 YMbL`{ ,ؾNzldk XugunMuSzd}ɲ,R&N*"2t$siVԿ:"!V:XEXrZbU:3!kޅñ8,8jaE"=&ye wuui=+u(z0$NN egaB_2X K#NΥЏzlLJ,.ץ^0ΚѹY>w!aii،lI6N

Success!

The Mozilla VPN is successfully installed. Go to your Applications folder to open up the VPN and start taking control of your online privacy.


Trouble with this install? Get help.

mozilla-vpn-client-2.2.0/macos/pkg/Resources/welcome.html000066400000000000000000000010441404202232700234100ustar00rootroot00000000000000

You will now be guided through the installation steps for the Mozilla VPN. Thank you for choosing your VPN from the trusted pioneer of internet privacy.


Click "Continue" to continue the setup.

mozilla-vpn-client-2.2.0/macos/pkg/scripts/000077500000000000000000000000001404202232700206055ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/macos/pkg/scripts/postinstall000077500000000000000000000034221404202232700231100ustar00rootroot00000000000000#!/usr/bin/env bash # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. set -eu LOG_DIR=/var/log/mozillavpn mkdir -p $LOG_DIR exec 2>&1 > $LOG_DIR/postinstall.log echo "Running postinstall at $(date)" DAEMON_PLIST_PATH="/Library/LaunchDaemons/org.mozilla.macos.FirefoxVPN.daemon.plist" DAEMON_PLIST=$(cat <<-EOM Label org.mozilla.macos.FirefoxVPN.daemon ProgramArguments /Applications/Mozilla VPN.app/Contents/MacOS/Mozilla VPN macosdaemon UserName root RunAtLoad KeepAlive SoftResourceLimits NumberOfFiles 1024 StandardErrorPath $LOG_DIR/stderr.log EOM ) # Kill all the existing apps pkill -x "Mozilla VPN" || echo "Unable to kill GUI, not running?" sleep 1 # Installing the service launchctl unload -w $DAEMON_PLIST_PATH echo "$DAEMON_PLIST" > $DAEMON_PLIST_PATH launchctl load -w $DAEMON_PLIST_PATH # Running the app open "/Applications/Mozilla VPN.app" mozilla-vpn-client-2.2.0/mozillavpn.pro000066400000000000000000000006251404202232700201530ustar00rootroot00000000000000# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. !versionAtLeast(QT_VERSION, 5.14.0) { message("Cannot use Qt $${QT_VERSION}") !android { error("Use Qt 5.14 or newer") } } TEMPLATE = subdirs SUBDIRS += src SUBDIRS += tests/unit mozilla-vpn-client-2.2.0/scripts/000077500000000000000000000000001404202232700167225ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/scripts/android_package.sh000077500000000000000000000145351404202232700223640ustar00rootroot00000000000000#!/bin/bash # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. . $(dirname $0)/commons.sh if [ -f .env ]; then . .env fi JOBS=8 QTPATH= RELEASE=1 PROD= export SPLITAPK=0 helpFunction() { print G "Usage:" print N "\t$0 [-d|--debug] [-j|--jobs ] [-p|--prod]" print N "" print N "By default, the android build is compiled in release mode. Use -d or --debug for a debug build." print N "By default, the project is compiled in staging mode. If you want to use the production env, use -p or --prod." print N "" exit 0 } print N "This script compiles MozillaVPN for Android" print N "" while [[ $# -gt 0 ]]; do key="$1" case $key in -j | --jobs) JOBS="$2" shift shift ;; -d | --debug) RELEASE= shift ;; -p | --prod) PROD=1 shift ;; -h | --help) helpFunction ;; *) if [[ "$QTPATH" ]]; then helpFunction fi QTPATH="$1" shift ;; esac done if ! [[ "$QTPATH" ]]; then helpFunction fi printn Y "Mode: " if [[ "$RELEASE" ]]; then print G "release" else print G "debug" fi PRODMODE= printn Y "Production mode: " if [[ "$PROD" ]]; then print G yes PRODMODE="CONFIG+=production" else print G no fi if ! [ -d "src" ] || ! [ -d "linux" ]; then die "This script must be executed at the root of the repository." fi if ! [ -d "$QTPATH/android/bin/" ]; then die "QTAndroid SDK was not found in the provided QT path" fi print Y "Checking Enviroment" if ! [ -d "src" ] || ! [ -d "linux" ]; then die "This script must be executed at the root of the repository." fi if ! [ -d "$QTPATH/android/bin/" ]; then die "QTAndroid SDK was not found in the provided QT path" fi if [ -z "${JAVA_HOME}" ]; then die "Could not find 'JAVA_HOME' in env" fi if [ -z "${ANDROID_NDK_ROOT}" ]; then die "Could not find 'ANDROID_NDK_ROOT' in env" fi if [ -z "${ANDROID_SDK_ROOT}" ]; then die "Could not find 'ANDROID_SDK_ROOT' in env" fi $QTPATH/android/bin/qmake -v &>/dev/null || die "qmake doesn't exist or it fails" printn Y "Cleaning the folder... " make distclean &>/dev/null; print G "done." rm -rf .tmp || die "Failed to remove the temporary directory" mkdir .tmp || die "Failed to create the temporary directory" print Y "Importing translation files..." python3 scripts/importLanguages.py $([[ "$PROD" ]] && echo "-p" || echo "") || die "Failed to import languages" printn Y "Computing the version... " export SHORTVERSION=$(cat version.pri | grep VERSION | grep defined | cut -d= -f2 | tr -d \ ) # Export so gradle can pick it up export VERSIONCODE=$(date +%s | sed 's/.\{3\}$//' )"0" #Remove the last 3 digits of the timestamp, so we only get every ~16m a new versioncode FULLVERSION=$SHORTVERSION.$(date +"%Y%m%d%H%M") print G "$SHORTVERSION - $FULLVERSION - $VERSIONCODE" print Y "Configuring the android build" cd .tmp/ if [[ "$RELEASE" ]]; then # On release builds only QT requires these *_metatypes.json # The files are actually all the same, but named by _ABI_ (they only differ for plattforms e.g android/ and ios/ ) # But sometimes the resolver seems to miss the current abi and defaults to the "none" abi # This one was missing on my machine, let's create a "none" version in case the resolver might fail too printn Y "Patch qt meta data" cp $QTPATH/android/lib/metatypes/qt5quick_armeabi-v7a_metatypes.json $QTPATH/android/lib/metatypes/qt5quick_metatypes.json cp $QTPATH/android/lib/metatypes/qt5charts_armeabi-v7a_metatypes.json $QTPATH/android/lib/metatypes/qt5charts_metatypes.json cp $QTPATH/android/lib/metatypes/qt5svg_armeabi-v7a_metatypes.json $QTPATH/android/lib/metatypes/qt5svg_metatypes.json cp $QTPATH/android/lib/metatypes/qt5widgets_armeabi-v7a_metatypes.json $QTPATH/android/lib/metatypes/qt5widgets_metatypes.json cp $QTPATH/android/lib/metatypes/qt5gui_armeabi-v7a_metatypes.json $QTPATH/android/lib/metatypes/qt5gui_metatypes.json cp $QTPATH/android/lib/metatypes/qt5qmlmodels_armeabi-v7a_metatypes.json $QTPATH/android/lib/metatypes/qt5qmlmodels_metatypes.json cp $QTPATH/android/lib/metatypes/qt5qml_armeabi-v7a_metatypes.json $QTPATH/android/lib/metatypes/qt5qml_metatypes.json cp $QTPATH/android/lib/metatypes/qt5networkauth_armeabi-v7a_metatypes.json $QTPATH/android/lib/metatypes/qt5networkauth_metatypes.json cp $QTPATH/android/lib/metatypes/qt5network_armeabi-v7a_metatypes.json $QTPATH/android/lib/metatypes/qt5network_xmetatypes.json cp $QTPATH/android/lib/metatypes/qt5test_armeabi-v7a_metatypes.json $QTPATH/android/lib/metatypes/qt5test_metatypes.json cp $QTPATH/android/lib/metatypes/qt5androidextras_armeabi-v7a_metatypes.json $QTPATH/android/lib/metatypes/qt5androidextras_metatypes.json cp $QTPATH/android/lib/metatypes/qt5core_armeabi-v7a_metatypes.json $QTPATH/android/lib/metatypes/qt5core_metatypes.json printn Y "Use release config" $QTPATH/android/bin/qmake -spec android-clang \ VERSION=$SHORTVERSION \ BUILD_ID=$VERSIONCODE \ CONFIG+=qtquickcompiler \ CONFIG-=debug \ CONFIG-=debug_and_release \ CONFIG+=release \ $PRODMODE \ ..//mozillavpn.pro || die "Qmake failed" else printn Y "Use debug config \n" $QTPATH/android/bin/qmake -spec android-clang \ VERSION=$SHORTVERSION \ BUILD_ID=$VERSIONCODE \ CONFIG+=debug \ CONFIG-=debug_and_release \ CONFIG-=release \ CONFIG+=qml_debug \ $PRODMODE \ ..//mozillavpn.pro || die "Qmake failed" fi print Y "Compiling apk_install_target in .tmp/" make -j $JOBS sub-src-apk_install_target || die "Compile of QT project failed" # We need to run the debug bundle step in any case # as this is the only make target that generates the gradle # project, that we can then use to generate a "real" release build print Y "Bundleing (debug) APK" cd src/ make apk || die "Compile of QT project failed" print G "All done!" print N "Your debug .APK is Located in .tmp/src/android-build/mozillavpn.apk" # If we wanted a release build we now need to # also compile the java/kotlin code in release mode if [[ "$RELEASE" ]]; then print Y "Generating Release APK..." export SPLITAPK=1 cd android-build ./gradlew compileReleaseSources ./gradlew assemble print G "Done 🎉" print G "Your Release APK is under .tmp/src/android-build/build/outputs/apk/release/android-build-universal-release-unsigned.apk" fi mozilla-vpn-client-2.2.0/scripts/apple_compile.sh000077500000000000000000000106461404202232700221010ustar00rootroot00000000000000#!/bin/bash # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. . $(dirname $0)/commons.sh if [ -f .env ]; then . .env fi RELEASE=1 OS= PROD= NETWORKEXTENSION= INSPECTOR= helpFunction() { print G "Usage:" print N "\t$0 [-d|--debug] [-p|--prod] [-i|--inspector] [-n|--networkextension]" print N "" print N "By default, the project is compiled in release mode. Use -d or --debug for a debug build." print N "By default, the project is compiled in staging mode. If you want to use the production env, use -p or --prod." print N "Use -n or --networkextension to force the network-extension component for MacOS too." print N "" print G "Config variables:" print N "\tQT_MACOS_BIN=" print N "\tQT_IOS_BIN=" print N "" exit 0 } print N "This script compiles MozillaVPN for MacOS/iOS" print N "" while [[ $# -gt 0 ]]; do key="$1" case $key in -d | --debug) RELEASE= shift ;; -p | --prod) PROD=1 shift ;; -i | --inspector) INSPECTOR=1 shift ;; -n | --networkextension) NETWORKEXTENSION=1 shift ;; -h | --help) helpFunction ;; *) if [[ "$OS" ]]; then helpFunction fi OS=$1 shift ;; esac done if [[ "$OS" != "macos" ]] && [[ "$OS" != "ios" ]] && [[ "$OS" != "macostest" ]]; then helpFunction fi if [[ "$OS" == "ios" ]]; then # Network-extension is the default for IOS NETWORKEXTENSION=1 fi if ! [ -d "src" ] || ! [ -d "ios" ] || ! [ -d "macos" ]; then die "This script must be executed at the root of the repository." fi QMAKE=qmake if [ "$OS" = "macos" ] && ! [ "$QT_MACOS_BIN" = "" ]; then QMAKE=$QT_MACOS_BIN/qmake elif [ "$OS" = "macostest" ] && ! [ "$QT_MACOS_BIN" = "" ]; then QMAKE=$QT_MACOS_BIN/qmake elif [ "$OS" = "ios" ] && ! [ "$QT_IOS_BIN" = "" ]; then QMAKE=$QT_IOS_BIN/qmake fi $QMAKE -v &>/dev/null || die "qmake doesn't exist or it fails" printn Y "Retrieve the wireguard-go version... " (cd macos/gobridge && go list -m golang.zx2c4.com/wireguard | sed -n 's/.*v\([0-9.]*\).*/#define WIREGUARD_GO_VERSION "\1"/p') > macos/gobridge/wireguard-go-version.h print G "done." printn Y "Cleaning the existing project... " rm -rf mozillavpn.xcodeproj/ || die "Failed to remove things" print G "done." print Y "Importing translation files..." python3 scripts/importLanguages.py $([[ "$PROD" ]] && echo "-p" || echo "") || die "Failed to import languages" printn Y "Extract the project version... " SHORTVERSION=$(cat version.pri | grep VERSION | grep defined | cut -d= -f2 | tr -d \ ) FULLVERSION=$(echo $SHORTVERSION | cut -d. -f1).$(date +"%Y%m%d%H%M") print G "$SHORTVERSION - $FULLVERSION" MACOS_FLAGS=" QTPLUGIN+=qsvg CONFIG-=static CONFIG+=balrog MVPN_MACOS=1 " MACOSTEST_FLAGS=" QTPLUGIN+=qsvg CONFIG-=static CONFIG+=DUMMY " IOS_FLAGS=" MVPN_IOS=1 " printn Y "Mode: " if [[ "$RELEASE" ]]; then print G "release" MODE="CONFIG-=debug CONFIG+=release CONFIG-=debug_and_release" else print G "debug" MODE="CONFIG+=debug CONFIG-=release CONFIG-=debug_and_release" fi OSRUBY=$OS printn Y "OS: " print G "$OS" if [ "$OS" = "macos" ]; then PLATFORM=$MACOS_FLAGS elif [ "$OS" = "macostest" ]; then OSRUBY=macos PLATFORM=$MACOSTEST_FLAGS elif [ "$OS" = "ios" ]; then PLATFORM=$IOS_FLAGS else die "Why we are here?" fi PRODMODE= printn Y "Production mode: " if [[ "$PROD" ]]; then print G yes PRODMODE="CONFIG+=production" else print G no fi printn Y "Enabling inspector: " if [[ "$INSPECTOR" ]]; then print G yes INSPECTOR="CONFIG+=inspector" else INSPECTOR="" print G no fi VPNMODE= printn Y "VPN mode: " if [[ "$NETWORKEXTENSION" ]]; then print G network-extension VPNMODE="CONFIG+=networkextension" else print G daemon fi print Y "Creating the xcode project via qmake..." $QMAKE \ VERSION=$SHORTVERSION \ BUILD_ID=$FULLVERSION \ -spec macx-xcode \ $MODE \ $PRODMODE \ $INSPECTOR \ $VPNMODE \ $PLATFORM \ src/src.pro || die "Compilation failed" print Y "Patching the xcode project..." ruby scripts/xcode_patcher.rb "MozillaVPN.xcodeproj" "$SHORTVERSION" "$FULLVERSION" "$OSRUBY" "$NETWORKEXTENSION" || die "Failed to merge xcode with wireguard" print G "done." print Y "Opening in XCode..." open MozillaVPN.xcodeproj print G "All done!" mozilla-vpn-client-2.2.0/scripts/apply-format000077500000000000000000000242631404202232700212720ustar00rootroot00000000000000#! /bin/bash # # Copyright 2018 Undo Ltd. # # https://github.com/barisione/clang-format-hooks # Force variable declaration before access. set -u # Make any failure in piped commands be reflected in the exit code. set -o pipefail readonly bash_source="${BASH_SOURCE[0]:-$0}" ################## # Misc functions # ################## function error_exit() { for str in "$@"; do echo -n "$str" >&2 done echo >&2 exit 1 } ######################## # Command line parsing # ######################## function show_help() { if [ -t 1 ] && hash tput 2> /dev/null; then local -r b=$(tput bold) local -r i=$(tput sitm) local -r n=$(tput sgr0) else local -r b= local -r i= local -r n= fi cat << EOF ${b}SYNOPSIS${n} To reformat git diffs: ${i}$bash_source [OPTIONS] [FILES-OR-GIT-DIFF-OPTIONS]${n} To reformat whole files, including unchanged parts: ${i}$bash_source [-f | --whole-file] FILES${n} ${b}DESCRIPTION${n} Reformat C or C++ code to match a specified formatting style. This command can either work on diffs, to reformat only changed parts of the code, or on whole files (if -f or --whole-file is used). ${b}FILES-OR-GIT-DIFF-OPTIONS${n} List of files to consider when applying clang-format to a diff. This is passed to "git diff" as is, so it can also include extra git options or revisions. For example, to apply clang-format on the changes made in the last few revisions you could use: ${i}\$ $bash_source HEAD~3${n} ${b}FILES${n} List of files to completely reformat. ${b}-f, --whole-file${n} Reformat the specified files completely (including parts you didn't change). The fix is printed on stdout by default. Use -i if you want to modify the files on disk. ${b}--staged, --cached${n} Reformat only code which is staged for commit. The fix is printed on stdout by default. Use -i if you want to modify the files on disk. ${b}-i${n} Reformat the code and apply the changes to the files on disk (instead of just printing the fix on stdout). ${b}--apply-to-staged${n} This is like specifying both --staged and -i, but the formatting changes are also staged for commit (so you can just use "git commit" to commit what you planned to, but formatted correctly). ${b}--style STYLE${n} The style to use for reformatting code. If no style is specified, then it's assumed there's a .clang-format file in the current directory or one of its parents. ${b}--help, -h, -?${n} Show this help. EOF } # getopts doesn't support long options. # getopt mangles stuff. # So we parse manually... declare positionals=() declare has_positionals=false declare whole_file=false declare apply_to_staged=false declare staged=false declare in_place=false declare style=file declare ignored=() while [ $# -gt 0 ]; do declare arg="$1" shift # Past option. case "$arg" in -h | -\? | --help ) show_help exit 0 ;; -f | --whole-file ) whole_file=true ;; --apply-to-staged ) apply_to_staged=true ;; --cached | --staged ) staged=true ;; -i ) in_place=true ;; --style=* ) style="${arg//--style=/}" ;; --style ) [ $# -gt 0 ] || \ error_exit "No argument for --style option." style="$1" shift ;; --internal-opt-ignore-regex=* ) ignored+=("${arg//--internal-opt-ignore-regex=/}") ;; --internal-opt-ignore-regex ) ignored+=("${arg//--internal-opt-ignore-regex=/}") [ $# -gt 0 ] || \ error_exit "No argument for --internal-opt-ignore-regex option." ignored+=("$1") shift ;; -- ) # Stop processing further arguments. if [ $# -gt 0 ]; then positionals+=("$@") has_positionals=true fi break ;; -* ) error_exit "Unknown argument: $arg" ;; *) positionals+=("$arg") ;; esac done # Restore positional arguments, access them from "$@". if [ ${#positionals[@]} -gt 0 ]; then set -- "${positionals[@]}" has_positionals=true fi [ -n "$style" ] || \ error_exit "If you use --style you need to specify a valid style." ####################################### # Detection of clang-format & friends # ####################################### # clang-format. declare format="${CLANG_FORMAT:-}" if [ -z "$format" ]; then format=$(type -p clang-format) fi if [ -z "$format" ]; then error_exit \ $'You need to install clang-format.\n' \ $'\n' \ $'On Ubuntu/Debian this is available in the clang-format package or, in\n' \ $'older distro versions, clang-format-VERSION.\n' \ $'On Fedora it\'s available in the clang package.\n' \ $'You can also specify your own path for clang-format by setting the\n' \ $'$CLANG_FORMAT environment variable.' fi # clang-format-diff. if [ "$whole_file" = false ]; then invalid="/dev/null/invalid/path" if [ "${OSTYPE:-}" = "linux-gnu" ]; then readonly sort_version=-V else # On macOS, sort doesn't have -V. readonly sort_version=-n fi declare paths_to_try=() # .deb packages directly from upstream. # We try these first as they are probably newer than the system ones. while read -r f; do paths_to_try+=("$f") done < <(compgen -G "/usr/share/clang/clang-format-*/clang-format-diff.py" | sort "$sort_version" -r) # LLVM official releases (just untarred in /usr/local). while read -r f; do paths_to_try+=("$f") done < <(compgen -G "/usr/local/clang+llvm*/share/clang/clang-format-diff.py" | sort "$sort_version" -r) # Maybe it's in the $PATH already? This is true for Ubuntu and Debian. paths_to_try+=( \ "$(type -p clang-format-diff 2> /dev/null || echo "$invalid")" \ "$(type -p clang-format-diff.py 2> /dev/null || echo "$invalid")" \ ) # Fedora. paths_to_try+=( \ /usr/share/clang/clang-format-diff.py \ ) # Gentoo. while read -r f; do paths_to_try+=("$f") done < <(compgen -G "/usr/lib/llvm/*/share/clang/clang-format-diff.py" | sort -n -r) # Homebrew. while read -r f; do paths_to_try+=("$f") done < <(compgen -G "/usr/local/Cellar/clang-format/*/share/clang/clang-format-diff.py" | sort -n -r) declare format_diff= # Did the user specify a path? if [ -n "${CLANG_FORMAT_DIFF:-}" ]; then format_diff="$CLANG_FORMAT_DIFF" else for path in "${paths_to_try[@]}"; do if [ -e "$path" ]; then # Found! format_diff="$path" if [ ! -x "$format_diff" ]; then format_diff="python $format_diff" fi break fi done fi if [ -z "$format_diff" ]; then error_exit \ $'Cannot find clang-format-diff which should be shipped as part of the same\n' \ $'package where clang-format is.\n' \ $'\n' \ $'Please find out where clang-format-diff is in your distro and report an issue\n' \ $'at https://github.com/barisione/clang-format-hooks/issues with details about\n' \ $'your operating system and setup.\n' \ $'\n' \ $'You can also specify your own path for clang-format-diff by setting the\n' \ $'$CLANG_FORMAT_DIFF environment variable, for instance:\n' \ $'\n' \ $' CLANG_FORMAT_DIFF="python /.../clang-format-diff.py" \\\n' \ $' ' "$bash_source" fi readonly format_diff fi ############################ # Actually run the command # ############################ if [ "$whole_file" = true ]; then [ "$has_positionals" = true ] || \ error_exit "No files to reformat specified." [ "$staged" = false ] || \ error_exit "--staged/--cached only make sense when applying to a diff." read -r -a format_args <<< "$format" format_args+=("-style=file") [ "$in_place" = true ] && format_args+=("-i") "${format_args[@]}" "$@" else # Diff-only. if [ "$apply_to_staged" = true ]; then [ "$staged" = false ] || \ error_exit "You don't need --staged/--cached with --apply-to-staged." [ "$in_place" = false ] || \ error_exit "You don't need -i with --apply-to-staged." staged=true readonly patch_dest=$(mktemp) trap '{ rm -f "$patch_dest"; }' EXIT else readonly patch_dest=/dev/stdout fi declare git_args=(git diff -U0 --no-color) [ "$staged" = true ] && git_args+=("--staged") # $format_diff may contain a command ("python") and the script to excute, so we # need to split it. read -r -a format_diff_args <<< "$format_diff" [ "$in_place" = true ] && format_diff_args+=("-i") # Build the regex for paths to consider or ignore. # We use negative lookahead assertions which preceed the list of allowed patterns # (that is, the extensions we want). exclusions_regex= if [ "${#ignored[@]}" -gt 0 ]; then for pattern in "${ignored[@]}"; do exclusions_regex="$exclusions_regex(?!$pattern)" done fi "${git_args[@]}" "$@" \ | "${format_diff_args[@]}" \ -p1 \ -style="$style" \ -iregex="$exclusions_regex"'.*\.(c|cpp|cxx|cc|h|hpp|m|mm|js|java)' \ > "$patch_dest" \ || exit 1 if [ "$apply_to_staged" = true ]; then if [ ! -s "$patch_dest" ]; then echo "No formatting changes to apply." exit 0 fi patch -p0 < "$patch_dest" || \ error_exit "Cannot apply fix to local files." git apply -p0 --cached < "$patch_dest" || \ error_exit "Cannot apply fix to git staged changes." fi fi mozilla-vpn-client-2.2.0/scripts/commons.sh000066400000000000000000000017561404202232700207420ustar00rootroot00000000000000#!/bin/bash # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. printv() { if [ -t 1 ]; then NCOLORS=$(tput colors) if test -n "$NCOLORS" && test "$NCOLORS" -ge 8; then NORMAL="$(tput sgr0)" RED="$(tput setaf 1)" GREEN="$(tput setaf 2)" YELLOW="$(tput setaf 3)" fi fi if [[ $2 = 'G' ]]; then # shellcheck disable=SC2086 echo $1 -e "${GREEN}$3${NORMAL}" elif [[ $2 = 'Y' ]]; then # shellcheck disable=SC2086 echo $1 -e "${YELLOW}$3${NORMAL}" elif [[ $2 = 'N' ]]; then # shellcheck disable=SC2086 echo $1 -e "$3" else # shellcheck disable=SC2086 echo $1 -e "${RED}$3${NORMAL}" fi } print() { printv '' "$1" "$2" } printn() { printv "-n" "$1" "$2" } error() { printv '' R "$1" } die() { if [[ "$1" ]]; then error "$1" else error Failed fi exit 1 } mozilla-vpn-client-2.2.0/scripts/generate_i18n_servers.sh000077500000000000000000000023321404202232700234630ustar00rootroot00000000000000#!/bin/bash # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. . $(dirname $0)/commons.sh print N "This script generates the i18n servers.json" print N "" if [ "$1" == "" ] || ! [ -f "$1" ]; then print G "Usage:" print N "\t$0 /path/mozillavpn" exit 1 fi if ! [ -d "src" ] || ! [ -d "tests" ]; then die "This script must be executed at the root of the repository." fi printn Y "Retrieving mozillavpn version... " "$1" -v 2>/dev/null || die "Failed." print G "done." print Y "Running the app..." "$1" &>/tmp/VPN_LOG.txt & PID=$! print G "done." print Y "Running the localization script..." export SERVER_OUTPUT=translations/servers.json export SERVER_API=translations/servers-api.json export SERVER_TEMPLATE=translations/servers-template.json mocha tests/functional/localizeServers.js || ERROR=yes wait $PID if [ "$ERROR" = yes ]; then cat /tmp/VPN_LOG.txt print R "Nooo" exit 1 fi echo "All done! The final server list can be found here: $SERVER_OUTPUT" echo "This is the merging of the generated server names ($SERVER_API) and the template file ($SERVER_TEMPLATE)." mozilla-vpn-client-2.2.0/scripts/git-pre-commit-format000077500000000000000000000301771404202232700230030ustar00rootroot00000000000000#! /bin/bash # # Copyright 2018 Undo Ltd. # # https://github.com/barisione/clang-format-hooks # Force variable declaration before access. set -u # Make any failure in piped commands be reflected in the exit code. set -o pipefail readonly bash_source="${BASH_SOURCE[0]:-$0}" if [ -t 1 ] && hash tput 2> /dev/null; then readonly b=$(tput bold) readonly i=$(tput sitm) readonly n=$(tput sgr0) else readonly b= readonly i= readonly n= fi function error_exit() { for str in "$@"; do echo -n "$b$str$n" >&2 done echo >&2 exit 1 } # realpath is not available everywhere. function realpath() { if [ "${OSTYPE:-}" = "linux-gnu" ]; then readlink -m "$@" else # Python should always be available on macOS. # We use sys.stdout.write instead of print so it's compatible with both Python 2 and 3. python -c "import sys; import os.path; sys.stdout.write(os.path.realpath('''$1''') + '\\n')" fi } # realpath --relative-to is only available on recent Linux distros. # This function behaves identical to Python's os.path.relpath() and doesn't need files to exist. function rel_realpath() { local -r path=$(realpath "$1") local -r rel_to=$(realpath "${2:-$PWD}") # Split the paths into components. IFS='/' read -r -a path_parts <<< "$path" IFS='/' read -r -a rel_to_parts <<< "$rel_to" # Search for the first different component. for ((idx=1; idx<${#path_parts[@]}; idx++)); do if [ "${path_parts[idx]}" != "${rel_to_parts[idx]:-}" ]; then break fi done result=() # Add the required ".." to the $result array. local -r first_different_idx="$idx" for ((idx=first_different_idx; idx<${#rel_to_parts[@]}; idx++)); do result+=("..") done # Add the required components from $path. for ((idx=first_different_idx; idx<${#path_parts[@]}; idx++)); do result+=("${path_parts[idx]}") done if [ "${#result[@]}" -gt 0 ]; then # Join the array with a "/" as separator. echo "$(export IFS='/'; echo "${result[*]}")" else echo . fi } # Find the top-level git directory (taking into account we could be in a submodule). declare git_test_dir=. declare top_dir while true; do top_dir=$(git -C "$git_test_dir" rev-parse --show-toplevel) || \ error_exit "You need to be in the git repository to run this script." # Try to handle git worktree. # The best way to deal both with git submodules and worktrees would be to # use --show-superproject-working-tree, but it's not supported in git 2.7.4 # which is shipped in Ubuntu 16.04. declare git_common_dir if git_common_dir=$(git -C "$git_test_dir" rev-parse --git-common-dir 2>/dev/null); then # The common dir could be relative, so we make it absolute. git_common_dir=$(cd "$git_test_dir" && realpath "$git_common_dir") declare maybe_top_dir maybe_top_dir=$(realpath "$git_common_dir/..") if [ -e "$maybe_top_dir/.git" ]; then # We are not in a submodules, otherwise common dir would have been # something like PROJ/.git/modules/SUBMODULE and there would not be # a .git directory in PROJ/.git/modules/. top_dir="$maybe_top_dir" fi fi [ -e "$top_dir/.git" ] || \ error_exit "No .git directory in $top_dir." if [ -d "$top_dir/.git" ]; then # We are done! top_dir is the root git directory. break elif [ -f "$top_dir/.git" ]; then # We are in a submodule. git_test_dir="$git_test_dir/.." fi done readonly top_dir hook_path="$top_dir/.git/hooks/pre-commit" readonly hook_path me=$(realpath "$bash_source") || exit 1 readonly me me_relative_to_hook=$(rel_realpath "$me" "$(dirname "$hook_path")") || exit 1 readonly me_relative_to_hook my_dir=$(dirname "$me") || exit 1 readonly my_dir apply_format="$my_dir/apply-format" readonly apply_format apply_format_relative_to_top_dir=$(rel_realpath "$apply_format" "$top_dir") || exit 1 readonly apply_format_relative_to_top_dir function is_installed() { if [ ! -e "$hook_path" ]; then echo nothing else existing_hook_target=$(realpath "$hook_path") || exit 1 readonly existing_hook_target if [ "$existing_hook_target" = "$me" ]; then # Already installed. echo installed else # There's a hook, but it's not us. echo different fi fi } function install() { if ln -s "$me_relative_to_hook" "$hook_path" 2> /dev/null; then echo "Pre-commit hook installed." else local -r res=$(is_installed) if [ "$res" = installed ]; then error_exit "The hook is already installed." elif [ "$res" = different ]; then error_exit "There's already an existing pre-commit hook, but for something else." elif [ "$res" = nothing ]; then error_exit "There's no pre-commit hook, but we couldn't create a symlink." else error_exit "Unexpected failure." fi fi } function uninstall() { local -r res=$(is_installed) if [ "$res" = installed ]; then rm "$hook_path" || \ error_exit "Couldn't remove the pre-commit hook." elif [ "$res" = different ]; then error_exit "There's a pre-commit hook installed, but for something else. Not removing." elif [ "$res" = nothing ]; then error_exit "There's no pre-commit hook, nothing to uninstall." else error_exit "Unexpected failure detecting the pre-commit hook status." fi } function show_help() { cat << EOF ${b}SYNOPSIS${n} $bash_source [install|uninstall] ${b}DESCRIPTION${n} Git hook to verify and fix formatting before committing. The script is invoked automatically when you commit, so you need to call it directly only to set up the hook or remove it. To setup the hook run this script passing "install" on the command line. To remove the hook run passing "uninstall". ${b}CONFIGURATION${n} You can configure the hook using the "git config" command. ${b}hooks.clangFormatDiffInteractive${n} (default: true) By default, the hook requires user input. If you don't run git from a terminal, you can disable the interactive prompt with: ${i}\$ git config hooks.clangFormatDiffInteractive false${n} ${b}hooks.clangFormatDiffStyle${n} (default: file) Unless a different style is specified, the hook expects a file named .clang-format to exist in the repository. This file should contain the configuration for clang-format. You can specify a different style (in this example, the WebKit one) with: ${i}\$ git config hooks.clangFormatDiffStyle WebKit${n} EOF } if [ $# = 1 ]; then case "$1" in -h | -\? | --help ) show_help exit 0 ;; install ) install exit 0 ;; uninstall ) uninstall exit 0 ;; esac fi [ $# = 0 ] || error_exit "Invalid arguments: $*" # This is a real run of the hook, not a install/uninstall run. if [ -z "${GIT_DIR:-}" ] && [ -z "${GIT_INDEX_FILE:-}" ]; then error_exit \ $'It looks like you invoked this script directly, but it\'s supposed to be used\n' \ $'as a pre-commit git hook.\n' \ $'\n' \ $'To install the hook try:\n' \ $' ' "$bash_source" $' install\n' \ $'\n' \ $'For more details on this script try:\n' \ $' ' "$bash_source" $' --help\n' fi [ -x "$apply_format" ] || \ error_exit \ $'Cannot find the apply-format script.\n' \ $'I expected it here:\n' \ $' ' "$apply_format" readonly style=$(cd "$top_dir" && git config hooks.clangFormatDiffStyle || echo file) apply_format_opts=( "--style=$style" --cached ) readonly exclusions_file="$top_dir/.clang-format-hook-exclude" if [ -e "$exclusions_file" ]; then while IFS= read -r line; do if [[ "$line" && "$line" != "#"* ]]; then apply_format_opts+=("--internal-opt-ignore-regex=$line") fi done < "$exclusions_file" fi readonly patch=$(mktemp) trap '{ rm -f "$patch"; }' EXIT "$apply_format" --style="$style" --cached "${apply_format_opts[@]}" > "$patch" || \ error_exit $'\nThe apply-format script failed.' if [ "$(wc -l < "$patch")" -eq 0 ]; then echo "The staged content is formatted correctly." exit 0 fi # The code is not formatted correctly. interactive=$(cd "$top_dir" && git config --bool hooks.clangFormatDiffInteractive) if [ "$interactive" != false ]; then # Interactive is the default, so anything that is not false is converted to # true, including possibly invalid values. interactive=true fi readonly interactive if [ "$interactive" = false ]; then echo "${b}The staged content is not formatted correctly.${n}" echo "You can fix the formatting with:" echo " ${i}\$ ./$apply_format_relative_to_top_dir --apply-to-staged${n}" echo echo "You can also make this script interactive (if you use git from a terminal) with:" echo " ${i}\$ git config hooks.clangFormatDiffInteractive true${n}" exit 1 fi if hash colordiff 2> /dev/null; then colordiff < "$patch" else echo "${b}(Install colordiff to see this diff in color!)${n}" echo cat "$patch" fi # We don't want to suggest applying clang-format after merge resolution if git rev-parse MERGE_HEAD >/dev/null 2>&1; then readonly this_is_a_merge=true else readonly this_is_a_merge=false fi echo echo "${b}The staged content is not formatted correctly.${n}" echo "The fix shown above can be applied automatically to the commit." echo if $this_is_a_merge; then echo "${b}You appear to be committing the result of a merge. It is not${n}" echo "${b}recommended to apply the fix if it will reformat any code you${n}" echo "${b}did not modify in your branch.${n}" echo readonly recommend_apply=" (not recommended for merge!)" readonly recommend_force="" readonly bold_apply="" readonly bold_force="${b}" else readonly recommend_apply="" readonly recommend_force=" (not recommended!)" readonly bold_apply="${b}" readonly bold_force="" fi echo "You can:" echo " ${bold_apply}[a]: Apply the fix${recommend_apply}${n}" echo " ${bold_force}[f]: Force and commit anyway${recommend_force}${n}" echo " [c]: Cancel the commit" echo " [?]: Show help" echo readonly tty=${PRE_COMMIT_HOOK_TTY:-/dev/tty} while true; do echo -n "What would you like to do? [a/f/c/?] " read -r answer < "$tty" case "$answer" in [aA] ) patch -p0 < "$patch" || \ error_exit \ $'\n' \ $'Cannot apply fix to local files.\n' \ $'Have you modified the file locally after starting the commit?' git apply -p0 --cached < "$patch" || \ error_exit \ $'\n' \ $'Cannot apply fix to git staged changes.\n' \ $'This may happen if you have some overlapping unstaged changes. To solve\n' \ $'you need to stage or reset changes manually.' if $this_is_a_merge; then echo echo "Applied the fix to reformat the merge commit." echo "You can always abort by quitting your editor with no commit message." echo echo -n "Press return to continue." read -r < "$tty" fi ;; [fF] ) echo if ! $this_is_a_merge; then echo "Will commit anyway!" echo "You can always abort by quitting your editor with no commit message." echo echo -n "Press return to continue." read -r < "$tty" fi exit 0 ;; [cC] ) error_exit "Commit aborted as requested." ;; \? ) echo show_help echo continue ;; * ) echo 'Invalid answer. Type "a", "f" or "c".' echo continue esac break done mozilla-vpn-client-2.2.0/scripts/importLanguages.py000066400000000000000000000056511404202232700224440ustar00rootroot00000000000000#! /usr/bin/env python3 # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. # This script must be executed at the root of the repository. import argparse import xml.etree.ElementTree as ET import os import sys # Include only locales above this threshold (e.g. 70%) in production l10n_threshold = 0.70 parser = argparse.ArgumentParser() parser.add_argument( '-p', '--prod', default=False, action="store_true", dest="isprod", help='Build only for production locales.') args = parser.parse_args() # Step 1 # Go through the i18n repo, check each XLIFF file and take # note which locale is complete above the minimum threshold. # Adds path of .xliff and .ts to l10n_files. l10n_files = [] for locale in os.listdir('i18n'): # Skip non folders if not os.path.isdir(os.path.join('i18n', locale)): continue # Skip hidden folders if locale.startswith('.'): continue xliff_path = os.path.join('i18n', locale, 'mozillavpn.xliff') # If it's the source locale (en), ignore parsing for completeness and # add it to the list. if locale == 'en': print(f'OK\t- en added (reference locale)') l10n_files.append({ 'ts': os.path.join('translations', f'mozillavpn_en.ts'), 'xliff': xliff_path }) continue tree = ET.parse(xliff_path) root = tree.getroot() sources = 0 translations = 0 for element in root.iter('{urn:oasis:names:tc:xliff:document:1.2}source'): sources += 1 for element in root.iter('{urn:oasis:names:tc:xliff:document:1.2}target'): translations += 1 completeness = translations/(sources*1.0) # Ignore locale with less than 70% of completeness for production builds if args.isprod and completeness < l10n_threshold: print(f'KO\t- {locale} is translated at {round(completeness*100, 2)}%, at least {l10n_threshold*100}% is needed') continue # Not enough translations next file please baseName = f'mozillavpn_{locale}' print(f'OK\t- {locale} added ({round(completeness*100, 2)}% translated)') l10n_files.append({ 'ts': os.path.join('translations', f'{baseName}.ts'), 'xliff': xliff_path }) # Step 2 # Write PRI file to import the locales that are ready with open('translations/translations.pri', 'w') as pri_file: output = [] output.append('TRANSLATIONS += \\ ') for file in l10n_files: output.append(f"../{file['ts']} \\ ") output.append('\n \n##End') pri_file.write('\n'.join(output)) print('Updated translations.pri') # Step 3 # Generate new ts files os.system('lupdate src/src.pro') # Step 4 # Now import translations into the files for l10n_file in l10n_files: os.system(f"lconvert -if xlf -i {l10n_file['xliff']} -o {l10n_file['ts']}") print(f'Imported {len(l10n_files)} locales') mozilla-vpn-client-2.2.0/scripts/linux_script.sh000077500000000000000000000065671404202232700220220ustar00rootroot00000000000000#!/bin/bash # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. . $(dirname $0)/commons.sh VERSION=1 RELEASE=focal STAGE= if [ -f .env ]; then . .env fi helpFunction() { print G "Usage:" print N "\t$0 [-r|--release ] [-v|--version ] [-s|--stage]" print N "" print N "By default, the release is 'focal'" print N "The default version is 1, but you can recreate packages using the same code version changing the version id." print N "" exit 0 } print N "This script compiles MozillaVPN and creates a debian/ubuntu package" print N "" while [[ $# -gt 0 ]]; do key="$1" case $key in -s | --stage) STAGE=1 shift ;; -r | --release) RELEASE="$2" shift shift ;; -v | --version) VERSION="$2" shift shift ;; *) helpFunction ;; esac done [ "$RELEASE" != "focal" ] && [ "$RELEASE" != "groovy" ] && [ "$RELEASE" != "bionic" ] && die "We support RELEASE focal, groovy and bionic only" printn Y "Computing the version... " SHORTVERSION=$(cat version.pri | grep VERSION | grep defined | cut -d= -f2 | tr -d \ ) FULLVERSION=$(echo $SHORTVERSION | cut -d. -f1).$(date +"%Y%m%d%H%M") print G "$SHORTVERSION - $FULLVERSION" rm -rf .tmp || die "Failed to remove the temporary directory" mkdir .tmp || die "Failed to create the temporary directory" print Y "Update the submodules..." git submodule init || die "Failed" git submodule update --remote --depth 1 i18n || die "Failed" git submodule update --remote --depth 1 3rdparty/wireguard-tools || die "Failed" print G "done." print G "Creating the orig tarball" printn N "Creating the working directory... " mkdir -p .tmp/mozillavpn-$SHORTVERSION || die "Failed" cp -R * .tmp/mozillavpn-$SHORTVERSION 2>/dev/null || die "Failed" print G "done." printn Y "Changing directory... " cd .tmp/mozillavpn-$SHORTVERSION || die "Failed" print G "done." print Y "Importing translation files..." python3 scripts/importLanguages.py $([[ "$STAGE" ]] && echo "" || echo "-p") || die "Failed to import languages" printn Y "Removing the debian template folder... " rm -rf linux/debian || die "Failed" print G "done." printn Y "Archiving the source code... " tar cfz ../mozillavpn_$SHORTVERSION.orig.tar.gz . || die "Failed" print G "done." print Y "Configuring the debian package for $RELEASE..." cp -r ../../linux/debian . || die "Failed" if [[ "$STAGE" ]]; then print Y "Staging env configured" mv debian/rules.stage.$RELEASE debian/rules || die "Failed" mv debian/control.stage.$RELEASE debian/control || die "Failed" else print Y "Production env configured" mv debian/rules.prod.$RELEASE debian/rules || die "Failed" mv debian/control.prod.$RELEASE debian/control || die "Failed" fi rm debian/control.* || die "Failed" rm debian/rules.stage* || die "Failed" rm debian/rules.prod* || die "Failed" mv debian/changelog.template debian/changelog || die "Failed" sed -i -e "s/SHORTVERSION/$SHORTVERSION/g" debian/changelog || die "Failed" sed -i -e "s/VERSION/$VERSION/g" debian/changelog || die "Failed" sed -i -e "s/RELEASE/$RELEASE/g" debian/changelog || die "Failed" sed -i -e "s/DATE/$(date -R)/g" debian/changelog || die "Failed" sed -i -e "s/FULLVERSION/$FULLVERSION/g" debian/rules || die "Failed" debuild -uc -us || die "Failed" print G "All done." mozilla-vpn-client-2.2.0/scripts/pkg_builder.sh000077500000000000000000000072471404202232700215620ustar00rootroot00000000000000#!/bin/bash # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. . $(dirname $0)/commons.sh SOURCE= DEST= PRODUCT="Mozilla VPN" helpFunction() { print G "Usage:" print N "\t$0 [-s|--source ] [-d|--destination ]" print N "" exit 0 } print N "This script creates a apple pkg file" print N "" while [[ $# -gt 0 ]]; do key="$1" case $key in -s | --source) SOURCE=$2 shift shift ;; -d | --destination) DEST=$2 shift shift ;; *) helpFunction ;; esac done if [[ "$SOURCE" == "" ]] || [[ "$DEST" == "" ]];then helpFunction fi printn Y "Extract the project version... " SHORTVERSION=$(cat version.pri | grep VERSION | grep defined | cut -d= -f2 | tr -d \ ) FULLVERSION=$(echo $SHORTVERSION | cut -d. -f1).$(date +"%Y%m%d%H%M") print G "$SHORTVERSION - $FULLVERSION" printn Y "Cleaning $DEST directory... " rm -rf "${DEST}" || die "Failed to remove the folder" mkdir "${DEST}" || die "Failed to create the folder" print G "done." printn Y "Copy template files... " cp -r macos/pkg "${DEST}/darwin" || die "Failed to copy" chmod -R 755 "${DEST}/darwin/scripts" || die "Failed to set permissions 1" chmod -R 755 "${DEST}/darwin/Resources" || die "Failed to set permissions 2" chmod 755 "${DEST}/darwin/Distribution" || die "Failed to set permissions 3" print G "done." printn Y "Configuring the postinstall script... " chmod -R 755 "${DEST}/darwin/scripts/postinstall" print G "done." printn Y "Configuring the Distribution and resources files... " chmod -R 755 "${DEST}/darwin/Distribution" chmod -R 755 "${DEST}/darwin/Resources/" print G "done." printn Y "Creating the pkg structure... " mkdir -p ${DEST}/darwinpkg || die "Failed to configure the folder" mkdir -p "${DEST}/darwinpkg/Applications" cp -r "${SOURCE}" "${DEST}/darwinpkg/Applications" [[ -d "${DEST}/darwinpkg/Applications/Mozilla VPN.app" ]] || die "The folder name must be 'Mozilla VPN.app'" chmod -R 755 "${DEST}/darwinpkg/Applications/Mozilla VPN.app" mkdir -p "${DEST}/package" chmod -R 755 "${DEST}/package" rm -rf "${DEST}/pkg" mkdir -p "${DEST}/pkg" chmod -R 755 "${DEST}/pkg" print G "done." print Y "Application installer package building started. Step 1" pkgbuild \ --identifier "$(cat xcode.xconfig | grep APP_ID_MACOS | cut -d= -f2)" \ --version ${SHORTVERSION} \ --scripts "${DEST}/darwin/scripts" \ --root "${DEST}/darwinpkg" \ "${DEST}/package/${PRODUCT}.pkg" || die "Failed" print Y "Application installer package building started. Step 2" productbuild \ --distribution "${DEST}/darwin/Distribution" \ --resources "${DEST}/darwin/Resources" \ --package-path "${DEST}/package" \ "${DEST}/pkg/MozillaVPN-${SHORTVERSION}.pkg" while true; do read -p "Do you wish to sign the installer (You should have Apple Developer Certificate) [y/N]?" answer [[ $answer == "y" || $answer == "Y" ]] && FLAG=true && break [[ $answer == "n" || $answer == "N" || $answer == "" ]] && print Y "Skiped signing process." && FLAG=false && break echo "Please answer with 'y' or 'n'" done if [[ $FLAG == "true" ]]; then print Y "Application installer signing process started. Step 3" mkdir -p "${DEST}/pkg-signed" chmod -R 755 "${DEST}/pkg-signed" read -p "Please enter the Apple Developer Installer Certificate ID:" APPLE_DEVELOPER_CERTIFICATE_ID productsign \ --sign "Developer ID Installer: ${APPLE_DEVELOPER_CERTIFICATE_ID}" \ "${DEST}/pkg/MozillaVPN-${SHORTVERSION}.pkg" \ "${DEST}/pkg-signed/MozillaVPN-${SHORTVERSION}.pkg" pkgutil --check-signature "${DEST}/pkg-signed/MozillaVPN-${SHORTVERSION}.pkg" fi mozilla-vpn-client-2.2.0/scripts/ppa_script.sh000077500000000000000000000107421404202232700214310ustar00rootroot00000000000000#!/bin/bash # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. . $(dirname $0)/commons.sh BRANCH= PPA=mozbaku/mozillavpn-focal VERSION=1 RELEASE=focal STAGE= if [ -f .env ]; then . .env fi helpFunction() { print G "Usage:" print N "\t$0 [-b|--branch ] [-p|--ppa ] [-r|--release ] [-v|--version ] [-s|--stage] [-ns|--no-sign] [-o|--orig ]" print N "" print N "By default, the ppa is: mozbaku/mozillavpn" print N "By default, the release is 'focal'" print N "The default version is 1, but you can recreate packages using the same code version changing the version id." print N "" exit 0 } print N "This script compiles MozillaVPN and creates a debian/ubuntu package" print N "" EXTRA_DEBUILD_OPTS="-S" DO_UPLOAD=1 while [[ $# -gt 0 ]]; do key="$1" case $key in -b | --branch) BRANCH="--branch $2" shift shift ;; -p | --ppa) PPA="$2" shift shift ;; -s | --stage) STAGE=1 shift ;; -r | --release) RELEASE="$2" shift shift ;; -v | --version) VERSION="$2" shift shift ;; -o | --orig) ORIGURL="$2" EXTRA_DEBUILD_OPTS="$EXTRA_DEBUILD_OPTS -sd" shift shift ;; -ns | --no-sign) EXTRA_DEBUILD_OPTS="$EXTRA_DEBUILD_OPTS --no-sign" shift ;; -nu | --no-upload) DO_UPLOAD=0 shift ;; *) helpFunction ;; esac done [ "$RELEASE" != "focal" ] && [ "$RELEASE" != "groovy" ] && [ "$RELEASE" != "bionic" ] && die "We support RELEASE focal, groovy and bionic only" printn Y "Computing the version... " SHORTVERSION=$(cat version.pri | grep VERSION | grep defined | cut -d= -f2 | tr -d \ ) FULLVERSION=$(echo $SHORTVERSION | cut -d. -f1).$(date +"%Y%m%d%H%M") print G "$SHORTVERSION - $FULLVERSION" rm -rf .tmp || die "Failed to remove the temporary directory" mkdir .tmp || die "Failed to create the temporary directory" if [[ "$ORIGURL" ]]; then print G "Downloading the orig tarball" cd .tmp || die "Failed" wget $ORIGURL || die "Failed" dn=$(echo *gz | sed s/.orig.tar.gz//) printn Y "Opening the source code... " mkdir $dn || die "Failed" cd $dn || die "Failed" tar xf ../*.orig.tar.gz || die "Failed" print G "done." else print G "Creating the orig tarball" print N "Checking out the code from the git repository..." git clone --depth 1 https://github.com/mozilla-mobile/mozilla-vpn-client .tmp/mozillavpn-$SHORTVERSION $BRANCH || die "Failed" printn Y "Changing directory... " cd .tmp/mozillavpn-$SHORTVERSION || die "Failed" print G "done." print Y "Update the submodules..." git submodule init || die "Failed" git submodule update --remote --depth 1 i18n || die "Failed" git submodule update --remote --depth 1 3rdparty/wireguard-tools || die "Failed" print G "done." print Y "Importing translation files..." python3 scripts/importLanguages.py $([[ "$STAGE" ]] && echo "" || echo "-p") || die "Failed to import languages" printn Y "Removing the debian template folder... " rm -rf linux/debian || die "Failed" print G "done." printn Y "Archiving the source code... " tar cfz ../mozillavpn_$SHORTVERSION.orig.tar.gz . || die "Failed" print G "done." fi print Y "Configuring the debian package for $RELEASE..." cp -r ../../linux/debian . || die "Failed" if [[ "$STAGE" ]]; then print Y "Staging env configured" mv debian/rules.stage.$RELEASE debian/rules || die "Failed" mv debian/control.stage.$RELEASE debian/control || die "Failed" else print Y "Production env configured" mv debian/rules.prod.$RELEASE debian/rules || die "Failed" mv debian/control.prod.$RELEASE debian/control || die "Failed" fi rm debian/control.* || die "Failed" rm debian/rules.stage* || die "Failed" rm debian/rules.prod* || die "Failed" mv debian/changelog.template debian/changelog || die "Failed" sed -i -e "s/SHORTVERSION/$SHORTVERSION/g" debian/changelog || die "Failed" sed -i -e "s/VERSION/$VERSION/g" debian/changelog || die "Failed" sed -i -e "s/RELEASE/$RELEASE/g" debian/changelog || die "Failed" sed -i -e "s/DATE/$(date -R)/g" debian/changelog || die "Failed" sed -i -e "s/FULLVERSION/$FULLVERSION/g" debian/rules || die "Failed" debuild $EXTRA_DEBUILD_OPTS || die "Failed" if [[ "$DO_UPLOAD" == "1" ]]; then print Y "Upload the changes to the ppa..." cd .. || die "Failed" dput ppa:$PPA mozillavpn*.changes || die "Failed" fi print G "All done." mozilla-vpn-client-2.2.0/scripts/qt5_compile.bat000066400000000000000000000042351404202232700216370ustar00rootroot00000000000000:: This Source Code Form is subject to the terms of the Mozilla Public :: License, v. 2.0. If a copy of the MPL was not distributed with this :: file, You can obtain one at http://mozilla.org/MPL/2.0/. @ECHO off SETLOCAL IF EXIST env.bat ( CALL env.bat ) IF "%selfWrapped%" == "" ( :: This is necessary so that we can use "EXIT" to terminate the batch file, :: and all subroutines, but not the original cmd.exe SET selfWrapped=true %ComSpec% /s /c ""%~0" %*" GOTO :EOF ) ECHO This script compiles Qt5 statically for windows IF [%2] == [] ( ECHO Usage: %1 /openssl/src/path /Qt5.15/src/path EXIT 1 ) IF NOT EXIST %1 ( ECHO %1 doesn't exist. EXIT 1 ) IF NOT EXIST %2 ( ECHO %2 doesn't exist. EXIT 1 ) ECHO Checking required commands... CALL :CheckCommand perl CALL :CheckCommand nasm CALL :CheckCommand python CALL :CheckCommand nmake CALL :CheckCommand cl ECHO Compiling openssl... cd %1 perl Configure VC-WIN64A --release --prefix=c:\MozillaVPNBuild --openssldir=c:\MozillaVPNBuild\SSL IF %ERRORLEVEL% NEQ 0 ( ECHO Failed to configure OpenSSL. EXIT 1 ) nmake IF %ERRORLEVEL% NEQ 0 ( ECHO Failed to compile OpenSSL. EXIT 1 ) nmake install IF %ERRORLEVEL% NEQ 0 ( ECHO Failed to install OpenSSL. EXIT 1 ) ECHO Compiling QT5... cd %2 IF NOT EXIST configure.bat ( ECHO This doesn't look like the QT5.15 source folder. EXIT /B 1 ) configure -static -opensource -release -no-dbus -no-feature-qdbus -confirm-license -strip -silent -no-compile-examples -nomake tests -make libs -no-sql-psql -no-sql-sqlite -skip qt3d -skip webengine -skip qtmultimedia -skip qtserialport -skip qtsensors -skip qtgamepad -skip qtwebchannel -skip qtandroidextras -feature-imageformat_png -qt-libpng -qt-zlib -recheck-all -openssl-linked -I c:\MozillaVPNBuild\include -L c:\MozillaVPNBuild\lib -prefix c:\MozillaVPNBuild IF %ERRORLEVEL% NEQ 0 ( ECHO Failed to configure QT5. EXIT /B 1 ) nmake IF %ERRORLEVEL% NEQ 0 ( ECHO Failed to compile QT5. EXIT /B 1 ) nmake install IF %ERRORLEVEL% NEQ 0 ( ECHO Failed to install QT5. EXIT /B 1 ) ECHO All done. EXIT 0 :CheckCommand WHERE %~1 > nul IF %ERRORLEVEL% NEQ 0 ( ECHO Command `%~1` has not been found. EXIT 1 ) mozilla-vpn-client-2.2.0/scripts/qt5_compile.sh000077500000000000000000000042431404202232700215050ustar00rootroot00000000000000#!/bin/bash # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. . $(dirname $0)/commons.sh POSITIONAL=() JOBS=8 helpFunction() { print G "Usage:" print N "\t$0 [-j|--jobs ] [anything else will be use as argument for the QT configure script]" print N "" exit 0 } print N "This script compiles Qt5 statically" print N "" while [[ $# -gt 0 ]]; do key="$1" case $key in -j | --jobs) JOBS="$2" shift shift ;; -h | --help) helpFunction ;; *) POSITIONAL+=("$1") shift ;; esac done set -- "${POSITIONAL[@]}" # restore positional parameters if [[ $# -lt 2 ]]; then helpFunction fi [ -d "$1" ] || die "Unable to find the QT source folder." cd "$1" || die "Unable to enter into the QT source folder" shift PREFIX=$1 shift printn Y "Cleaning the folder... " make distclean -j $JOBS &>/dev/null; print G "done." LINUX=" -platform linux-clang \ -egl \ -opengl es2 \ -no-linuxfb \ -bundled-xcb-xinput \ -xcb \ " MACOS=" -appstore-compliant \ -no-feature-qdbus \ -no-speechd " if [[ "$OSTYPE" == "linux-gnu"* ]]; then print N "Configure for linux" PLATFORM=$LINUX elif [[ "$OSTYPE" == "darwin"* ]]; then print N "Configure for darwin" PLATFORM=$MACOS else die "Unsupported platform (yet?)" fi print Y "Wait..." ./configure \ $* \ --prefix=$PREFIX \ --recheck-all \ -opensource \ -confirm-license \ -release \ -static \ -strip \ -silent \ -no-compile-examples \ -nomake tests \ -make libs \ -no-sql-psql \ -no-sql-sqlite \ -skip qt3d \ -skip webengine \ -skip qtmultimedia \ -skip qtserialport \ -skip qtsensors \ -skip qtgamepad \ -skip qtwebchannel \ -skip qtandroidextras \ -feature-imageformat_png \ -qt-doubleconversion \ -qt-libpng \ -qt-zlib \ -qt-pcre \ -qt-freetype \ $PLATFORM || die "Configuration error." print Y "Compiling..." make -j $JOBS || die "Make failed" print Y "Installing..." make -j $JOBS install || die "Make install failed" print G "All done!" mozilla-vpn-client-2.2.0/scripts/test_coverage.sh000077500000000000000000000023241404202232700221140ustar00rootroot00000000000000#!/bin/bash # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. . $(dirname $0)/commons.sh export LLVM_PROFILE_FILE=/tmp/mozillavpn.llvm REPORT_FILE=/tmp/report.html print N "This script runs the unit tests and shows the test coverage." print N "" if ! [ -d "src" ] || ! [ -d "tests" ]; then die "This script must be executed at the root of the repository." fi print Y "Compiling..." make || die "Failed to compile" print G "done." print Y "Running tests..." ./tests/unit/tests || die "Failed to run tests" print G "done." if ! [ -f $LLVM_PROFILE_FILE ]; then die "No report generated!" fi printn Y "Merge the profile data... " llvm-profdata-10 merge $LLVM_PROFILE_FILE -o $LLVM_PROFILE_FILE-final || die "Failed to merge the coverage report" print G "done." print Y "Report:" llvm-cov-10 report ./tests/unit/tests -instr-profile=$LLVM_PROFILE_FILE-final src printn Y "Generating the HTML report... " llvm-cov-10 show ./tests/unit/tests -instr-profile=$LLVM_PROFILE_FILE-final src -format=html > $REPORT_FILE || die "Failed to generate the HTML report" print G $REPORT_FILE mozilla-vpn-client-2.2.0/scripts/test_function.sh000077500000000000000000000037431404202232700221540ustar00rootroot00000000000000#!/bin/bash # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. . $(dirname $0)/commons.sh export LLVM_PROFILE_FILE=/tmp/mozillavpn.llvm-0 REPORT_FILE=/tmp/report.html print N "This script runs the functional tests" print N "" ID=0 runTest() { ID=$((ID+1)) export LLVM_PROFILE_FILE=/tmp/mozillavpn.llvm-$ID print Y "Running the app..." "$1" &>/tmp/VPN_LOG.txt & PID=$! print G "done." print Y "Running the test: $2" mocha $2 || ERROR=yes wait $PID if [ "$ERROR" = yes ]; then cat /tmp/VPN_LOG.txt print R "Nooo" exit 1 fi } if [ "$1" == "" ] || ! [ -f "$1" ]; then print G "Usage:" print N "\t$0 /path/mozillavpn" exit 1 fi if ! [ -d "src" ] || ! [ -d "tests" ]; then die "This script must be executed at the root of the repository." fi APP=$1 printn Y "Retrieving mozillavpn version... " "$APP" -v 2>/dev/null || die "Failed." print G "done." shift if [ $# -ne 0 ]; then for i in $*; do runTest "$APP" "$i" done else for i in tests/functional/test*; do runTest "$APP" "$i" done fi FILES=() for ((I=0; $I <= $ID; I=$I+1)); do LLVM_PROFILE_FILE=/tmp/mozillavpn.llvm-$I if ! [ -f $LLVM_PROFILE_FILE ]; then print Y "$LLVM_PROFILE_FILE: No report generated!" continue fi FILES[$I]="$LLVM_PROFILE_FILE" done if [ ${#FILES[*]} == 0 ]; then print Y "No reports generated" exit 0 fi printn Y "Merge the profile data... " llvm-profdata-10 merge ${FILES[@]} -o /tmp/mozillavpn.llvm-final || die "Failed to merge the coverage report" print G "done." print Y "Report:" llvm-cov-10 report "$APP" -instr-profile=/tmp/mozillavpn.llvm-final src || die "Failed to create the report" printn Y "Generating the HTML report... " llvm-cov-10 show "$APP" -instr-profile=/tmp/mozillavpn.llvm-final src -format=html > $REPORT_FILE || die "Failed to generate the HTML report" print G $REPORT_FILE mozilla-vpn-client-2.2.0/scripts/wasm_compile.sh000077500000000000000000000040161404202232700217410ustar00rootroot00000000000000#!/bin/bash # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. . $(dirname $0)/commons.sh if [ -f .env ]; then . .env fi WASM_QT_PATH= helpFunction() { print G "Usage:" print N "\t$0 " print N "" exit 0 } print N "This script compiles MozillaVPN for WebAssembly" print N "" while [[ $# -gt 0 ]]; do key="$1" case $key in *) if [[ "$WASM_QT_PATH" ]]; then helpFunction fi WASM_QT_PATH=$1 shift ;; esac done if ! [ -d "$WASM_QT_PATH" ]; then helpFunction fi if ! [ -d "src" ] || ! [ -d "wasm" ]; then die "This script must be executed at the root of the repository." fi printn Y "Extract the project version... " SHORTVERSION=$(cat version.pri | grep VERSION | grep defined | cut -d= -f2 | tr -d \ ) FULLVERSION=$(echo $SHORTVERSION | cut -d. -f1).$(date +"%Y%m%d%H%M") print G "$SHORTVERSION - $FULLVERSION" QMAKE=$WASM_QT_PATH/qmake [ -f "$QMAKE" ] || die "Unable to find qmake at the path $QMAKE" printn Y "Setting PATH var... " export PATH=$PATH:$WASM_QT_PATH print G "done." printn Y "Checking emscripten... " em++ --version &>/dev/null || die "em++ not found. Have you forgotten to load emsdk_env.sh?" em++ --version 2>&1 | grep 1.39.8 &>/dev/null || die "em++ doesn't match the required version: 1.39.8" print G "done." $QMAKE -v &>/dev/null || die "qmake doesn't exist or it fails" print Y "Importing translation files..." python3 scripts/importLanguages.py $([[ "$PROD" ]] && echo "-p" || echo "") || die "Failed to import languages" print Y "Configuring the project via qmake..." $QMAKE CONFIG-=debug CONFIG-=debug_and_release CONFIG+=release BUILD_ID=$FULLVERSION || die "Compilation failed" print Y "Compiling..." make -j8 || die "Compilation failed" printn Y "Moving files... " mv src/mozillavpn.wasm wasm || die "Failed" mv src/mozillavpn.js wasm || die "Failed" mv src/qtloader.js wasm || die "Failed" print G "done." mozilla-vpn-client-2.2.0/scripts/windows_compile.bat000066400000000000000000000101721404202232700226150ustar00rootroot00000000000000:: This Source Code Form is subject to the terms of the Mozilla Public :: License, v. 2.0. If a copy of the MPL was not distributed with this :: file, You can obtain one at http://mozilla.org/MPL/2.0/. @ECHO off SETLOCAL IF EXIST env.bat ( CALL env.bat ) IF "%selfWrapped%" == "" ( :: This is necessary so that we can use "EXIT" to terminate the batch file, :: and all subroutines, but not the original cmd.exe SET selfWrapped=true %ComSpec% /s /c ""%~0" %*" GOTO :EOF ) ECHO This script MozillaVPN for windows IF NOT EXIST src ( ECHO THis doesn't seem to be the root of the MozillaVPN repository. EXIT 1 ) SET SHOW_HELP=F if "%1" NEQ "" ( if "%1" == "-h" SET SHOW_HELP=T if "%1" == "-help" SET SHOW_HELP=T if "%1" NEQ "-p" ( if "%1" NEQ "--prod" ( if "%1" NEQ "-t" ( if "%1" NEQ "--test" ( SET SHOW_HELP=T ) ) ) ) ) if "%SHOW_HELP%" == "T" ( ECHO "Options:" ECHO " -h|--help Help menu" ECHO " -p|--prod Production build" ECHO " -t|--test Test mode" EXIT 0 ) SET PROD_BUILD=F if "%1"== "-p" SET PROD_BUILD=T if "%1"== "--prod" SET PROD_BUILD=T SET TEST_BUILD=F if "%1"== "-t" SET TEST_BUILD=T if "%1"== "--test" SET TEST_BUILD=T ECHO Extract version... FOR /F "tokens=2* delims==" %%A IN ('FINDSTR /IC:":VERSION" version.pri') DO call :SetVersion %%A SET FLAGS=BUILD_ID=%VERSION% if "%PROD_BUILD%" == "T" ( ECHO Production build enabled SET FLAGS=%FLAGS% CONFIG+=production ) else ( ECHO Staging build enabled SET FLAGS=%FLAGS% CONFIG+=inspector ) if "%TEST_BUILD%" == "T" ( ECHO Test build enabled SET FLAGS=%FLAGS% CONFIG+=DUMMY ) else ( SET FLAGS=%FLAGS% CONFIG+=balrog ) ECHO Checking required commands... CALL :CheckCommand python CALL :CheckCommand nmake CALL :CheckCommand cl CALL :CheckCommand qmake ECHO Copying the installer dependencies... CALL :CopyDependency libcrypto-1_1-x64.dll c:\MozillaVPNBuild\bin\libcrypto-1_1-x64.dll CALL :CopyDependency libssl-1_1-x64.dll c:\MozillaVPNBuild\bin\libssl-1_1-x64.dll CALL :CopyDependency libEGL.dll c:\MozillaVPNBuild\bin\libEGL.dll CALL :CopyDependency libGLESv2.dll c:\MozillaVPNBuild\bin\libGLESv2.dll CALL :CopyDependency Microsoft_VC142_CRT_x86.msm "c:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Redist\\MSVC\\14.28.29325\\MergeModules\\Microsoft_VC142_CRT_x86.msm" CALL :CopyDependency Microsoft_VC142_CRT_x64.msm "c:\\Program Files (x86)\\Microsoft Visual Studio\\2019\\Community\\VC\\Redist\\MSVC\\14.28.29325\\MergeModules\\Microsoft_VC142_CRT_x64.msm" ECHO Importing languages... python scripts\importLanguages.py ECHO Creating the project with flags: %FLAGS% qmake -tp vc src/src.pro CONFIG-=debug CONFIG+=release CONFIG-=debug_and_release %FLAGS% IF %ERRORLEVEL% NEQ 0 ( ECHO Failed to configure the project EXIT 1 ) IF NOT EXIST MozillaVPN.vcxproj ( echo The VC project doesn't exist. Why? EXIT 1 ) ECHO Compiling the balrog.dll... CALL balrog\build.cmd IF %ERRORLEVEL% NEQ 0 ( ECHO Failed to clean up the project EXIT 1 ) ECHO Compiling the tunnel.dll... CALL windows\tunnel\build.cmd IF %ERRORLEVEL% NEQ 0 ( ECHO Failed to clean up the project EXIT 1 ) ECHO Cleaning up the project... MSBuild -t:Clean -p:Configuration=Release MozillaVPN.vcxproj IF %ERRORLEVEL% NEQ 0 ( ECHO Failed to clean up the project EXIT 1 ) MSBuild -t:Build -p:Configuration=Release MozillaVPN.vcxproj IF %ERRORLEVEL% NEQ 0 ( ECHO Failed to build the project EXIT 1 ) ECHO Creating the installer... CALL windows\installer\build.cmd IF %ERRORLEVEL% NEQ 0 ( ECHO Failed to clean up the project EXIT 1 ) ECHO All done. EXIT 0 :CheckCommand WHERE %~1 > nul IF %ERRORLEVEL% NEQ 0 ( ECHO Command `%~1` has not been found. EXIT 1 ) goto :eof :CopyDependency IF NOT EXIST %~1 ( COPY /y "%~2" "%~1" > nul IF %ERRORLEVEL% NEQ 0 ( ECHO Failed to copy the dependency `%~1`. EXIT 1 ) ) goto :eof :SetVersion for /f "tokens=1* delims=." %%A IN ("%1") DO call :ComposeVersion %%A goto :EOF :ComposeVersion SET VERSION=%1 SET T=%TIME: =0% SET VERSION=%VERSION%.%date:~-4%%date:~4,2%%date:~7,2%%T:~0,2%%T:~3,2% goto :EOF mozilla-vpn-client-2.2.0/scripts/xcode_patcher.rb000066400000000000000000000500461404202232700220640ustar00rootroot00000000000000# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. require 'xcodeproj' class XCodeprojPatcher attr :project attr :target_main attr :target_extension def run(file, shortVersion, fullVersion, platform, networkExtension, configHash) open_project file open_target_main die 'IOS requires networkExtension mode' if not networkExtension and platform == 'ios' group = @project.main_group.new_group('Configuration') @configFile = group.new_file('xcode.xconfig') setup_target_main shortVersion, fullVersion, platform, networkExtension, configHash setup_target_loginitem shortVersion, fullVersion, configHash if platform == "macos" if networkExtension setup_target_extension shortVersion, fullVersion, platform, configHash setup_target_gobridge else setup_target_wireguardgo setup_target_wireguardtools setup_target_wireguardhelper end setup_target_balrog if platform == 'macos' @project.save end def open_project(file) @project = Xcodeproj::Project.open(file) die 'Failed to open the project file: ' + file if @project.nil? end def open_target_main @target_main = @project.targets.find { |target| target.to_s == 'MozillaVPN' } return @target_main if not @target_main.nil? die 'Unable to open MozillaVPN target' end def setup_target_main(shortVersion, fullVersion, platform, networkExtension, configHash) @target_main.build_configurations.each do |config| config.base_configuration_reference = @configFile config.build_settings['LD_RUNPATH_SEARCH_PATHS'] ||= '"$(inherited) @executable_path/../Frameworks"' config.build_settings['SWIFT_VERSION'] ||= '5.0' config.build_settings['CLANG_ENABLE_MODULES'] ||= 'YES' config.build_settings['SWIFT_OBJC_BRIDGING_HEADER'] ||= 'macos/app/WireGuard-Bridging-Header.h' # Versions and names config.build_settings['MARKETING_VERSION'] ||= shortVersion config.build_settings['CURRENT_PROJECT_VERSION'] ||= fullVersion config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = configHash['APP_ID_MACOS'] if platform == 'macos' config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] = configHash['APP_ID_IOS'] if platform == 'ios' config.build_settings['PRODUCT_NAME'] = 'Mozilla VPN' # other config config.build_settings['INFOPLIST_FILE'] ||= platform + '/app/Info.plist' if platform == 'ios' config.build_settings['CODE_SIGN_ENTITLEMENTS'] ||= 'ios/app/main.entitlements' elsif networkExtension config.build_settings['CODE_SIGN_ENTITLEMENTS'] ||= 'macos/app/networkExtension.entitlements' else config.build_settings['CODE_SIGN_ENTITLEMENTS'] ||= 'macos/app/daemon.entitlements' end config.build_settings['CODE_SIGN_IDENTITY'] ||= 'Apple Development' config.build_settings['ENABLE_BITCODE'] ||= 'NO' if platform == 'ios' config.build_settings['SDKROOT'] = 'iphoneos' if platform == 'ios' groupId = ""; if (platform == 'macos') groupId = configHash['DEVELOPMENT_TEAM'] + "." + configHash['GROUP_ID_MACOS'] else groupId = configHash['GROUP_ID_IOS'] end config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ 'GROUP_ID=\"' + groupId + '\"', "VPN_NE_BUNDLEID=\\\"" + (platform == 'macos' ? configHash['NETEXT_ID_MACOS'] : configHash['NETEXT_ID_IOS']) + "\\\"", ] if config.name == 'Release' config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] ||= '-Onone' end end if networkExtension # WireGuard group group = @project.main_group.new_group('WireGuard') [ 'macos/gobridge/wireguard-go-version.h', '3rdparty/wireguard-apple/WireGuard/Shared/Keychain.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/Data+KeyEncoding.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/IPAddressRange.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/InterfaceConfiguration.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/NETunnelProviderProtocol+Extension.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/TunnelConfiguration.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/TunnelConfiguration+WgQuickConfig.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/Endpoint.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/String+ArrayConversion.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/PeerConfiguration.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/DNSServer.swift', '3rdparty/wireguard-apple/WireGuard/WireGuard/LocalizationHelper.swift', '3rdparty/wireguard-apple/WireGuard/Shared/FileManager+Extension.swift', ].each { |filename| file = group.new_file(filename) @target_main.add_file_references([file]) } # @target_main + swift integration group = @project.main_group.new_group('SwiftIntegration') [ 'src/platforms/ios/ioscontroller.swift', 'src/platforms/ios/ioslogger.swift', ].each { |filename| file = group.new_file(filename) @target_main.add_file_references([file]) } end end def setup_target_extension(shortVersion, fullVersion, platform, configHash) @target_extension = @project.new_target(:app_extension, 'WireGuardNetworkExtension', platform == 'macos' ? :osx : :ios) @target_extension.build_configurations.each do |config| config.base_configuration_reference = @configFile config.build_settings['LD_RUNPATH_SEARCH_PATHS'] ||= '"$(inherited) @executable_path/../Frameworks"' config.build_settings['SWIFT_VERSION'] ||= '5.0' config.build_settings['CLANG_ENABLE_MODULES'] ||= 'YES' config.build_settings['SWIFT_OBJC_BRIDGING_HEADER'] ||= 'macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h' # Versions and names config.build_settings['MARKETING_VERSION'] ||= shortVersion config.build_settings['CURRENT_PROJECT_VERSION'] ||= fullVersion config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] ||= configHash['NETEXT_ID_MACOS'] if platform == 'macos' config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] ||= configHash['NETEXT_ID_IOS'] if platform == 'ios' config.build_settings['PRODUCT_NAME'] = 'WireGuardNetworkExtension' # other configs config.build_settings['INFOPLIST_FILE'] ||= 'macos/networkextension/Info.plist' config.build_settings['CODE_SIGN_ENTITLEMENTS'] ||= platform + '/networkextension/MozillaVPNNetworkExtension.entitlements' config.build_settings['CODE_SIGN_IDENTITY'] = 'Apple Development' if platform == 'ios' config.build_settings['ENABLE_BITCODE'] ||= 'NO' config.build_settings['SDKROOT'] = 'iphoneos' config.build_settings['OTHER_LDFLAGS'] ||= [ "-stdlib=libc++", "-Wl,-rpath,@executable_path/Frameworks", "-framework", "AssetsLibrary", "-framework", "MobileCoreServices", "-lm", "-framework", "UIKit", "-lz", "-framework", "OpenGLES", ] end groupId = ""; if (platform == 'macos') groupId = configHash['DEVELOPMENT_TEAM'] + "." + configHash['GROUP_ID_MACOS'] else groupId = configHash['GROUP_ID_IOS'] end config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ # This is needed to compile the iosglue without Qt. 'NETWORK_EXTENSION=1', 'GROUP_ID=\"' + groupId + '\"', ] if config.name == 'Release' config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] ||= '-Onone' end end group = @project.main_group.new_group('WireGuardExtension') [ '3rdparty/wireguard-apple/WireGuard/WireGuardNetworkExtension/PacketTunnelProvider.swift', '3rdparty/wireguard-apple/WireGuard/WireGuardNetworkExtension/PacketTunnelSettingsGenerator.swift', '3rdparty/wireguard-apple/WireGuard/WireGuardNetworkExtension/DNSResolver.swift', '3rdparty/wireguard-apple/WireGuard/WireGuardNetworkExtension/ErrorNotifier.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Keychain.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/TunnelConfiguration+WgQuickConfig.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/NETunnelProviderProtocol+Extension.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/String+ArrayConversion.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/TunnelConfiguration.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/Data+KeyEncoding.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/IPAddressRange.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/Endpoint.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/DNSServer.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/InterfaceConfiguration.swift', '3rdparty/wireguard-apple/WireGuard/Shared/Model/PeerConfiguration.swift', '3rdparty/wireguard-apple/WireGuard/Shared/FileManager+Extension.swift', ].each { |filename| file = group.new_file(filename) @target_extension.add_file_references([file]) } # @target_extension + swift integration group = @project.main_group.new_group('SwiftIntegration') [ 'src/platforms/ios/iosglue.mm', 'src/platforms/ios/ioslogger.swift', ].each { |filename| file = group.new_file(filename) @target_extension.add_file_references([file]) } frameworks_group = @project.groups.find { |group| group.display_name == 'Frameworks' } frameworks_build_phase = @target_extension.build_phases.find { |build_phase| build_phase.to_s == 'FrameworksBuildPhase' } frameworks_build_phase.clear framework_ref = frameworks_group.new_file('libwg-go.a') frameworks_build_phase.add_file_reference(framework_ref) framework_ref = frameworks_group.new_file('NetworkExtension.framework') frameworks_build_phase.add_file_reference(framework_ref) # This fails: @target_main.add_dependency @target_extension container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy) container_proxy.container_portal = @project.root_object.uuid container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target] container_proxy.remote_global_id_string = @target_extension.uuid container_proxy.remote_info = @target_extension.name dependency = @project.new(Xcodeproj::Project::PBXTargetDependency) dependency.name = @target_extension.name dependency.target = @target_main dependency.target_proxy = container_proxy @target_main.dependencies << dependency copy_appex = @target_main.new_copy_files_build_phase copy_appex.name = 'Copy Network-Extension plugin' copy_appex.symbol_dst_subfolder_spec = :plug_ins appex_file = copy_appex.add_file_reference @target_extension.product_reference appex_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] } end def setup_target_gobridge target_gobridge = legacy_target = @project.new(Xcodeproj::Project::PBXLegacyTarget) target_gobridge.build_working_directory = 'macos/gobridge' target_gobridge.build_tool_path = 'make' target_gobridge.pass_build_settings_in_environment = '1' target_gobridge.build_arguments_string = '$(ACTION)' target_gobridge.name = 'WireGuardGoBridge' target_gobridge.product_name = 'WireGuardGoBridge' @project.targets << target_gobridge @target_extension.add_dependency target_gobridge end def setup_target_balrog target_balrog = legacy_target = @project.new(Xcodeproj::Project::PBXLegacyTarget) target_balrog.build_working_directory = 'balrog' target_balrog.build_tool_path = 'make' target_balrog.pass_build_settings_in_environment = '1' target_balrog.build_arguments_string = '$(ACTION)' target_balrog.name = 'WireGuardBalrog' target_balrog.product_name = 'WireGuardBalrog' @project.targets << target_balrog frameworks_group = @project.groups.find { |group| group.display_name == 'Frameworks' } frameworks_build_phase = @target_main.build_phases.find { |build_phase| build_phase.to_s == 'FrameworksBuildPhase' } framework_ref = frameworks_group.new_file('balrog/balrog.a') frameworks_build_phase.add_file_reference(framework_ref) # This fails: @target_main.add_dependency target_balrog container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy) container_proxy.container_portal = @project.root_object.uuid container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target] container_proxy.remote_global_id_string = target_balrog.uuid container_proxy.remote_info = target_balrog.name dependency = @project.new(Xcodeproj::Project::PBXTargetDependency) dependency.name = target_balrog.name dependency.target = @target_main dependency.target_proxy = container_proxy @target_main.dependencies << dependency end def setup_target_wireguardhelper copy_wireguardHelper = @target_main.new_copy_files_build_phase copy_wireguardHelper.name = 'Copy wireguard helper' copy_wireguardHelper.symbol_dst_subfolder_spec = :wrapper copy_wireguardHelper.dst_path = 'Contents/Resources/utils' group = @project.main_group.new_group('WireGuardHelper') file = group.new_file 'macos/daemon/helper.sh' wireguardHelper_file = copy_wireguardHelper.add_file_reference file wireguardHelper_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] } end def setup_target_wireguardtools target_wireguardtools = legacy_target = @project.new(Xcodeproj::Project::PBXLegacyTarget) target_wireguardtools.build_working_directory = '3rdparty/wireguard-tools/src' target_wireguardtools.build_tool_path = 'make' target_wireguardtools.pass_build_settings_in_environment = '1' target_wireguardtools.build_arguments_string = '$(ACTION)' target_wireguardtools.name = 'WireGuardTools' target_wireguardtools.product_name = 'WireGuardTools' @project.targets << target_wireguardtools # This fails: @target_main.add_dependency target_wireguardtools container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy) container_proxy.container_portal = @project.root_object.uuid container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target] container_proxy.remote_global_id_string = target_wireguardtools.uuid container_proxy.remote_info = target_wireguardtools.name dependency = @project.new(Xcodeproj::Project::PBXTargetDependency) dependency.name = target_wireguardtools.name dependency.target = @target_main dependency.target_proxy = container_proxy @target_main.dependencies << dependency copy_wireguardTools = @target_main.new_copy_files_build_phase copy_wireguardTools.name = 'Copy wireguard-tools' copy_wireguardTools.symbol_dst_subfolder_spec = :wrapper copy_wireguardTools.dst_path = 'Contents/Resources/utils' group = @project.main_group.new_group('WireGuardTools') file = group.new_file '3rdparty/wireguard-tools/src/wg' wireguardTools_file = copy_wireguardTools.add_file_reference file wireguardTools_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] } end def setup_target_wireguardgo target_wireguardgo = legacy_target = @project.new(Xcodeproj::Project::PBXLegacyTarget) target_wireguardgo.build_working_directory = '3rdparty/wireguard-go' target_wireguardgo.build_tool_path = 'make' target_wireguardgo.pass_build_settings_in_environment = '1' target_wireguardgo.build_arguments_string = '$(ACTION)' target_wireguardgo.name = 'WireGuardGo' target_wireguardgo.product_name = 'WireGuardGo' @project.targets << target_wireguardgo # This fails: @target_main.add_dependency target_wireguardgo container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy) container_proxy.container_portal = @project.root_object.uuid container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target] container_proxy.remote_global_id_string = target_wireguardgo.uuid container_proxy.remote_info = target_wireguardgo.name dependency = @project.new(Xcodeproj::Project::PBXTargetDependency) dependency.name = target_wireguardgo.name dependency.target = @target_main dependency.target_proxy = container_proxy @target_main.dependencies << dependency copy_wireguardGo = @target_main.new_copy_files_build_phase copy_wireguardGo.name = 'Copy wireguard-go' copy_wireguardGo.symbol_dst_subfolder_spec = :wrapper copy_wireguardGo.dst_path = 'Contents/Resources/utils' group = @project.main_group.new_group('WireGuardGo') file = group.new_file '3rdparty/wireguard-go/wireguard-go' wireguardGo_file = copy_wireguardGo.add_file_reference file wireguardGo_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] } end def setup_target_loginitem(shortVersion, fullVersion, configHash) @target_loginitem = @project.new_target(:application, 'MozillaVPNLoginItem', :osx) @target_loginitem.build_configurations.each do |config| config.base_configuration_reference = @configFile config.build_settings['LD_RUNPATH_SEARCH_PATHS'] ||= '"$(inherited) @executable_path/../Frameworks"' # Versions and names config.build_settings['MARKETING_VERSION'] ||= shortVersion config.build_settings['CURRENT_PROJECT_VERSION'] ||= fullVersion config.build_settings['PRODUCT_BUNDLE_IDENTIFIER'] ||= configHash['LOGIN_ID_MACOS'] config.build_settings['PRODUCT_NAME'] = 'MozillaVPNLoginItem' # other configs config.build_settings['INFOPLIST_FILE'] ||= 'macos/loginitem/Info.plist' config.build_settings['CODE_SIGN_ENTITLEMENTS'] ||= 'macos/loginitem/MozillaVPNLoginItem.entitlements' config.build_settings['CODE_SIGN_IDENTITY'] = 'Apple Development' config.build_settings['SKIP_INSTALL'] = 'YES' config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= [ 'APP_ID=\"' + configHash['APP_ID_MACOS'] + '\"', ] if config.name == 'Release' config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] ||= '-Onone' end end group = @project.main_group.new_group('LoginItem') [ 'macos/loginitem/main.m', ].each { |filename| file = group.new_file(filename) @target_loginitem.add_file_references([file]) } # This fails: @target_main.add_dependency @target_loginitem container_proxy = @project.new(Xcodeproj::Project::PBXContainerItemProxy) container_proxy.container_portal = @project.root_object.uuid container_proxy.proxy_type = Xcodeproj::Constants::PROXY_TYPES[:native_target] container_proxy.remote_global_id_string = @target_loginitem.uuid container_proxy.remote_info = @target_loginitem.name dependency = @project.new(Xcodeproj::Project::PBXTargetDependency) dependency.name = @target_loginitem.name dependency.target = @target_main dependency.target_proxy = container_proxy @target_main.dependencies << dependency copy_app = @target_main.new_copy_files_build_phase copy_app.name = 'Copy LoginItem' copy_app.symbol_dst_subfolder_spec = :wrapper copy_app.dst_path = 'Contents/Library/LoginItems' app_file = copy_app.add_file_reference @target_loginitem.product_reference app_file.settings = { "ATTRIBUTES" => ['RemoveHeadersOnCopy'] } end def die(msg) print $msg exit 1 end end if ARGV.length < 4 || (ARGV[3] != "ios" && ARGV[3] != "macos") puts "Usage: mozilla-vpn-client-2.2.0/src/ipaddress.cpp000066400000000000000000000110711404202232700205040ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ipaddress.h" #include "leakdetector.h" #include "logger.h" #include namespace { Logger logger(LOG_NETWORKING, "IPAddress"); } static quint32 s_allOnes = static_cast(qPow(2, 32) - 1); // static IPAddress IPAddress::create(const QString& ip) { if (ip.contains("/")) { QPair p = QHostAddress::parseSubnet(ip); Q_ASSERT(p.first.protocol() == QAbstractSocket::IPv4Protocol); if (p.second < 32) { return IPAddress(p.first, p.second); } return IPAddress(p.first); } return IPAddress(QHostAddress(ip)); } IPAddress::IPAddress() { MVPN_COUNT_CTOR(IPAddress); } IPAddress::IPAddress(const IPAddress& other) { MVPN_COUNT_CTOR(IPAddress); *this = other; } IPAddress& IPAddress::operator=(const IPAddress& other) { if (this == &other) return *this; m_address = other.m_address; m_prefixLength = other.m_prefixLength; m_netmask = other.m_netmask; m_hostmask = other.m_hostmask; m_broadcastAddress = other.m_broadcastAddress; return *this; } IPAddress::IPAddress(const QHostAddress& address) : m_address(address), m_prefixLength(32), m_netmask(QHostAddress(s_allOnes)), m_hostmask(QHostAddress((quint32)(0))), m_broadcastAddress(address) { MVPN_COUNT_CTOR(IPAddress); Q_ASSERT(address.protocol() == QAbstractSocket::IPv4Protocol); } IPAddress::IPAddress(const QHostAddress& address, int prefixLength) : m_address(address), m_prefixLength(prefixLength) { MVPN_COUNT_CTOR(IPAddress); Q_ASSERT(address.protocol() == QAbstractSocket::IPv4Protocol); Q_ASSERT(prefixLength >= 0 && prefixLength <= 32); m_netmask = QHostAddress(s_allOnes ^ (s_allOnes >> prefixLength)); m_hostmask = QHostAddress(m_netmask.toIPv4Address() ^ s_allOnes); m_broadcastAddress = QHostAddress(address.toIPv4Address() | m_hostmask.toIPv4Address()); } IPAddress::~IPAddress() { MVPN_COUNT_DTOR(IPAddress); } bool IPAddress::overlaps(const IPAddress& other) const { return other.contains(m_address) || other.contains(m_broadcastAddress) || contains(other.m_address) || contains(other.m_broadcastAddress); } bool IPAddress::contains(const QHostAddress& address) const { return (m_address.toIPv4Address() <= address.toIPv4Address()) && (address.toIPv4Address() <= m_broadcastAddress.toIPv4Address()); } bool IPAddress::operator==(const IPAddress& other) const { return m_address == other.m_address && m_netmask == other.m_netmask; } bool IPAddress::subnetOf(const IPAddress& other) const { return other.m_address.toIPv4Address() <= m_address.toIPv4Address() && other.m_broadcastAddress.toIPv4Address() >= m_broadcastAddress.toIPv4Address(); } QList IPAddress::subnets() const { quint64 start = m_address.toIPv4Address(); quint64 end = quint64(m_broadcastAddress.toIPv4Address()) + 1; quint64 step = ((quint64)m_hostmask.toIPv4Address() + 1) >> 1; QList list; if (m_prefixLength == 32) { list.append(*this); return list; } while (start < end) { int newPrefixLength = m_prefixLength + 1; if (newPrefixLength == 32) { list.append(IPAddress(QHostAddress(start))); } else { list.append(IPAddress(QHostAddress(start), m_prefixLength + 1)); } start += step; } return list; } // static QList IPAddress::excludeAddresses( const QList& sourceList, const QList& excludeList) { QList results = sourceList; for (const IPAddress& exclude : excludeList) { QList newResults; for (const IPAddress& ip : results) { if (ip.overlaps(exclude)) { QList range = ip.excludeAddresses(exclude); newResults.append(range); } else { newResults.append(ip); } } results = newResults; } return results; } QList IPAddress::excludeAddresses(const IPAddress& ip) const { QList sn = subnets(); Q_ASSERT(sn.length() >= 2); QList result; while (sn[0] != ip && sn[1] != ip) { if (ip.subnetOf(sn[0])) { result.append(sn[1]); sn = sn[0].subnets(); } else if (ip.subnetOf(sn[1])) { result.append(sn[0]); sn = sn[1].subnets(); } else { Q_ASSERT(false); } } if (sn[0] == ip) { result.append(sn[1]); } else if (sn[1] == ip) { result.append(sn[0]); } else { Q_ASSERT(false); } return result; } mozilla-vpn-client-2.2.0/src/ipaddress.h000066400000000000000000000032701404202232700201530ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef IPADDRESS_H #define IPADDRESS_H #include class IPAddress final { public: static IPAddress create(const QString& ip); static QList excludeAddresses(const QList& sourceList, const QList& excludeList); IPAddress(); IPAddress(const IPAddress& other); IPAddress& operator=(const IPAddress& other); ~IPAddress(); QString toString() const { return QString("%1/%2").arg(m_address.toString()).arg(m_prefixLength); } const QHostAddress& address() const { return m_address; } int prefixLength() const { return m_prefixLength; } const QHostAddress& netmask() const { return m_netmask; } const QHostAddress& hostmask() const { return m_hostmask; } const QHostAddress& broadcastAddress() const { return m_broadcastAddress; } bool overlaps(const IPAddress& other) const; bool contains(const QHostAddress& address) const; bool operator==(const IPAddress& other) const; bool operator!=(const IPAddress& other) const { return !operator==(other); } bool subnetOf(const IPAddress& other) const; QList subnets() const; QList excludeAddresses(const IPAddress& ip) const; private: IPAddress(const QHostAddress& address); IPAddress(const QHostAddress& address, int prefixLength); private: QHostAddress m_address; int m_prefixLength; QHostAddress m_netmask; QHostAddress m_hostmask; QHostAddress m_broadcastAddress; }; #endif // IPADDRESS_H mozilla-vpn-client-2.2.0/src/ipaddressrange.cpp000066400000000000000000000022621404202232700215230ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ipaddressrange.h" #include "ipaddress.h" #include "leakdetector.h" IPAddressRange::IPAddressRange(const QString& ipAddress, uint32_t range, IPAddressType type) : m_ipAddress(ipAddress), m_range(range), m_type(type) { MVPN_COUNT_CTOR(IPAddressRange); } IPAddressRange::IPAddressRange(const IPAddressRange& other) { MVPN_COUNT_CTOR(IPAddressRange); *this = other; } IPAddressRange& IPAddressRange::operator=(const IPAddressRange& other) { if (this == &other) return *this; m_ipAddress = other.m_ipAddress; m_range = other.m_range; m_type = other.m_type; return *this; } IPAddressRange::~IPAddressRange() { MVPN_COUNT_DTOR(IPAddressRange); } // static QList IPAddressRange::fromIPAddressList( const QList& list) { QList result; for (const IPAddress& ip : list) { result.append( IPAddressRange(ip.address().toString(), ip.prefixLength(), IPv4)); } return result; } mozilla-vpn-client-2.2.0/src/ipaddressrange.h000066400000000000000000000020311404202232700211620ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef IPADDRESSRANGE_H #define IPADDRESSRANGE_H #include #include class IPAddress; class IPAddressRange final { public: enum IPAddressType { IPv4, IPv6, }; static QList fromIPAddressList(const QList& list); IPAddressRange(const QString& ipAddress, uint32_t range, IPAddressType type); IPAddressRange(const IPAddressRange& other); IPAddressRange& operator=(const IPAddressRange& other); ~IPAddressRange(); const QString& ipAddress() const { return m_ipAddress; } uint32_t range() const { return m_range; } IPAddressType type() const { return m_type; } const QString toString() const { return QString("%1/%2").arg(m_ipAddress).arg(m_range); } private: QString m_ipAddress; uint32_t m_range; IPAddressType m_type; }; #endif // IPADDRESSRANGE_H mozilla-vpn-client-2.2.0/src/leakdetector.cpp000066400000000000000000000033041404202232700211740ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "leakdetector.h" #include #include #include #include #ifdef QT_DEBUG static QMutex s_leakDetector; QHash> s_leaks; #endif LeakDetector::LeakDetector() { #ifndef QT_DEBUG qFatal("LeakDetector _must_ be created in debug builds only!"); #endif } LeakDetector::~LeakDetector() { #ifdef QT_DEBUG QTextStream out(stderr); out << "== Mozilla VPN - Leak report ===================" << Qt::endl; bool hasLeaks = false; for (auto i = s_leaks.begin(); i != s_leaks.end(); ++i) { QString className = i.key(); if (i->size() == 0) { continue; } hasLeaks = true; out << className << Qt::endl; for (auto l = i->begin(); l != i->end(); ++l) { out << " - ptr: " << l.key() << " size:" << l.value() << Qt::endl; } } if (!hasLeaks) { out << "No leaks detected." << Qt::endl; } #endif } #ifdef QT_DEBUG void LeakDetector::logCtor(void* ptr, const char* typeName, uint32_t size) { QMutexLocker lock(&s_leakDetector); QString type(typeName); if (!s_leaks.contains(type)) { s_leaks.insert(type, QHash()); } s_leaks[type].insert(ptr, size); } void LeakDetector::logDtor(void* ptr, const char* typeName, uint32_t size) { QMutexLocker lock(&s_leakDetector); QString type(typeName); Q_ASSERT(s_leaks.contains(type)); QHash& leak = s_leaks[type]; Q_ASSERT(leak.contains(ptr)); Q_ASSERT(leak[ptr] == size); leak.remove(ptr); } #endif mozilla-vpn-client-2.2.0/src/leakdetector.h000066400000000000000000000024701404202232700206440ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef LEAKDETECTOR_H #define LEAKDETECTOR_H #include #ifdef QT_DEBUG # define MVPN_COUNT_CTOR(_type) \ do { \ static_assert(std::is_class<_type>(), \ "Token '" #_type "' is not a class type."); \ LeakDetector::logCtor((void*)this, #_type, sizeof(*this)); \ } while (0) # define MVPN_COUNT_DTOR(_type) \ do { \ static_assert(std::is_class<_type>(), \ "Token '" #_type "' is not a class type."); \ LeakDetector::logDtor((void*)this, #_type, sizeof(*this)); \ } while (0) #else # define MVPN_COUNT_CTOR(_type) # define MVPN_COUNT_DTOR(_type) #endif class LeakDetector { public: LeakDetector(); ~LeakDetector(); #ifdef QT_DEBUG static void logCtor(void* ptr, const char* typeName, uint32_t size); static void logDtor(void* ptr, const char* typeName, uint32_t size); #endif }; #endif // LEAKDETECTOR_H mozilla-vpn-client-2.2.0/src/localizer.cpp000066400000000000000000000161611404202232700205170ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "localizer.h" #include "constants.h" #include "leakdetector.h" #include "logger.h" #include "serveri18n.h" #include "settingsholder.h" #include #include #include #include namespace { Logger logger(LOG_MAIN, "Localizer"); Localizer* s_instance = nullptr; struct StaticLanguage { QString m_name; QString m_localizedName; }; // Some languages do not have the right localized/non-localized names in the QT // framework (and some are missing entirely). This static map is the fallback // when this happens. QMap s_languageMap{ {"co", StaticLanguage{"Corsu", ""}}, {"es_AR", StaticLanguage{"Spanish (Argentina)", "Español, Argentina"}}, {"es_MX", StaticLanguage{"Spanish (Mexico)", "Español, México"}}, {"en_GB", StaticLanguage{"English (United Kingdom)", ""}}, {"en_CA", StaticLanguage{"English (Canada)", ""}}, }; } // namespace // static Localizer* Localizer::instance() { Q_ASSERT(s_instance); return s_instance; } Localizer::Localizer() { MVPN_COUNT_CTOR(Localizer); Q_ASSERT(!s_instance); s_instance = this; SettingsHolder* settingsHolder = SettingsHolder::instance(); if (settingsHolder->hasLanguageCode()) { m_code = settingsHolder->languageCode(); } initialize(); } Localizer::~Localizer() { MVPN_COUNT_DTOR(Localizer); Q_ASSERT(s_instance = this); s_instance = nullptr; } void Localizer::initialize() { QString systemCode = QLocale::system().bcp47Name(); // In previous versions, we did not have the support for the system language. // If this is the first time we are here, we need to check if the current // language matches with the system one. SettingsHolder* settingsHolder = SettingsHolder::instance(); if (!settingsHolder->hasSystemLanguageCodeMigrated() || !settingsHolder->systemLanguageCodeMigrated()) { settingsHolder->setSystemLanguageCodeMigrated(true); if (settingsHolder->hasLanguageCode() && settingsHolder->languageCode() == systemCode) { settingsHolder->setPreviousLanguageCode(settingsHolder->languageCode()); settingsHolder->setLanguageCode(""); } } // We always need a previous code. if (!settingsHolder->hasPreviousLanguageCode() || settingsHolder->previousLanguageCode().isEmpty()) { settingsHolder->setPreviousLanguageCode(systemCode); } loadLanguage(m_code); QCoreApplication::installTranslator(&m_translator); QDir dir(":/i18n"); QStringList files = dir.entryList(); for (const QString& file : files) { if (!file.startsWith("mozillavpn_") || !file.endsWith(".qm")) { continue; } QStringList parts = file.split("."); Q_ASSERT(parts.length() == 2); QString code = parts[0].remove(0, 11); Language language{code, languageName(code), localizedLanguageName(code)}; m_languages.append(language); } // Sorting languages. std::sort(m_languages.begin(), m_languages.end(), languageSort); } void Localizer::loadLanguage(const QString& code) { logger.log() << "Loading language:" << code; if (!loadLanguageInternal(code)) { logger.log() << "Loading default language (fallback)"; loadLanguageInternal("en"); } SettingsHolder* settingsHolder = SettingsHolder::instance(); if (code.isEmpty() && settingsHolder->hasLanguageCode()) { QString previousCode = settingsHolder->languageCode(); if (!previousCode.isEmpty()) { settingsHolder->setPreviousLanguageCode(previousCode); emit previousCodeChanged(); } } SettingsHolder::instance()->setLanguageCode(code); m_code = code; emit codeChanged(); } bool Localizer::loadLanguageInternal(const QString& code) { QLocale locale = QLocale(code); if (code.isEmpty()) { // On IOS, for some QT issues (to be investigated) we cannot use // QLocale::system() directly because it would load the 'en' language // instead of the system one. Let's recreate a new QLocale object using the // bcp47 code. locale = QLocale(QLocale::system().bcp47Name()); } QLocale::setDefault(locale); if (!m_translator.load(locale, "mozillavpn", "_", ":/i18n")) { logger.log() << "Loading the locale failed." << "code"; return false; } return true; } // static QString Localizer::languageName(const QString& code) { if (s_languageMap.contains(code)) { QString languageName = s_languageMap[code].m_name; if (!languageName.isEmpty()) { return languageName; } } QLocale locale(code); if (code.isEmpty()) { locale = QLocale::system(); } if (locale.language() == QLocale::C) { return "English (US)"; } QString name = QLocale::languageToString(locale.language()); // Capitalize the string. name.replace(0, 1, locale.toUpper(QString(name[0]))); return name; } // static QString Localizer::localizedLanguageName(const QString& code) { if (s_languageMap.contains(code)) { QString languageName = s_languageMap[code].m_localizedName; if (!languageName.isEmpty()) { return languageName; } } QLocale locale(code); if (code.isEmpty()) { locale = QLocale::system(); } if (locale.language() == QLocale::C) { return "English (US)"; } QString name = locale.nativeLanguageName(); if (name.isEmpty()) { return languageName(code); } // Capitalize the string. name.replace(0, 1, locale.toUpper(QString(name[0]))); return name; } QHash Localizer::roleNames() const { QHash roles; roles[LanguageRole] = "language"; roles[LocalizedLanguageRole] = "localizedLanguage"; roles[CodeRole] = "code"; return roles; } int Localizer::rowCount(const QModelIndex&) const { return m_languages.count(); } QVariant Localizer::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } switch (role) { case LanguageRole: return QVariant(m_languages.at(index.row()).m_name); case LocalizedLanguageRole: return QVariant(m_languages.at(index.row()).m_localizedName); case CodeRole: return QVariant(m_languages.at(index.row()).m_code); default: return QVariant(); } } QStringList Localizer::languages() const { QStringList languages; for (const Language& language : m_languages) { languages.append(language.m_code); } return languages; } bool Localizer::languageSort(const Localizer::Language& a, const Localizer::Language& b) { return a.m_localizedName < b.m_localizedName; } QString Localizer::previousCode() const { return SettingsHolder::instance()->previousLanguageCode(); } QString Localizer::translateServerCountry(const QString& countryCode, const QString& countryName) { return ServerI18N::translateCountryName(countryCode, countryName); } QString Localizer::translateServerCity(const QString& countryCode, const QString& cityName) { return ServerI18N::translateCityName(countryCode, cityName); } mozilla-vpn-client-2.2.0/src/localizer.h000066400000000000000000000041451404202232700201630ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef LOCALIZER_H #define LOCALIZER_H #include #include class SettingsHolder; class Localizer final : public QAbstractListModel { Q_OBJECT Q_DISABLE_COPY_MOVE(Localizer) Q_PROPERTY(QString code READ code WRITE setCode NOTIFY codeChanged) Q_PROPERTY(QString previousCode READ previousCode NOTIFY previousCodeChanged) Q_PROPERTY(bool hasLanguages READ hasLanguages CONSTANT) struct Language { QString m_code; QString m_name; QString m_localizedName; }; public: enum ServerCountryRoles { LanguageRole = Qt::UserRole + 1, LocalizedLanguageRole, CodeRole, }; static Localizer* instance(); Localizer(); ~Localizer(); void initialize(); void loadLanguage(const QString& code); bool hasLanguages() const { return m_languages.length() > 1; } const QString& code() const { return m_code; } void setCode(const QString& code) { loadLanguage(code); } QString previousCode() const; QStringList languages() const; // QAbstractListModel methods QHash roleNames() const override; int rowCount(const QModelIndex&) const override; QVariant data(const QModelIndex& index, int role) const override; // For QML Q_INVOKABLE QString translateServerCountry(const QString& countryCode, const QString& countryName); Q_INVOKABLE QString translateServerCity(const QString& countryCode, const QString& cityName); signals: void codeChanged(); void previousCodeChanged(); private: static QString languageName(const QString& code); static QString localizedLanguageName(const QString& code); static bool languageSort(const Language& a, const Language& b); bool loadLanguageInternal(const QString& code); private: QTranslator m_translator; QString m_code; QList m_languages; }; #endif // LOCALIZER_H mozilla-vpn-client-2.2.0/src/localsocketcontroller.cpp000066400000000000000000000205541404202232700231430ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "localsocketcontroller.h" #include "errorhandler.h" #include "ipaddressrange.h" #include "leakdetector.h" #include "logger.h" #include "models/device.h" #include "models/keys.h" #include "models/server.h" #include "mozillavpn.h" #include "settingsholder.h" #include #include #include #include #include #include #include namespace { Logger logger(LOG_CONTROLLER, "LocalSocketController"); } LocalSocketController::LocalSocketController() { MVPN_COUNT_CTOR(LocalSocketController); m_socket = new QLocalSocket(this); connect(m_socket, &QLocalSocket::connected, this, &LocalSocketController::daemonConnected); connect(m_socket, &QLocalSocket::disconnected, this, &LocalSocketController::disconnected); connect(m_socket, &QLocalSocket::errorOccurred, this, &LocalSocketController::errorOccurred); connect(m_socket, &QLocalSocket::readyRead, this, &LocalSocketController::readData); } LocalSocketController::~LocalSocketController() { MVPN_COUNT_DTOR(LocalSocketController); } void LocalSocketController::errorOccurred( QLocalSocket::LocalSocketError error) { logger.log() << "Error occurred:" << error; if (m_state == eInitializing) { emit initialized(false, false, QDateTime()); } m_state = eDisconnected; MozillaVPN::instance()->errorHandle(ErrorHandler::ControllerError); emit disconnected(); } void LocalSocketController::initialize(const Device* device, const Keys* keys) { logger.log() << "Initializing"; Q_UNUSED(device); Q_UNUSED(keys); Q_ASSERT(m_state == eUnknown); m_state = eInitializing; #ifdef MVPN_WINDOWS QString path = "\\\\.\\pipe\\mozillavpn"; #else QString path = "/var/run/mozillavpn/daemon.socket"; if (!QFileInfo::exists(path)) { path = "/tmp/mozillavpn.socket"; } #endif logger.log() << "Connecting to:" << path; m_socket->connectToServer(path); } void LocalSocketController::daemonConnected() { logger.log() << "Daemon connected"; Q_ASSERT(m_state == eInitializing); checkStatus(); } void LocalSocketController::activate( const Server& server, const Device* device, const Keys* keys, const QList& allowedIPAddressRanges, const QList& vpnDisabledApps, Reason reason) { Q_UNUSED(vpnDisabledApps); Q_UNUSED(reason); if (m_state != eReady) { emit disconnected(); return; } QJsonObject json; json.insert("type", "activate"); json.insert("privateKey", QJsonValue(keys->privateKey())); json.insert("deviceIpv4Address", QJsonValue(device->ipv4Address())); json.insert("deviceIpv6Address", QJsonValue(device->ipv6Address())); json.insert("serverIpv4Gateway", QJsonValue(server.ipv4Gateway())); json.insert("serverIpv6Gateway", QJsonValue(server.ipv6Gateway())); json.insert("serverPublicKey", QJsonValue(server.publicKey())); json.insert("serverIpv4AddrIn", QJsonValue(server.ipv4AddrIn())); json.insert("serverIpv6AddrIn", QJsonValue(server.ipv6AddrIn())); json.insert("serverPort", QJsonValue((double)server.choosePort())); json.insert("ipv6Enabled", QJsonValue(SettingsHolder::instance()->ipv6Enabled())); QJsonArray allowedIPAddesses; for (const IPAddressRange& i : allowedIPAddressRanges) { QJsonObject range; range.insert("address", QJsonValue(i.ipAddress())); range.insert("range", QJsonValue((double)i.range())); range.insert("isIpv6", QJsonValue(i.type() == IPAddressRange::IPv6)); allowedIPAddesses.append(range); }; json.insert("allowedIPAddressRanges", allowedIPAddesses); write(json); } void LocalSocketController::deactivate(Reason reason) { logger.log() << "Deactivating"; if (m_state != eReady) { emit disconnected(); return; } if (reason == ReasonSwitching) { logger.log() << "No disconnect for quick server switching"; emit disconnected(); return; } QJsonObject json; json.insert("type", "deactivate"); write(json); } void LocalSocketController::checkStatus() { logger.log() << "Check status"; if (m_state == eReady || m_state == eInitializing) { Q_ASSERT(m_socket); QJsonObject json; json.insert("type", "status"); write(json); } } void LocalSocketController::getBackendLogs( std::function&& a_callback) { logger.log() << "Backend logs"; if (m_logCallback) { m_logCallback(""); m_logCallback = nullptr; } if (m_state != eReady) { std::function callback = a_callback; callback(""); return; } m_logCallback = std::move(a_callback); QJsonObject json; json.insert("type", "logs"); write(json); } void LocalSocketController::cleanupBackendLogs() { logger.log() << "Cleanup logs"; if (m_logCallback) { m_logCallback(""); m_logCallback = nullptr; } if (m_state != eReady) { return; } QJsonObject json; json.insert("type", "cleanlogs"); write(json); } void LocalSocketController::readData() { logger.log() << "Reading"; Q_ASSERT(m_socket); Q_ASSERT(m_state == eInitializing || m_state == eReady); QByteArray input = m_socket->readAll(); m_buffer.append(input); while (true) { int pos = m_buffer.indexOf("\n"); if (pos == -1) { break; } QByteArray line = m_buffer.left(pos); m_buffer.remove(0, pos + 1); QByteArray command(line); command = command.trimmed(); if (command.isEmpty()) { continue; } parseCommand(command); } } void LocalSocketController::parseCommand(const QByteArray& command) { logger.log() << "Parse command:" << command.left(20); QJsonDocument json = QJsonDocument::fromJson(command); if (!json.isObject()) { logger.log() << "Invalid JSON - object expected"; return; } QJsonObject obj = json.object(); QJsonValue typeValue = obj.value("type"); if (!typeValue.isString()) { logger.log() << "Invalid JSON - no type"; return; } QString type = typeValue.toString(); if (m_state == eInitializing && type == "status") { m_state = eReady; QJsonValue connected = obj.value("connected"); if (!connected.isBool()) { logger.log() << "Invalid JSON for status - connected expected"; return; } QDateTime datetime; if (connected.toBool()) { QJsonValue date = obj.value("date"); if (!date.isString()) { logger.log() << "Invalid JSON for status - date expected"; return; } datetime = QDateTime::fromString(date.toString()); if (!datetime.isValid()) { logger.log() << "Invalid JSON for status - date is invalid"; return; } } emit initialized(true, connected.toBool(), datetime); return; } if (m_state != eReady) { logger.log() << "Unexpected command"; return; } if (type == "status") { QJsonValue serverIpv4Gateway = obj.value("serverIpv4Gateway"); if (!serverIpv4Gateway.isString()) { logger.log() << "Unexpected serverIpv4Gateway value"; return; } QJsonValue txBytes = obj.value("txBytes"); if (!txBytes.isDouble()) { logger.log() << "Unexpected txBytes value"; return; } QJsonValue rxBytes = obj.value("rxBytes"); if (!rxBytes.isDouble()) { logger.log() << "Unexpected rxBytes value"; return; } emit statusUpdated(serverIpv4Gateway.toString(), txBytes.toDouble(), rxBytes.toDouble()); return; } if (type == "disconnected") { emit disconnected(); return; } if (type == "connected") { emit connected(); return; } if (type == "backendFailure") { MozillaVPN::instance()->errorHandle(ErrorHandler::ControllerError); return; } if (type == "logs") { // We don't care if we are not waiting for logs. if (!m_logCallback) { return; } QJsonValue logs = obj.value("logs"); m_logCallback(logs.isString() ? logs.toString().replace("|", "\n") : QString()); m_logCallback = nullptr; return; } logger.log() << "Invalid command received:" << command; } void LocalSocketController::write(const QJsonObject& json) { Q_ASSERT(m_socket); m_socket->write(QJsonDocument(json).toJson(QJsonDocument::Compact)); m_socket->write("\n"); } mozilla-vpn-client-2.2.0/src/localsocketcontroller.h000066400000000000000000000027501404202232700226060ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef LOCALSOCKETCONTROLLER_H #define LOCALSOCKETCONTROLLER_H #include "controllerimpl.h" #include #include class QJsonObject; class LocalSocketController final : public ControllerImpl { Q_DISABLE_COPY_MOVE(LocalSocketController) public: LocalSocketController(); ~LocalSocketController(); void initialize(const Device* device, const Keys* keys) override; void activate(const Server& server, const Device* device, const Keys* keys, const QList& allowedIPAddressRanges, const QList& vpnDisabledApps, Reason reason) override; void deactivate(Reason reason) override; void checkStatus() override; void getBackendLogs(std::function&& callback) override; void cleanupBackendLogs() override; private: void daemonConnected(); void errorOccurred(QLocalSocket::LocalSocketError socketError); void readData(); void parseCommand(const QByteArray& command); void write(const QJsonObject& json); private: enum { eUnknown, eInitializing, eReady, eDisconnected, } m_state = eUnknown; QLocalSocket* m_socket = nullptr; QByteArray m_buffer; std::function m_logCallback = nullptr; }; #endif // LOCALSOCKETCONTROLLER_H mozilla-vpn-client-2.2.0/src/logger.cpp000066400000000000000000000025411404202232700200070ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "logger.h" #include "loghandler.h" Logger::Logger(const QString& module, const QString& className) : Logger(QStringList({module}), className) {} Logger::Logger(const QStringList& modules, const QString& className) : m_modules(modules), m_className(className) {} Logger::Log Logger::log() { return Log(this); } Logger::Log::Log(Logger* logger) : m_logger(logger), m_data(new Data()) {} Logger::Log::~Log() { LogHandler::messageHandler(m_logger->modules(), m_logger->className(), m_data->m_buffer.trimmed()); delete m_data; } #define CREATE_LOG_OP_REF(x) \ Logger::Log& Logger::Log::operator<<(x t) { \ m_data->m_ts << t << ' '; \ return *this; \ } CREATE_LOG_OP_REF(uint64_t); CREATE_LOG_OP_REF(const char*); CREATE_LOG_OP_REF(const QString&); CREATE_LOG_OP_REF(const QByteArray&); #undef CREATE_LOG_OP_REF Logger::Log& Logger::Log::operator<<(const QStringList& t) { m_data->m_ts << '[' << t.join(",") << ']' << ' '; return *this; } Logger::Log& Logger::Log::operator<<(QTextStreamFunction t) { m_data->m_ts << t; return *this; } mozilla-vpn-client-2.2.0/src/logger.h000066400000000000000000000035621404202232700174600ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef LOGGER_H #define LOGGER_H #include #include constexpr const char* LOG_CAPTIVEPORTAL = "captiveportal"; constexpr const char* LOG_CONTROLLER = "controller"; constexpr const char* LOG_MAIN = "main"; constexpr const char* LOG_MODEL = "model"; constexpr const char* LOG_NETWORKING = "networking"; constexpr const char* LOG_INSPECTOR = "inspector"; #ifdef MVPN_IOS constexpr const char* LOG_IAP = "iap"; #endif #if defined(MVPN_LINUX) || defined(MVPN_ANDROID) constexpr const char* LOG_LINUX = "linux"; #endif #ifdef MVPN_WINDOWS constexpr const char* LOG_WINDOWS = "windows"; #endif #if __APPLE__ || defined(MVPN_WASM) constexpr const char* LOG_MACOS = "macos"; constexpr const char* LOG_IOS = "ios"; #endif #if defined(MVPN_ANDROID) || defined(UNIT_TEST) constexpr const char* LOG_ANDROID = "android"; #endif class Logger { public: Logger(const QString& module, const QString& className); Logger(const QStringList& modules, const QString& className); const QStringList& modules() const { return m_modules; } const QString& className() const { return m_className; } class Log { public: Log(Logger* logger); ~Log(); Log& operator<<(uint64_t t); Log& operator<<(const char* t); Log& operator<<(const QString& t); Log& operator<<(const QStringList& t); Log& operator<<(const QByteArray& t); Log& operator<<(QTextStreamFunction t); private: Logger* m_logger; struct Data { Data() : m_ts(&m_buffer, QIODevice::WriteOnly) {} QString m_buffer; QTextStream m_ts; }; Data* m_data; }; Log log(); private: QStringList m_modules; QString m_className; }; #endif // LOGGER_H mozilla-vpn-client-2.2.0/src/loghandler.cpp000066400000000000000000000145451404202232700206560ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "loghandler.h" #include "logger.h" #include #include #include #include #include #include #include #include #include #include #ifdef MVPN_ANDROID # include #endif constexpr qint64 LOG_MAX_FILE_SIZE = 204800; constexpr const char* LOG_FILENAME = "mozillavpn.txt"; namespace { QMutex s_mutex; LogHandler* s_instance = nullptr; } // namespace // static LogHandler* LogHandler::instance() { QMutexLocker lock(&s_mutex); return maybeCreate(lock); } // static void LogHandler::messageQTHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { QMutexLocker lock(&s_mutex); maybeCreate(lock)->addLog( Log(type, context.file, context.function, context.line, message), lock); } // static void LogHandler::messageHandler(const QStringList& modules, const QString& className, const QString& message) { QMutexLocker lock(&s_mutex); maybeCreate(lock)->addLog(Log(modules, className, message), lock); } // static LogHandler* LogHandler::maybeCreate(const QMutexLocker& proofOfLock) { if (!s_instance) { QStringList modules; QProcessEnvironment pe = QProcessEnvironment::systemEnvironment(); if (pe.contains("MOZVPN_LOG")) { QStringList parts = pe.value("MOZVPN_LOG").split(","); for (const QString& part : parts) { modules.append(part.trimmed()); } } s_instance = new LogHandler(modules, proofOfLock); } return s_instance; } // static void LogHandler::prettyOutput(QTextStream& out, const LogHandler::Log& log) { out << "[" << log.m_dateTime.toString("dd.MM.yyyy hh:mm:ss.zzz") << "] "; if (log.m_fromQT) { switch (log.m_type) { case QtDebugMsg: out << "Debug: "; break; case QtInfoMsg: out << "Info: "; break; case QtWarningMsg: out << "Warning: "; break; case QtCriticalMsg: out << "Critical: "; break; case QtFatalMsg: out << "Fatal: "; break; default: out << "?!?: "; break; } out << log.m_message; if (!log.m_file.isEmpty() || !log.m_function.isEmpty()) { out << " ("; if (!log.m_file.isEmpty()) { int pos = log.m_file.lastIndexOf("/"); out << log.m_file.right(log.m_file.length() - pos - 1); if (log.m_line >= 0) { out << ":" << log.m_line; } if (!log.m_function.isEmpty()) { out << ", "; } } if (!log.m_function.isEmpty()) { out << log.m_function; } out << ")"; } } else { out << "(" << log.m_modules.join("|") << " - " << log.m_className << ") " << log.m_message; } out << Qt::endl; } LogHandler::LogHandler(const QStringList& modules, const QMutexLocker& proofOfLock) : m_modules(modules) { Q_UNUSED(proofOfLock); QString location = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); if (!location.isEmpty()) { QDir appDataLocation(location); if (!QFileInfo::exists(location)) { QDir tmp(location); tmp.cdUp(); if (tmp.exists()) { tmp.mkdir(appDataLocation.dirName()); } } if (QFileInfo::exists(location)) { m_logFileName = appDataLocation.filePath(LOG_FILENAME); openLogFile(proofOfLock); } } } void LogHandler::addLog(const Log& log, const QMutexLocker& proofOfLock) { if (!matchModule(log, proofOfLock)) { return; } if (m_output) { prettyOutput(*m_output, log); } #if defined(MVPN_ANDROID) || defined(MVPN_INSPECTOR) QByteArray buffer; { QTextStream out(&buffer); prettyOutput(out, log); } # if defined(MVPN_INSPECTOR) emit logEntryAdded(buffer); # endif #endif // defined(MVPN_ANDROID) || defined(MVPN_INSPECTOR) #if defined(MVPN_ANDROID) const char* str = buffer.constData(); if (str) { __android_log_write(ANDROID_LOG_DEBUG, "mozillavpn", str); } #elif defined(QT_DEBUG) || defined(MVPN_WASM) QTextStream out(stderr); prettyOutput(out, log); #endif } bool LogHandler::matchModule(const Log& log, const QMutexLocker& proofOfLock) const { Q_UNUSED(proofOfLock); // Let's include QT logs always. if (log.m_fromQT) { return true; } // If no modules has been specified, let's include all. if (m_modules.isEmpty()) { return true; } for (const QString& module : log.m_modules) { if (m_modules.contains(module)) { return true; } } return false; } // static void LogHandler::writeLogs(QTextStream& out) { QMutexLocker lock(&s_mutex); if (!s_instance || s_instance->m_logFileName.isEmpty()) { return; } s_instance->closeLogFile(lock); { QFile file(s_instance->m_logFileName); if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { return; } out << file.readAll(); } s_instance->openLogFile(lock); } // static void LogHandler::cleanupLogs() { QMutexLocker lock(&s_mutex); if (!s_instance || s_instance->m_logFileName.isEmpty()) { return; } s_instance->closeLogFile(lock); { QFile file(s_instance->m_logFileName); file.remove(); } s_instance->openLogFile(lock); } void LogHandler::openLogFile(const QMutexLocker& proofOfLock) { Q_UNUSED(proofOfLock); Q_ASSERT(!m_logFile); Q_ASSERT(!m_output); m_logFile = new QFile(m_logFileName); if (m_logFile->size() > LOG_MAX_FILE_SIZE) { m_logFile->remove(); } if (!m_logFile->open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) { delete m_logFile; m_logFile = nullptr; return; } m_output = new QTextStream(m_logFile); addLog(Log(QStringList{LOG_MAIN}, "LogHandler", QString("Log file: %1").arg(m_logFileName)), proofOfLock); } void LogHandler::closeLogFile(const QMutexLocker& proofOfLock) { Q_UNUSED(proofOfLock); if (m_logFile) { delete m_output; m_output = nullptr; delete m_logFile; m_logFile = nullptr; } } mozilla-vpn-client-2.2.0/src/loghandler.h000066400000000000000000000045561404202232700203240ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef LOGHANDLER_H #define LOGHANDLER_H #include #include #include class QFile; class QMutexLocker; class QTextStream; class LogHandler final : public QObject { Q_OBJECT public: struct Log { Log() = default; Log(const QStringList& modules, const QString& className, const QString& message) : m_dateTime(QDateTime::currentDateTime()), m_modules(modules), m_className(className), m_message(message), m_fromQT(false) {} Log(QtMsgType type, const QString& file, const QString& function, uint32_t line, const QString& message) : m_dateTime(QDateTime::currentDateTime()), m_file(file), m_function(function), m_message(message), m_type(type), m_line(line), m_fromQT(true) {} QDateTime m_dateTime; QString m_file; QString m_function; QStringList m_modules; QString m_className; QString m_message; QtMsgType m_type = QtMsgType::QtDebugMsg; int32_t m_line = -1; bool m_fromQT = false; }; static LogHandler* instance(); static void messageQTHandler(QtMsgType type, const QMessageLogContext& context, const QString& message); static void messageHandler(const QStringList& modules, const QString& className, const QString& message); static void prettyOutput(QTextStream& out, const LogHandler::Log& log); static void writeLogs(QTextStream& out); static void cleanupLogs(); signals: void logEntryAdded(const QByteArray& log); private: LogHandler(const QStringList& modules, const QMutexLocker& proofOfLock); static LogHandler* maybeCreate(const QMutexLocker& proofOfLock); void addLog(const Log& log, const QMutexLocker& proofOfLock); bool matchModule(const Log& log, const QMutexLocker& proofOfLock) const; void openLogFile(const QMutexLocker& proofOfLock); void closeLogFile(const QMutexLocker& proofOfLock); const QStringList m_modules; QString m_logFileName; QFile* m_logFile = nullptr; QTextStream* m_output = nullptr; }; #endif // LOGHANDLER_H mozilla-vpn-client-2.2.0/src/logo_beta.qrc000066400000000000000000000001561404202232700204660ustar00rootroot00000000000000 ui/resources/logo-dock-beta.png mozilla-vpn-client-2.2.0/src/logo_prod.qrc000066400000000000000000000001511404202232700205120ustar00rootroot00000000000000 ui/resources/logo-dock.png mozilla-vpn-client-2.2.0/src/logoutobserver.cpp000066400000000000000000000015041404202232700216070ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "logoutobserver.h" #include "leakdetector.h" #include "mozillavpn.h" LogoutObserver::LogoutObserver(QObject* parent) : QObject(parent) { MVPN_COUNT_CTOR(LogoutObserver); MozillaVPN* vpn = MozillaVPN::instance(); Q_ASSERT(vpn->userAuthenticated()); connect(vpn, &MozillaVPN::userAuthenticationChanged, this, &LogoutObserver::userAuthenticationChanged); } LogoutObserver::~LogoutObserver() { MVPN_COUNT_DTOR(LogoutObserver); } void LogoutObserver::userAuthenticationChanged() { MozillaVPN* vpn = MozillaVPN::instance(); if (!vpn->userAuthenticated()) { emit ready(); deleteLater(); } } mozilla-vpn-client-2.2.0/src/logoutobserver.h000066400000000000000000000010571404202232700212570ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef LOGOUTOBSERVER_H #define LOGOUTOBSERVER_H #include class LogoutObserver final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(LogoutObserver) public: explicit LogoutObserver(QObject* parent); ~LogoutObserver(); signals: void ready(); private slots: void userAuthenticationChanged(); }; #endif // LOGOUTOBSERVER_H mozilla-vpn-client-2.2.0/src/main.cpp000066400000000000000000000006651404202232700174610ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "commandlineparser.h" #include "leakdetector.h" int main(int argc, char* argv[]) { #ifdef QT_DEBUG LeakDetector leakDetector; Q_UNUSED(leakDetector); #endif CommandLineParser clp; return clp.parse(argc, argv); } mozilla-vpn-client-2.2.0/src/models/000077500000000000000000000000001404202232700173055ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/models/device.cpp000066400000000000000000000050761404202232700212600ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "device.h" #include "keys.h" #include "leakdetector.h" #include #include #include #ifdef QT_DEBUG # include #endif #ifdef MVPN_IOS # include "platforms/ios/iosutils.h" #elif MVPN_MACOS # include "platforms/macos/macosutils.h" #elif MVPN_ANDROID # include "platforms/android/androidutils.h" #endif // static QString Device::currentDeviceName() { QString deviceName = #ifdef MVPN_IOS IOSUtils::computerName(); #elif MVPN_MACOS // MacOS has a funny way to rename the hostname based on the network // status. MacOSUtils::computerName(); #elif MVPN_ANDROID AndroidUtils::GetDeviceName(); #elif MVPN_WASM "WASM"; #else QSysInfo::machineHostName() + " " + QSysInfo::productType() + " " + QSysInfo::productVersion(); #endif return deviceName; } Device::Device() { MVPN_COUNT_CTOR(Device); } Device::Device(const Device& other) { MVPN_COUNT_CTOR(Device); *this = other; } Device& Device::operator=(const Device& other) { if (this == &other) return *this; m_deviceName = other.m_deviceName; m_createdAt = other.m_createdAt; m_publicKey = other.m_publicKey; m_ipv4Address = other.m_ipv4Address; m_ipv6Address = other.m_ipv6Address; return *this; } Device::~Device() { MVPN_COUNT_DTOR(Device); } bool Device::fromJson(const QJsonValue& json) { if (!json.isObject()) { return false; } QJsonObject obj = json.toObject(); QJsonValue name = obj.value("name"); if (!name.isString()) { return false; } QJsonValue pubKey = obj.value("pubkey"); if (!pubKey.isString()) { return false; } QJsonValue createdAt = obj.value("created_at"); if (!createdAt.isString()) { return false; } QDateTime date = QDateTime::fromString(createdAt.toString(), Qt::ISODate); if (!date.isValid()) { return false; } QJsonValue ipv4Address = obj.value("ipv4_address"); if (!ipv4Address.isString()) { return false; } QJsonValue ipv6Address = obj.value("ipv6_address"); if (!ipv6Address.isString()) { return false; } m_deviceName = name.toString(); m_createdAt = date; m_publicKey = pubKey.toString(); m_ipv4Address = ipv4Address.toString(); m_ipv6Address = ipv6Address.toString(); return true; } bool Device::isCurrentDevice(const Keys* keys) const { Q_ASSERT(keys); return m_publicKey == keys->publicKey(); } mozilla-vpn-client-2.2.0/src/models/device.h000066400000000000000000000021651404202232700207210ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef DEVICE_H #define DEVICE_H #include #include class Keys; class QJsonValue; class Device final { public: Device(); Device(const Device& other); Device& operator=(const Device& other); ~Device(); static QString currentDeviceName(); [[nodiscard]] bool fromJson(const QJsonValue& json); const QString& name() const { return m_deviceName; } const QDateTime& createdAt() const { return m_createdAt; } bool isDevice(const QString& deviceName) const { return m_deviceName == deviceName; } const QString& publicKey() const { return m_publicKey; } const QString& ipv4Address() const { return m_ipv4Address; } const QString& ipv6Address() const { return m_ipv6Address; } bool isCurrentDevice(const Keys* keys) const; private: QString m_deviceName; QDateTime m_createdAt; QString m_publicKey; QString m_ipv4Address; QString m_ipv6Address; }; #endif // DEVICE_H mozilla-vpn-client-2.2.0/src/models/devicemodel.cpp000066400000000000000000000107021404202232700222710ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "devicemodel.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "settingsholder.h" #include #include #include #include #include namespace { Logger logger(LOG_MODEL, "DeviceModel"); } DeviceModel::DeviceModel() { MVPN_COUNT_CTOR(DeviceModel); } DeviceModel::~DeviceModel() { MVPN_COUNT_DTOR(DeviceModel); } bool DeviceModel::fromJson(const Keys* keys, const QByteArray& s) { logger.log() << "DeviceModel from json"; if (!s.isEmpty() && m_rawJson == s) { logger.log() << "Nothing has changed"; return true; } if (!fromJsonInternal(keys, s)) { return false; } m_rawJson = s; return true; } bool DeviceModel::fromSettings(const Keys* keys) { SettingsHolder* settingsHolder = SettingsHolder::instance(); Q_ASSERT(settingsHolder); logger.log() << "Reading the device list from settings"; if (!settingsHolder->hasDevices()) { return false; } const QByteArray& json = settingsHolder->devices(); if (!fromJsonInternal(keys, json)) { return false; } m_rawJson = json; return true; } namespace { bool sortCallback(const Device& a, const Device& b, const Keys* keys) { if (a.isCurrentDevice(keys)) { return true; } if (b.isCurrentDevice(keys)) { return false; } return a.createdAt() > b.createdAt(); } } // anonymous namespace bool DeviceModel::fromJsonInternal(const Keys* keys, const QByteArray& json) { beginResetModel(); m_rawJson = ""; m_devices.clear(); QJsonDocument doc = QJsonDocument::fromJson(json); if (!doc.isObject()) { return false; } QJsonObject obj = doc.object(); if (!obj.contains("devices")) { return false; } QJsonValue devices = obj.value("devices"); if (!devices.isArray()) { return false; } QJsonArray devicesArray = devices.toArray(); for (QJsonValue deviceValue : devicesArray) { Device device; if (!device.fromJson(deviceValue)) { return false; } m_devices.append(device); } std::sort(m_devices.begin(), m_devices.end(), std::bind(sortCallback, std::placeholders::_1, std::placeholders::_2, keys)); endResetModel(); emit changed(); return true; } void DeviceModel::writeSettings() { SettingsHolder::instance()->setDevices(m_rawJson); } QHash DeviceModel::roleNames() const { QHash roles; roles[NameRole] = "name"; roles[CurrentOneRole] = "currentOne"; roles[CreatedAtRole] = "createdAt"; return roles; } int DeviceModel::rowCount(const QModelIndex&) const { return m_devices.count(); } QVariant DeviceModel::data(const QModelIndex& index, int role) const { if (!index.isValid()) { return QVariant(); } switch (role) { case NameRole: return QVariant(m_devices.at(index.row()).name()); case CurrentOneRole: return QVariant(m_devices.at(index.row()) .isCurrentDevice(MozillaVPN::instance()->keys())); case CreatedAtRole: return QVariant(m_devices.at(index.row()).createdAt()); default: return QVariant(); } } bool DeviceModel::hasDevice(const QString& deviceName) const { for (const Device& device : m_devices) { if (device.isDevice(deviceName)) { return true; } } return false; } const Device* DeviceModel::device(const QString& deviceName) const { for (const Device& device : m_devices) { if (device.isDevice(deviceName)) { return &device; } } return nullptr; } void DeviceModel::removeDevice(const QString& deviceName) { for (int i = 0; i < m_devices.length(); ++i) { if (m_devices.at(i).isDevice(deviceName)) { removeRow(i); emit changed(); return; } } } bool DeviceModel::removeRows(int row, int count, const QModelIndex& parent) { Q_ASSERT(count == 1); Q_UNUSED(parent); beginRemoveRows(parent, row, row); m_devices.removeAt(row); endRemoveRows(); return true; } const Device* DeviceModel::currentDevice(const Keys* keys) const { for (const Device& device : m_devices) { if (device.isCurrentDevice(keys)) { return &device; } } return nullptr; } bool DeviceModel::hasCurrentDevice(const Keys* keys) const { return currentDevice(keys) != nullptr; } mozilla-vpn-client-2.2.0/src/models/devicemodel.h000066400000000000000000000033661404202232700217460ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef DEVICEMODEL_H #define DEVICEMODEL_H #include #include #include "device.h" class Keys; class DeviceModel final : public QAbstractListModel { Q_OBJECT Q_DISABLE_COPY_MOVE(DeviceModel) Q_PROPERTY(int activeDevices READ activeDevices NOTIFY changed) public: DeviceModel(); ~DeviceModel(); enum ServerCountryRoles { NameRole = Qt::UserRole + 1, CurrentOneRole, CreatedAtRole, }; [[nodiscard]] bool fromJson(const Keys* keys, const QByteArray& s); [[nodiscard]] bool fromSettings(const Keys* keys); bool initialized() const { return !m_rawJson.isEmpty(); } void writeSettings(); bool hasDevice(const QString& deviceName) const; void removeDevice(const QString& deviceName); const Device* device(const QString& deviceName) const; int activeDevices() const { return m_devices.count(); } const QList& devices() const { return m_devices; } const Device* currentDevice(const Keys* keys) const; bool hasCurrentDevice(const Keys* keys) const; // QAbstractListModel methods QHash roleNames() const override; int rowCount(const QModelIndex&) const override; QVariant data(const QModelIndex& index, int role) const override; signals: void changed(); private: [[nodiscard]] bool fromJsonInternal(const Keys* keys, const QByteArray& json); bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override; private: QByteArray m_rawJson; QList m_devices; }; #endif // DEVICEMODEL_H mozilla-vpn-client-2.2.0/src/models/helpmodel.cpp000066400000000000000000000061441404202232700217670ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "helpmodel.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" namespace { bool s_initialized = false; Logger logger(LOG_MAIN, "HelpModel"); struct HelpEntry { HelpEntry(const char* nameId, bool externalLink, bool viewLog, MozillaVPN::LinkType linkType) : m_nameId(nameId), m_externalLink(externalLink), m_viewLog(viewLog), m_linkType(linkType) {} const char* m_nameId; bool m_externalLink; bool m_viewLog; MozillaVPN::LinkType m_linkType; }; static QList s_helpEntries; void maybeInitialize() { if (s_initialized) { return; } s_initialized = true; // Here we use the logger to force lrelease to add the help menu Ids. //% "View log" logger.log() << "Adding:" << qtTrId("help.viewLog"); s_helpEntries.append(HelpEntry("help.viewLog", #if defined(MVPN_ANDROID) || defined(MVPN_IOS) false, #else true, #endif true, MozillaVPN::LinkContact)); //% "Help Center" logger.log() << "Adding:" << qtTrId("help.helpCenter"); s_helpEntries.append( HelpEntry("help.helpCenter", true, false, MozillaVPN::LinkHelpSupport)); //% "Contact us" logger.log() << "Adding:" << qtTrId("help.contactUs"); s_helpEntries.append( HelpEntry("help.contactUs", true, false, MozillaVPN::LinkContact)); } } // namespace HelpModel::HelpModel() { MVPN_COUNT_CTOR(HelpModel); } HelpModel::~HelpModel() { MVPN_COUNT_DTOR(HelpModel); } void HelpModel::open(int id) { Q_ASSERT(s_initialized); Q_ASSERT(id >= 0 && id < s_helpEntries.length()); const HelpEntry& entry = s_helpEntries.at(id); if (entry.m_viewLog) { emit MozillaVPN::instance()->requestViewLogs(); return; } MozillaVPN::instance()->openLink(entry.m_linkType); } void HelpModel::forEach(std::function&& a_callback) { maybeInitialize(); std::function callback = std::move(a_callback); for (int i = 0; i < s_helpEntries.length(); ++i) { callback(s_helpEntries.at(i).m_nameId, i); } } QHash HelpModel::roleNames() const { QHash roles; roles[HelpEntryRole] = "name"; roles[HelpIdRole] = "id"; roles[HelpExternalLinkRole] = "externalLink"; return roles; } int HelpModel::rowCount(const QModelIndex&) const { maybeInitialize(); return s_helpEntries.length(); } QVariant HelpModel::data(const QModelIndex& index, int role) const { maybeInitialize(); if (!index.isValid() || index.row() >= s_helpEntries.length()) { return QVariant(); } switch (role) { case HelpEntryRole: return QVariant(qtTrId(s_helpEntries.at(index.row()).m_nameId)); case HelpIdRole: return QVariant(index.row()); case HelpExternalLinkRole: return QVariant(s_helpEntries.at(index.row()).m_externalLink); default: return QVariant(); } } mozilla-vpn-client-2.2.0/src/models/helpmodel.h000066400000000000000000000015421404202232700214310ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef HELPMODEL_H #define HELPMODEL_H #include class HelpModel final : public QAbstractListModel { Q_OBJECT Q_DISABLE_COPY_MOVE(HelpModel) public: enum HelpRoles { HelpEntryRole = Qt::UserRole + 1, HelpIdRole, HelpExternalLinkRole, }; HelpModel(); ~HelpModel(); Q_INVOKABLE void open(int id); void forEach(std::function&& callback); // QAbstractListModel methods QHash roleNames() const override; int rowCount(const QModelIndex&) const override; QVariant data(const QModelIndex& index, int role) const override; }; #endif // HELPMODEL_H mozilla-vpn-client-2.2.0/src/models/keys.cpp000066400000000000000000000035501404202232700207670ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "keys.h" #include "devicemodel.h" #include "leakdetector.h" #include "settingsholder.h" #include #include #include #include Keys::Keys() { MVPN_COUNT_CTOR(Keys); } Keys::~Keys() { MVPN_COUNT_DTOR(Keys); } bool Keys::fromSettings() { SettingsHolder* settingsHolder = SettingsHolder::instance(); Q_ASSERT(settingsHolder); if (!settingsHolder->hasPrivateKey()) { return false; } // Quick migration to retrieve the public key from the current device. if (!settingsHolder->hasPublicKey()) { if (!settingsHolder->hasDevices()) { return false; } const QByteArray& json = settingsHolder->devices(); QJsonDocument doc = QJsonDocument::fromJson(json); if (!doc.isObject()) { return false; } QJsonObject obj = doc.object(); if (!obj.contains("devices")) { return false; } QJsonValue devices = obj.value("devices"); if (!devices.isArray()) { return false; } QJsonArray devicesArray = devices.toArray(); for (QJsonValue deviceValue : devicesArray) { Device device; if (!device.fromJson(deviceValue)) { return false; } if (!device.isDevice(Device::currentDeviceName())) { continue; } settingsHolder->setPublicKey(device.publicKey()); break; } } m_privateKey = settingsHolder->privateKey(); m_publicKey = settingsHolder->publicKey(); return true; } void Keys::storeKeys(const QString& privateKey, const QString& publicKey) { m_privateKey = privateKey; m_publicKey = publicKey; } void Keys::forgetKeys() { m_privateKey.clear(); m_publicKey.clear(); } mozilla-vpn-client-2.2.0/src/models/keys.h000066400000000000000000000014201404202232700204260ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef KEYS_H #define KEYS_H #include #include class Keys final { Q_DISABLE_COPY_MOVE(Keys) public: Keys(); ~Keys(); [[nodiscard]] bool fromSettings(); bool initialized() const { return !m_privateKey.isEmpty() && !m_publicKey.isEmpty(); } void storeKeys(const QString& privateKey, const QString& publicKey); void forgetKeys(); const QString& privateKey() const { return m_privateKey; } const QString& publicKey() const { return m_publicKey; } private: QString m_privateKey; QString m_publicKey; }; #endif // KEYS_H mozilla-vpn-client-2.2.0/src/models/server.cpp000066400000000000000000000071541404202232700213260ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "server.h" #include "leakdetector.h" #include #include #include #include Server::Server() { MVPN_COUNT_CTOR(Server); } Server::Server(const Server& other) { MVPN_COUNT_CTOR(Server); *this = other; } Server& Server::operator=(const Server& other) { if (this == &other) return *this; m_hostname = other.m_hostname; m_ipv4AddrIn = other.m_ipv4AddrIn; m_ipv4Gateway = other.m_ipv4Gateway; m_ipv6AddrIn = other.m_ipv6AddrIn; m_ipv6Gateway = other.m_ipv6Gateway; m_portRanges = other.m_portRanges; m_publicKey = other.m_publicKey; m_weight = other.m_weight; return *this; } Server::~Server() { MVPN_COUNT_DTOR(Server); } bool Server::fromJson(const QJsonObject& obj) { // Reset. m_hostname = ""; QJsonValue hostname = obj.value("hostname"); if (!hostname.isString()) { return false; } QJsonValue ipv4AddrIn = obj.value("ipv4_addr_in"); if (!ipv4AddrIn.isString()) { return false; } QJsonValue ipv4Gateway = obj.value("ipv4_gateway"); if (!ipv4Gateway.isString()) { return false; } QJsonValue ipv6AddrIn = obj.value("ipv6_addr_in"); // If this object comes from the IOS migration, the ipv6_addr_in is missing. QJsonValue ipv6Gateway = obj.value("ipv6_gateway"); if (!ipv6Gateway.isString()) { return false; } QJsonValue publicKey = obj.value("public_key"); if (!publicKey.isString()) { return false; } QJsonValue weight = obj.value("weight"); if (!weight.isDouble()) { return false; } QJsonValue portRanges = obj.value("port_ranges"); if (!portRanges.isArray()) { return false; } QList> prList; QJsonArray portRangesArray = portRanges.toArray(); for (QJsonValue portRangeValue : portRangesArray) { if (!portRangeValue.isArray()) { return false; } QJsonArray port = portRangeValue.toArray(); if (port.count() != 2) { return false; } QJsonValue a = port.at(0); if (!a.isDouble()) { return false; } QJsonValue b = port.at(1); if (!b.isDouble()) { return false; } prList.append(QPair(a.toInt(), b.toInt())); } m_hostname = hostname.toString(); m_ipv4AddrIn = ipv4AddrIn.toString(); m_ipv4Gateway = ipv4Gateway.toString(); m_ipv6AddrIn = ipv6AddrIn.toString(); m_ipv6Gateway = ipv6Gateway.toString(); m_portRanges.swap(prList); m_publicKey = publicKey.toString(); m_weight = weight.toInt(); return true; } // static const Server& Server::weightChooser(const QList& servers) { Q_ASSERT(!servers.isEmpty()); uint32_t weightSum = 0; for (const Server& server : servers) { weightSum += server.weight(); } quint32 r = QRandomGenerator::global()->generate() % (weightSum + 1); for (const Server& server : servers) { if (server.weight() >= r) { return server; } r -= server.weight(); } // This should not happen. Q_ASSERT(false); return servers[0]; } uint32_t Server::choosePort() const { if (m_portRanges.isEmpty()) { return 0; } quint32 r = QRandomGenerator::global()->generate() % m_portRanges.length(); const QPair& ports = m_portRanges.at(r); if (ports.first == ports.second) { return ports.first; } Q_ASSERT(ports.first < ports.second); return ports.first + (QRandomGenerator::global()->generate() % (ports.second - ports.first)); } mozilla-vpn-client-2.2.0/src/models/server.h000066400000000000000000000024641404202232700207720ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef SERVER_H #define SERVER_H #include #include #include class QJsonObject; class Server final { public: Server(); Server(const Server& other); Server& operator=(const Server& other); ~Server(); [[nodiscard]] bool fromJson(const QJsonObject& obj); static const Server& weightChooser(const QList& servers); bool initialized() const { return !m_hostname.isEmpty(); } const QString& hostname() const { return m_hostname; } const QString& ipv4AddrIn() const { return m_ipv4AddrIn; } const QString& ipv4Gateway() const { return m_ipv4Gateway; } const QString& ipv6AddrIn() const { return m_ipv6AddrIn; } const QString& ipv6Gateway() const { return m_ipv6Gateway; } const QString& publicKey() const { return m_publicKey; } uint32_t weight() const { return m_weight; } uint32_t choosePort() const; private: QString m_hostname; QString m_ipv4AddrIn; QString m_ipv4Gateway; QString m_ipv6AddrIn; QString m_ipv6Gateway; QList> m_portRanges; QString m_publicKey; uint32_t m_weight = 0; }; #endif // SERVER_H mozilla-vpn-client-2.2.0/src/models/servercity.cpp000066400000000000000000000030011404202232700222020ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "servercity.h" #include "leakdetector.h" #include #include #include ServerCity::ServerCity() { MVPN_COUNT_CTOR(ServerCity); } ServerCity::ServerCity(const ServerCity& other) { MVPN_COUNT_CTOR(ServerCity); *this = other; } ServerCity& ServerCity::operator=(const ServerCity& other) { if (this == &other) return *this; m_name = other.m_name; m_code = other.m_code; m_servers = other.m_servers; return *this; } ServerCity::~ServerCity() { MVPN_COUNT_DTOR(ServerCity); } bool ServerCity::fromJson(const QJsonObject& obj) { QJsonValue name = obj.value("name"); if (!name.isString()) { return false; } QJsonValue code = obj.value("code"); if (!code.isString()) { return false; } QJsonValue serversValue = obj.value("servers"); if (!serversValue.isArray()) { return false; } QList servers; QJsonArray serversArray = serversValue.toArray(); for (QJsonValue serverValue : serversArray) { if (!serverValue.isObject()) { return false; } QJsonObject serverObj = serverValue.toObject(); Server server; if (!server.fromJson(serverObj)) { return false; } servers.append(server); } m_name = name.toString(); m_code = code.toString(); m_servers.swap(servers); return true; } mozilla-vpn-client-2.2.0/src/models/servercity.h000066400000000000000000000014431404202232700216570ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef SERVERCITY_H #define SERVERCITY_H #include "server.h" #include #include class QJsonObject; class ServerCity final { public: ServerCity(); ServerCity(const ServerCity& other); ServerCity& operator=(const ServerCity& other); ~ServerCity(); [[nodiscard]] bool fromJson(const QJsonObject& obj); const QString& name() const { return m_name; } const QString& code() const { return m_code; } const QList servers() const { return m_servers; } private: QString m_name; QString m_code; QList m_servers; }; #endif // SERVERCITY_H mozilla-vpn-client-2.2.0/src/models/servercountry.cpp000066400000000000000000000041071404202232700227450ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "servercountry.h" #include "leakdetector.h" #include "serverdata.h" #include #include #include #include namespace { bool sortCityCallback(const ServerCity& a, const ServerCity& b) { return a.name() < b.name(); } } // anonymous namespace ServerCountry::ServerCountry() { MVPN_COUNT_CTOR(ServerCountry); } ServerCountry::ServerCountry(const ServerCountry& other) { MVPN_COUNT_CTOR(ServerCountry); *this = other; } ServerCountry& ServerCountry::operator=(const ServerCountry& other) { if (this == &other) return *this; m_name = other.m_name; m_code = other.m_code; m_cities = other.m_cities; return *this; } ServerCountry::~ServerCountry() { MVPN_COUNT_DTOR(ServerCountry); } bool ServerCountry::fromJson(const QJsonObject& countryObj) { QJsonValue countryName = countryObj.value("name"); if (!countryName.isString()) { return false; } QJsonValue countryCode = countryObj.value("code"); if (!countryCode.isString()) { return false; } QJsonValue cities = countryObj.value("cities"); if (!cities.isArray()) { return false; } QList scList; QJsonArray citiesArray = cities.toArray(); for (QJsonValue cityValue : citiesArray) { if (!cityValue.isObject()) { return false; } QJsonObject cityObject = cityValue.toObject(); ServerCity serverCity; if (!serverCity.fromJson(cityObject)) { return false; } scList.append(serverCity); } m_name = countryName.toString(); m_code = countryCode.toString(); m_cities.swap(scList); std::sort(m_cities.begin(), m_cities.end(), sortCityCallback); return true; } const QList ServerCountry::servers(const ServerData& data) const { for (const ServerCity& city : m_cities) { if (city.name() == data.city()) { return city.servers(); } } return QList(); } mozilla-vpn-client-2.2.0/src/models/servercountry.h000066400000000000000000000016561404202232700224200ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef SERVERCOUNTRY_H #define SERVERCOUNTRY_H #include "servercity.h" #include #include class ServerData; class QJsonObject; class QStringList; class ServerCountry final { public: ServerCountry(); ServerCountry(const ServerCountry& other); ServerCountry& operator=(const ServerCountry& other); ~ServerCountry(); [[nodiscard]] bool fromJson(const QJsonObject& obj); const QString& name() const { return m_name; } const QString& code() const { return m_code; } const QList& cities() const { return m_cities; } const QList servers(const ServerData& data) const; private: QString m_name; QString m_code; QList m_cities; }; #endif // SERVERCOUNTRY_H mozilla-vpn-client-2.2.0/src/models/servercountrymodel.cpp000066400000000000000000000132371404202232700237720ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "servercountrymodel.h" #include "leakdetector.h" #include "logger.h" #include "servercountry.h" #include "serverdata.h" #include "settingsholder.h" #include #include #include #include namespace { Logger logger(LOG_MODEL, "ServerCountryModel"); } ServerCountryModel::ServerCountryModel() { MVPN_COUNT_CTOR(ServerCountryModel); } ServerCountryModel::~ServerCountryModel() { MVPN_COUNT_DTOR(ServerCountryModel); } bool ServerCountryModel::fromSettings() { SettingsHolder* settingsHolder = SettingsHolder::instance(); Q_ASSERT(settingsHolder); logger.log() << "Reading the server list from settings"; if (!settingsHolder->hasServers()) { return false; } const QByteArray json = settingsHolder->servers(); if (!fromJsonInternal(json)) { return false; } m_rawJson = json; return true; } bool ServerCountryModel::fromJson(const QByteArray& s) { logger.log() << "Reading from JSON"; if (!s.isEmpty() && m_rawJson == s) { logger.log() << "Nothing has changed"; return true; } if (!fromJsonInternal(s)) { return false; } m_rawJson = s; return true; } namespace { bool sortCountryCallback(const ServerCountry& a, const ServerCountry& b) { return a.name() < b.name(); } } // anonymous namespace bool ServerCountryModel::fromJsonInternal(const QByteArray& s) { beginResetModel(); m_rawJson = ""; m_countries.clear(); QJsonDocument doc = QJsonDocument::fromJson(s); if (!doc.isObject()) { return false; } QJsonObject obj = doc.object(); QJsonValue countries = obj.value("countries"); if (!countries.isArray()) { return false; } QJsonArray countriesArray = countries.toArray(); for (QJsonValue countryValue : countriesArray) { if (!countryValue.isObject()) { return false; } QJsonObject countryObj = countryValue.toObject(); ServerCountry country; if (!country.fromJson(countryObj)) { return false; } m_countries.append(country); } std::sort(m_countries.begin(), m_countries.end(), sortCountryCallback); endResetModel(); return true; } QHash ServerCountryModel::roleNames() const { QHash roles; roles[NameRole] = "name"; roles[CodeRole] = "code"; roles[CitiesRole] = "cities"; return roles; } QVariant ServerCountryModel::data(const QModelIndex& index, int role) const { if (!index.isValid() || index.row() >= m_countries.length()) { return QVariant(); } switch (role) { case NameRole: return QVariant(m_countries.at(index.row()).name()); case CodeRole: return QVariant(m_countries.at(index.row()).code()); case CitiesRole: { QStringList list; const QList& cities = m_countries.at(index.row()).cities(); for (const ServerCity& city : cities) { list.append(city.name()); } return QVariant(list); } default: return QVariant(); } } bool ServerCountryModel::pickIfExists(const QString& countryCode, const QString& cityCode, ServerData& data) const { logger.log() << "Checking if the server exists" << countryCode << cityCode; for (const ServerCountry& country : m_countries) { if (country.code() == countryCode) { for (const ServerCity& city : country.cities()) { if (city.code() == cityCode) { data.initialize(country, city); return true; } } break; } } return false; } void ServerCountryModel::pickRandom(ServerData& data) const { logger.log() << "Choosing a random server"; quint32 countryId = QRandomGenerator::global()->generate() % m_countries.length(); const ServerCountry& country = m_countries[countryId]; quint32 cityId = QRandomGenerator::global()->generate() % country.cities().length(); const ServerCity& city = country.cities().at(cityId); data.initialize(country, city); } bool ServerCountryModel::pickByIPv4Address(const QString& ipv4Address, ServerData& data) const { logger.log() << "Choosing a server with addres:" << ipv4Address; for (const ServerCountry& country : m_countries) { for (const ServerCity& city : country.cities()) { for (const Server& server : city.servers()) { if (server.ipv4AddrIn() == ipv4Address) { data.initialize(country, city); return true; } } } } return false; } bool ServerCountryModel::exists(ServerData& data) const { logger.log() << "Check if the server is still valid."; Q_ASSERT(data.initialized()); for (const ServerCountry& country : m_countries) { if (country.code() == data.countryCode()) { for (const ServerCity& city : country.cities()) { if (data.city() == city.name()) { return true; } } break; } } return false; } const QList ServerCountryModel::servers(const ServerData& data) const { for (const ServerCountry& country : m_countries) { if (country.code() == data.countryCode()) { return country.servers(data); } } return QList(); } const QString ServerCountryModel::countryName( const QString& countryCode) const { for (const ServerCountry& country : m_countries) { if (country.code() == countryCode) { return country.name(); } } return QString(); } void ServerCountryModel::retranslate() { beginResetModel(); endResetModel(); } mozilla-vpn-client-2.2.0/src/models/servercountrymodel.h000066400000000000000000000033671404202232700234420ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef SERVERCOUNTRYMODEL_H #define SERVERCOUNTRYMODEL_H #include "servercountry.h" #include #include #include #include class ServerData; class ServerCountryModel final : public QAbstractListModel { Q_DISABLE_COPY_MOVE(ServerCountryModel) public: enum ServerCountryRoles { NameRole = Qt::UserRole + 1, CodeRole, CitiesRole, }; ServerCountryModel(); ~ServerCountryModel(); [[nodiscard]] bool fromSettings(); [[nodiscard]] bool fromJson(const QByteArray& data); bool initialized() const { return !m_rawJson.isEmpty(); } void pickRandom(ServerData& data) const; bool pickIfExists(const QString& countryCode, const QString& cityCode, ServerData& data) const; // For windows data migration. bool pickByIPv4Address(const QString& ipv4Address, ServerData& data) const; bool exists(ServerData& data) const; const QList servers(const ServerData& data) const; const QString countryName(const QString& countryCode) const; const QList& countries() const { return m_countries; } void retranslate(); // QAbstractListModel methods QHash roleNames() const override; int rowCount(const QModelIndex&) const override { return m_countries.length(); } QVariant data(const QModelIndex& index, int role) const override; private: [[nodiscard]] bool fromJsonInternal(const QByteArray& data); private: QByteArray m_rawJson; QList m_countries; }; #endif // SERVERCOUNTRYMODEL_H mozilla-vpn-client-2.2.0/src/models/serverdata.cpp000066400000000000000000000041101404202232700221450ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "serverdata.h" #include "leakdetector.h" #include "logger.h" #include "servercountrymodel.h" #include "settingsholder.h" namespace { Logger logger(LOG_MODEL, "ServerData"); } ServerData::ServerData() { MVPN_COUNT_CTOR(ServerData); } ServerData::~ServerData() { MVPN_COUNT_DTOR(ServerData); } bool ServerData::fromSettings() { SettingsHolder* settingsHolder = SettingsHolder::instance(); Q_ASSERT(settingsHolder); if (!settingsHolder->hasCurrentServerCountry() || !settingsHolder->hasCurrentServerCity() || !settingsHolder->hasCurrentServerCountryCode()) { return false; } initializeInternal(settingsHolder->currentServerCountryCode(), settingsHolder->currentServerCountry(), settingsHolder->currentServerCity()); logger.log() << m_countryCode << m_country << m_city; return true; } void ServerData::writeSettings() { SettingsHolder* settingsHolder = SettingsHolder::instance(); Q_ASSERT(settingsHolder); settingsHolder->setCurrentServerCountryCode(m_countryCode); settingsHolder->setCurrentServerCountry(m_country); settingsHolder->setCurrentServerCity(m_city); } void ServerData::initialize(const ServerCountry& country, const ServerCity& city) { logger.log() << "Country:" << country.name() << "City:" << city.name(); initializeInternal(country.code(), country.name(), city.name()); emit changed(); } void ServerData::update(const QString& countryCode, const QString& country, const QString& city) { initializeInternal(countryCode, country, city); emit changed(); } void ServerData::initializeInternal(const QString& countryCode, const QString& country, const QString& city) { m_initialized = true; m_countryCode = countryCode; m_country = country; m_city = city; } mozilla-vpn-client-2.2.0/src/models/serverdata.h000066400000000000000000000025771404202232700216310ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef SERVERDATA_H #define SERVERDATA_H #include class ServerCountryModel; class ServerCountry; class ServerCity; class Server; class ServerData final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(ServerData); Q_PROPERTY(QString countryCode READ countryCode NOTIFY changed) Q_PROPERTY(QString city READ city NOTIFY changed) public: ServerData(); ~ServerData(); [[nodiscard]] bool fromSettings(); void writeSettings(); void initialize(const ServerCountry& country, const ServerCity& city); bool initialized() const { return m_initialized; } const QString& countryCode() const { return m_countryCode; } const QString& country() const { return m_country; } const QString& city() const { return m_city; } void forget() { m_initialized = false; } void update(const QString& countryCode, const QString& country, const QString& city); signals: void changed(); private: void initializeInternal(const QString& countryCode, const QString& country, const QString& city); private: bool m_initialized = false; QString m_countryCode; QString m_country; QString m_city; }; #endif // SERVERDATA_H mozilla-vpn-client-2.2.0/src/models/user.cpp000066400000000000000000000056001404202232700207700ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "user.h" #include "leakdetector.h" #include "settingsholder.h" #include #include #include #include #include User::User() { MVPN_COUNT_CTOR(User); } User::~User() { MVPN_COUNT_DTOR(User); } bool User::fromJson(const QByteArray& json) { m_initialized = false; QJsonDocument doc = QJsonDocument::fromJson(json); if (!doc.isObject()) { return false; } QJsonObject obj = doc.object(); QJsonValue avatar = obj.value("avatar"); if (!avatar.isString()) { return false; } QJsonValue displayName = obj.value("display_name"); if (!displayName.isString()) { return false; } QJsonValue email = obj.value("email"); if (!email.isString()) { return false; } QJsonValue maxDevices = obj.value("max_devices"); if (!maxDevices.isDouble()) { return false; } QJsonValue subscriptions = obj.value("subscriptions"); if (!subscriptions.isObject()) { return false; } bool subscriptionNeeded = true; QJsonObject subscriptionsObj = subscriptions.toObject(); if (subscriptionsObj.contains("vpn")) { QJsonValue subVpn = subscriptionsObj.value("vpn"); if (!subVpn.isObject()) { return false; } QJsonObject subVpnObj = subVpn.toObject(); QJsonValue active = subVpnObj.value("active"); if (!active.isBool()) { return false; } subscriptionNeeded = !active.toBool(); } m_avatar = avatar.toString(); m_displayName = displayName.toString(); m_email = email.toString(); m_maxDevices = maxDevices.toInt(); m_subscriptionNeeded = subscriptionNeeded; m_initialized = true; emit changed(); return true; } bool User::fromSettings() { SettingsHolder* settingsHolder = SettingsHolder::instance(); Q_ASSERT(settingsHolder); if (!settingsHolder->hasUserAvatar() || !settingsHolder->hasUserDisplayName() || !settingsHolder->hasUserEmail() || !settingsHolder->hasUserMaxDevices() || !settingsHolder->hasUserSubscriptionNeeded()) { return false; } m_avatar = settingsHolder->userAvatar(); m_displayName = settingsHolder->userDisplayName(); m_email = settingsHolder->userEmail(); m_maxDevices = settingsHolder->userMaxDevices(); m_subscriptionNeeded = settingsHolder->userSubscriptionNeeded(); m_initialized = true; return true; } void User::writeSettings() { SettingsHolder* settingsHolder = SettingsHolder::instance(); Q_ASSERT(settingsHolder); settingsHolder->setUserAvatar(m_avatar); settingsHolder->setUserDisplayName(m_displayName); settingsHolder->setUserEmail(m_email); settingsHolder->setUserMaxDevices(m_maxDevices); settingsHolder->setUserSubscriptionNeeded(m_subscriptionNeeded); } mozilla-vpn-client-2.2.0/src/models/user.h000066400000000000000000000025021404202232700204330ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef USER_H #define USER_H #include #include #include class QJsonObject; class User final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(User) Q_PROPERTY(QString avatar READ avatar NOTIFY changed) Q_PROPERTY(QString displayName READ displayName NOTIFY changed) Q_PROPERTY(QString email READ email NOTIFY changed) Q_PROPERTY(int maxDevices READ maxDevices NOTIFY changed) public: User(); ~User(); [[nodiscard]] bool fromJson(const QByteArray& json); [[nodiscard]] bool fromSettings(); void writeSettings(); bool initialized() const { return m_initialized; } const QString& avatar() const { return m_avatar; } const QString& displayName() const { return m_displayName; } const QString& email() const { return m_email; } int maxDevices() const { return (int)m_maxDevices; } bool subscriptionNeeded() const { return m_subscriptionNeeded; } signals: void changed(); private: QString m_avatar; QString m_displayName; QString m_email; int m_maxDevices = 5; bool m_subscriptionNeeded = false; bool m_initialized = false; }; #endif // USER_H mozilla-vpn-client-2.2.0/src/mozillavpn.cpp000066400000000000000000000753171404202232700207360ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozillavpn.h" #include "constants.h" #include "leakdetector.h" #include "logger.h" #include "loghandler.h" #include "logoutobserver.h" #include "models/device.h" #include "models/servercountrymodel.h" #include "models/user.h" #include "qmlengineholder.h" #include "settingsholder.h" #include "tasks/accountandservers/taskaccountandservers.h" #include "tasks/adddevice/taskadddevice.h" #include "tasks/authenticate/taskauthenticate.h" #include "tasks/captiveportallookup/taskcaptiveportallookup.h" #include "tasks/controlleraction/taskcontrolleraction.h" #include "tasks/function/taskfunction.h" #include "tasks/heartbeat/taskheartbeat.h" #include "tasks/removedevice/taskremovedevice.h" #include "urlopener.h" #ifdef MVPN_IOS # include "platforms/ios/iaphandler.h" # include "platforms/ios/iosdatamigration.h" # include "platforms/ios/taskiosproducts.h" #endif #ifdef MVPN_ANDROID # include "platforms/android/androidutils.h" #endif #ifdef MVPN_WINDOWS # include "platforms/windows/windowsdatamigration.h" #endif #ifdef MVPN_ANDROID # include "platforms/android/androiddatamigration.h" #endif #ifdef MVPN_INSPECTOR # include "inspector/inspectorwebsocketconnection.h" #endif #include #include #include #include #include #include #include #include #include #include // in seconds, hide alerts constexpr const uint32_t HIDE_ALERT_SEC = 4; namespace { Logger logger(LOG_MAIN, "MozillaVPN"); MozillaVPN* s_instance = nullptr; } // namespace // static MozillaVPN* MozillaVPN::instance() { Q_ASSERT(s_instance); return s_instance; } MozillaVPN::MozillaVPN() : m_private(new Private()) { MVPN_COUNT_CTOR(MozillaVPN); logger.log() << "Creating MozillaVPN singleton"; Q_ASSERT(!s_instance); s_instance = this; connect(&m_alertTimer, &QTimer::timeout, [this]() { setAlert(NoAlert); }); connect(&m_periodicOperationsTimer, &QTimer::timeout, [this]() { scheduleTask(new TaskAccountAndServers()); scheduleTask(new TaskCaptivePortalLookup()); scheduleTask(new TaskHeartbeat()); }); connect(this, &MozillaVPN::stateChanged, [this]() { if (m_state != StateMain) { // We don't call deactivate() because that is meant to be used for // UI interactions only and it deletes all the pending tasks. scheduleTask(new TaskControllerAction(TaskControllerAction::eDeactivate)); } }); connect(&m_private->m_controller, &Controller::readyToUpdate, [this]() { setState(StateUpdateRequired); }); connect(&m_private->m_controller, &Controller::readyToBackendFailure, [this]() { deleteTasks(); setState(StateBackendFailure); }); connect(&m_private->m_controller, &Controller::stateChanged, this, &MozillaVPN::controllerStateChanged); connect(&m_private->m_controller, &Controller::stateChanged, &m_private->m_statusIcon, &StatusIcon::stateChanged); connect(this, &MozillaVPN::stateChanged, &m_private->m_statusIcon, &StatusIcon::stateChanged); connect(&m_private->m_controller, &Controller::stateChanged, &m_private->m_connectionHealth, &ConnectionHealth::connectionStateChanged); connect(&m_private->m_controller, &Controller::stateChanged, &m_private->m_captivePortalDetection, &CaptivePortalDetection::stateChanged); connect(&m_private->m_connectionHealth, &ConnectionHealth::stabilityChanged, &m_private->m_captivePortalDetection, &CaptivePortalDetection::stateChanged); connect(SettingsHolder::instance(), &SettingsHolder::captivePortalAlertChanged, &m_private->m_captivePortalDetection, &CaptivePortalDetection::settingsChanged); connect(&m_private->m_controller, &Controller::stateChanged, &m_private->m_connectionDataHolder, &ConnectionDataHolder::stateChanged); connect(this, &MozillaVPN::stateChanged, &m_private->m_connectionDataHolder, &ConnectionDataHolder::stateChanged); #ifdef MVPN_IOS IAPHandler* iap = IAPHandler::createInstance(); connect(iap, &IAPHandler::subscriptionStarted, this, &MozillaVPN::subscriptionStarted); connect(iap, &IAPHandler::subscriptionFailed, this, &MozillaVPN::subscriptionFailed); connect(iap, &IAPHandler::subscriptionCanceled, this, &MozillaVPN::subscriptionCanceled); connect(iap, &IAPHandler::subscriptionCompleted, this, &MozillaVPN::subscriptionCompleted); connect(iap, &IAPHandler::alreadySubscribed, this, &MozillaVPN::alreadySubscribed); #endif } MozillaVPN::~MozillaVPN() { MVPN_COUNT_DTOR(MozillaVPN); logger.log() << "Deleting MozillaVPN singleton"; Q_ASSERT(s_instance == this); s_instance = nullptr; delete m_private; } MozillaVPN::State MozillaVPN::state() const { return m_state; } void MozillaVPN::initialize() { logger.log() << "MozillaVPN Initialization"; Q_ASSERT(!m_initialized); m_initialized = true; // This is our first state. Q_ASSERT(m_state == StateInitialize); m_private->m_releaseMonitor.runSoon(); SettingsHolder* settingsHolder = SettingsHolder::instance(); Q_ASSERT(settingsHolder); #ifdef MVPN_IOS if (!settingsHolder->hasNativeIOSDataMigrated()) { IOSDataMigration::migrate(); settingsHolder->setNativeIOSDataMigrated(true); } #endif #ifdef MVPN_WINDOWS if (!settingsHolder->hasNativeWindowsDataMigrated()) { WindowsDataMigration::migrate(); settingsHolder->setNativeWindowsDataMigrated(true); } #endif #ifdef MVPN_ANDROID if (!settingsHolder->hasNativeAndroidDataMigrated()) { AndroidDataMigration::migrate(); settingsHolder->setNativeAndroidDataMigrated(true); } #endif m_private->m_captivePortalDetection.initialize(); m_private->m_networkWatcher.initialize(); if (!settingsHolder->hasToken()) { return; } logger.log() << "We have a valid token"; if (!m_private->m_user.fromSettings()) { return; } if (!m_private->m_keys.fromSettings()) { logger.log() << "No keys found"; settingsHolder->clear(); return; } if (!m_private->m_serverCountryModel.fromSettings()) { logger.log() << "No server list found"; settingsHolder->clear(); return; } if (!m_private->m_deviceModel.fromSettings(keys())) { logger.log() << "No devices found"; settingsHolder->clear(); return; } if (!m_private->m_deviceModel.hasCurrentDevice(keys())) { logger.log() << "The current device has not been found"; settingsHolder->clear(); return; } if (!m_private->m_captivePortal.fromSettings()) { // We do not care about CaptivePortal settings. } if (!modelsInitialized()) { logger.log() << "Models not initialized yet"; settingsHolder->clear(); return; } Q_ASSERT(!m_private->m_serverData.initialized()); if (!m_private->m_serverData.fromSettings()) { m_private->m_serverCountryModel.pickRandom(m_private->m_serverData); Q_ASSERT(m_private->m_serverData.initialized()); m_private->m_serverData.writeSettings(); } scheduleTask(new TaskAccountAndServers()); scheduleTask(new TaskCaptivePortalLookup()); #ifdef MVPN_IOS scheduleTask(new TaskIOSProducts()); #endif setUserAuthenticated(true); maybeStateMain(); } void MozillaVPN::setState(State state) { logger.log() << "Set state:" << state; m_state = state; emit stateChanged(); // If we are activating the app, let's initialize the controller. if (m_state == StateMain) { m_private->m_controller.initialize(); startSchedulingPeriodicOperations(); } else { stopSchedulingPeriodicOperations(); } } void MozillaVPN::maybeStateMain() { logger.log() << "Maybe state main"; #ifdef MVPN_IOS if (m_private->m_user.subscriptionNeeded()) { setState(StateSubscriptionNeeded); return; } #endif #if !defined(MVPN_ANDROID) && !defined(MVPN_IOS) SettingsHolder* settingsHolder = SettingsHolder::instance(); if (!settingsHolder->hasPostAuthenticationShown() || !settingsHolder->postAuthenticationShown()) { settingsHolder->setPostAuthenticationShown(true); setState(StatePostAuthentication); return; } #endif if (!m_private->m_deviceModel.hasCurrentDevice(keys())) { Q_ASSERT(m_private->m_deviceModel.activeDevices() == m_private->m_user.maxDevices()); setState(StateDeviceLimit); return; } if (m_state != StateUpdateRequired) { setState(StateMain); } } void MozillaVPN::authenticate() { logger.log() << "Authenticate"; setState(StateAuthenticating); hideAlert(); if (m_userAuthenticated) { LogoutObserver* lo = new LogoutObserver(this); // Let's use QueuedConnection to avoid nexted tasks executions. connect(lo, &LogoutObserver::ready, this, &MozillaVPN::authenticate, Qt::QueuedConnection); return; } scheduleTask(new TaskHeartbeat()); scheduleTask(new TaskAuthenticate()); } void MozillaVPN::abortAuthentication() { logger.log() << "Abort authentication"; Q_ASSERT(m_state == StateAuthenticating); setState(StateInitialize); } void MozillaVPN::openLink(LinkType linkType) { logger.log() << "Opening link: " << linkType; QString url; switch (linkType) { case LinkAccount: url = Constants::API_URL; url.append("/r/vpn/account"); break; case LinkContact: url = Constants::API_URL; url.append("/r/vpn/contact"); break; case LinkFeedback: url = Constants::API_URL; url.append("/r/vpn/client/feedback"); break; case LinkHelpSupport: url = Constants::API_URL; url.append("/r/vpn/support"); break; case LinkLicense: url = "https://github.com/mozilla-mobile/mozilla-vpn-client/blob/main/" "LICENSE.md"; break; case LinkTermsOfService: url = Constants::API_URL; url.append("/r/vpn/terms"); break; case LinkPrivacyNotice: url = Constants::API_URL; url.append("/r/vpn/privacy"); break; case LinkUpdate: url = Constants::API_URL; url.append("/r/vpn/update/"); #if defined(MVPN_LINUX) url.append("linux"); #elif defined(MVPN_MACOS) url.append("macos"); #elif defined(MVPN_IOS) url.append("ios"); #elif defined(MVPN_ANDROID) url.append("android"); #else url.append("dummy"); #endif break; case LinkSubscriptionBlocked: url = Constants::API_URL; url.append("/r/vpn/subscriptionBlocked"); break; default: qFatal("Unsupported link type!"); return; } UrlOpener::open(url); } void MozillaVPN::scheduleTask(Task* task) { Q_ASSERT(task); logger.log() << "Scheduling task: " << task->name(); m_tasks.append(task); maybeRunTask(); } void MozillaVPN::maybeRunTask() { logger.log() << "Tasks: " << m_tasks.size(); if (m_running_task || m_tasks.empty()) { return; } m_running_task = m_tasks.takeFirst(); Q_ASSERT(m_running_task); QObject::connect(m_running_task, &Task::completed, this, &MozillaVPN::taskCompleted); m_running_task->run(this); } void MozillaVPN::taskCompleted() { logger.log() << "Task completed"; Q_ASSERT(m_running_task); m_running_task->deleteLater(); m_running_task->disconnect(); m_running_task = nullptr; maybeRunTask(); } void MozillaVPN::setToken(const QString& token) { SettingsHolder::instance()->setToken(token); } void MozillaVPN::authenticationCompleted(const QByteArray& json, const QString& token) { logger.log() << "Authentication completed"; if (!m_private->m_user.fromJson(json)) { logger.log() << "Failed to parse the User JSON data"; errorHandle(ErrorHandler::RemoteServiceError); return; } if (!m_private->m_deviceModel.fromJson(keys(), json)) { logger.log() << "Failed to parse the DeviceModel JSON data"; errorHandle(ErrorHandler::RemoteServiceError); return; } m_private->m_user.writeSettings(); m_private->m_deviceModel.writeSettings(); setToken(token); setUserAuthenticated(true); #ifdef MVPN_IOS if (m_private->m_user.subscriptionNeeded()) { scheduleTask(new TaskIOSProducts()); scheduleTask(new TaskFunction([this](MozillaVPN*) { maybeStateMain(); })); return; } #endif completeActivation(); } MozillaVPN::RemovalDeviceOption MozillaVPN::maybeRemoveCurrentDevice() { logger.log() << "Maybe remove current device"; const Device* currentDevice = m_private->m_deviceModel.device(Device::currentDeviceName()); if (!currentDevice) { logger.log() << "No removal needed because the device doesn't exist yet"; return DeviceNotFound; } if (currentDevice->publicKey() == m_private->m_keys.publicKey() && !m_private->m_keys.privateKey().isEmpty()) { logger.log() << "No removal needed because the private key is still fine."; return DeviceStillValid; } logger.log() << "Removal needed"; scheduleTask(new TaskRemoveDevice(currentDevice->publicKey())); return DeviceRemoved; } void MozillaVPN::completeActivation() { int deviceCount = m_private->m_deviceModel.activeDevices(); // If we already have a device with the same name, let's remove it. RemovalDeviceOption option = maybeRemoveCurrentDevice(); if (option == DeviceRemoved) { --deviceCount; } if (deviceCount >= m_private->m_user.maxDevices()) { maybeStateMain(); return; } // Here we add the current device. if (option != DeviceStillValid) { scheduleTask(new TaskAddDevice(Device::currentDeviceName())); } // Let's fetch the account and the servers. scheduleTask(new TaskAccountAndServers()); #ifdef MVPN_IOS scheduleTask(new TaskIOSProducts()); #endif // Finally we are able to activate the client. scheduleTask(new TaskFunction([this](MozillaVPN*) { if (!modelsInitialized()) { logger.log() << "Failed to complete the authentication"; errorHandle(ErrorHandler::RemoteServiceError); setUserAuthenticated(false); return; } Q_ASSERT(m_private->m_serverData.initialized()); maybeStateMain(); })); } void MozillaVPN::deviceAdded(const QString& deviceName, const QString& publicKey, const QString& privateKey) { Q_UNUSED(publicKey); logger.log() << "Device added" << deviceName; SettingsHolder::instance()->setPrivateKey(privateKey); SettingsHolder::instance()->setPublicKey(publicKey); m_private->m_keys.storeKeys(privateKey, publicKey); } void MozillaVPN::deviceRemoved(const QString& deviceName) { logger.log() << "Device removed"; m_private->m_deviceModel.removeDevice(deviceName); } bool MozillaVPN::setServerList(const QByteArray& serverData) { if (!m_private->m_serverCountryModel.fromJson(serverData)) { logger.log() << "Failed to store the server-countries"; return false; } SettingsHolder::instance()->setServers(serverData); return true; } void MozillaVPN::serversFetched(const QByteArray& serverData) { logger.log() << "Server fetched!"; if (!setServerList(serverData)) { // This is OK. The check is done elsewhere. return; } // The serverData could be unset or invalid with the new server list. if (!m_private->m_serverData.initialized() || !m_private->m_serverCountryModel.exists(m_private->m_serverData)) { m_private->m_serverCountryModel.pickRandom(m_private->m_serverData); Q_ASSERT(m_private->m_serverData.initialized()); m_private->m_serverData.writeSettings(); } } void MozillaVPN::removeDevice(const QString& deviceName) { logger.log() << "Remove device" << deviceName; // Let's inform the UI about what is going to happen. emit deviceRemoving(deviceName); const Device* device = m_private->m_deviceModel.device(deviceName); if (device) { scheduleTask(new TaskRemoveDevice(device->publicKey())); } if (m_state != StateDeviceLimit) { return; } // Let's recover from the device-limit mode. Q_ASSERT(!m_private->m_deviceModel.hasCurrentDevice(keys())); // Here we add the current device. scheduleTask(new TaskAddDevice(Device::currentDeviceName())); // Let's fetch the devices again. scheduleTask(new TaskAccountAndServers()); // Finally we are able to activate the client. scheduleTask(new TaskFunction([this](MozillaVPN*) { if (m_state != StateDeviceLimit) { return; } if (!modelsInitialized()) { logger.log() << "Models not initialized yet"; errorHandle(ErrorHandler::RemoteServiceError); SettingsHolder::instance()->clear(); setState(StateInitialize); return; } maybeStateMain(); })); } void MozillaVPN::accountChecked(const QByteArray& json) { logger.log() << "Account checked"; if (!m_private->m_user.fromJson(json)) { logger.log() << "Failed to parse the User JSON data"; // We don't need to communicate it to the user. Let's ignore it. return; } if (!m_private->m_deviceModel.fromJson(keys(), json)) { logger.log() << "Failed to parse the DeviceModel JSON data"; // We don't need to communicate it to the user. Let's ignore it. return; } m_private->m_user.writeSettings(); m_private->m_deviceModel.writeSettings(); #ifdef MVPN_IOS if (m_private->m_user.subscriptionNeeded() && m_state == StateMain) { maybeStateMain(); return; } #endif // To test the subscription needed view, comment out this line: // m_private->m_controller.subscriptionNeeded(); } void MozillaVPN::cancelAuthentication() { logger.log() << "Canceling authentication"; if (m_state != StateAuthenticating) { // We cannot cancel tasks if we are not in authenticating state. return; } reset(true); } void MozillaVPN::logout() { logger.log() << "Logout"; setAlert(LogoutAlert); deleteTasks(); #ifdef MVPN_IOS IAPHandler::instance()->stopSubscription(); #endif // update-required state is the only one we want to keep when logging out. if (m_state != StateUpdateRequired) { setState(StateInitialize); } if (m_private->m_deviceModel.hasCurrentDevice(keys())) { scheduleTask(new TaskRemoveDevice(keys()->publicKey())); } scheduleTask(new TaskFunction([](MozillaVPN* vpn) { vpn->reset(false); })); } void MozillaVPN::reset(bool forceInitialState) { logger.log() << "Cleaning up all"; deleteTasks(); SettingsHolder::instance()->clear(); m_private->m_keys.forgetKeys(); m_private->m_serverData.forget(); setUserAuthenticated(false); if (forceInitialState) { setState(StateInitialize); } } void MozillaVPN::deleteTasks() { for (Task* task : m_tasks) { task->deleteLater(); } m_tasks.clear(); if (m_running_task) { m_running_task->deleteLater(); m_running_task->disconnect(); m_running_task = nullptr; } } void MozillaVPN::setAlert(AlertType alert) { m_alertTimer.stop(); if (alert != NoAlert) { m_alertTimer.start(1000 * HIDE_ALERT_SEC); } m_alert = alert; emit alertChanged(); } void MozillaVPN::errorHandle(ErrorHandler::ErrorType error) { logger.log() << "Handling error" << error; Q_ASSERT(error != ErrorHandler::NoError); AlertType alert = NoAlert; switch (error) { case ErrorHandler::VPNDependentConnectionError: // This type of error might be caused by switchting the VPN // on, in which case it's okay to be ignored. // In Case the vpn is not connected - handle this like a // ConnectionFailureError if (controller()->state() == Controller::StateOn) { logger.log() << "Ignore network error probably caused by enabled VPN"; break; } [[fallthrough]]; case ErrorHandler::ConnectionFailureError: alert = ConnectionFailedAlert; break; case ErrorHandler::NoConnectionError: alert = NoConnectionAlert; break; case ErrorHandler::AuthenticationError: if (m_userAuthenticated) { alert = AuthenticationFailedAlert; } break; case ErrorHandler::ControllerError: alert = ControllerErrorAlert; break; case ErrorHandler::RemoteServiceError: alert = RemoteServiceErrorAlert; break; case ErrorHandler::SubscriptionFailureError: alert = SubscriptionFailureAlert; break; case ErrorHandler::GeoIpRestrictionError: alert = GeoIpRestrictionAlert; break; case ErrorHandler::UnrecoverableError: alert = UnrecoverableErrorAlert; break; default: break; } setAlert(alert); logger.log() << "Alert:" << alert << "State:" << m_state; if (alert == NoAlert) { return; } // Any error in authenticating state sends to the Initial state. if (m_state == StateAuthenticating) { setState(StateInitialize); return; } if (alert == AuthenticationFailedAlert) { reset(true); return; } } const QList MozillaVPN::servers() const { return m_private->m_serverCountryModel.servers(m_private->m_serverData); } void MozillaVPN::changeServer(const QString& countryCode, const QString& city) { QString countryName = m_private->m_serverCountryModel.countryName(countryCode); m_private->m_serverData.update(countryCode, countryName, city); m_private->m_serverData.writeSettings(); } void MozillaVPN::postAuthenticationCompleted() { logger.log() << "Post authentication completed"; // Super racy, but it could happen that we are already in update-required // state. if (m_state == StateUpdateRequired) { return; } maybeStateMain(); } void MozillaVPN::setUpdateRecommended(bool value) { m_updateRecommended = value; emit updateRecommendedChanged(); } void MozillaVPN::setUserAuthenticated(bool state) { logger.log() << "User authentication state:" << state; m_userAuthenticated = state; emit userAuthenticationChanged(); } void MozillaVPN::startSchedulingPeriodicOperations() { logger.log() << "Start scheduling account and servers" << Constants::SCHEDULE_ACCOUNT_AND_SERVERS_TIMER_MSEC; m_periodicOperationsTimer.start( Constants::SCHEDULE_ACCOUNT_AND_SERVERS_TIMER_MSEC); } void MozillaVPN::stopSchedulingPeriodicOperations() { logger.log() << "Stop scheduling account and servers"; m_periodicOperationsTimer.stop(); } bool MozillaVPN::writeAndShowLogs(QStandardPaths::StandardLocation location) { return writeLogs(location, [](const QString& filename) { logger.log() << "Opening the logFile somehow:" << filename; QUrl url = QUrl::fromLocalFile(filename); UrlOpener::open(url); }); } bool MozillaVPN::writeLogs( QStandardPaths::StandardLocation location, std::function&& a_callback) { logger.log() << "Trying to save logs in:" << location; std::function callback = std::move(a_callback); if (!QFileInfo::exists(QStandardPaths::writableLocation(location))) { return false; } QString filename; QDate now = QDate::currentDate(); QTextStream(&filename) << "mozillavpn-" << now.year() << "-" << now.month() << "-" << now.day() << ".txt"; QDir logDir(QStandardPaths::writableLocation(location)); QString logFile = logDir.filePath(filename); if (QFileInfo::exists(logFile)) { logger.log() << logFile << "exists. Let's try a new filename"; for (uint32_t i = 1;; ++i) { QString filename; QTextStream(&filename) << "mozillavpn-" << now.year() << "-" << now.month() << "-" << now.day() << "_" << i << ".txt"; logFile = logDir.filePath(filename); if (!QFileInfo::exists(logFile)) { logger.log() << "Filename found!" << i; break; } } } logger.log() << "Writing logs into: " << logFile; QFile* file = new QFile(logFile); if (!file->open(QIODevice::WriteOnly | QIODevice::Text)) { logger.log() << "Failed to open the logfile"; delete file; return false; } QTextStream* out = new QTextStream(file); serializeLogs(out, [callback = std::move(callback), logFile, file, out]() { Q_ASSERT(out); Q_ASSERT(file); delete out; delete file; callback(logFile); }); return true; } void MozillaVPN::serializeLogs(QTextStream* out, std::function&& a_finalizeCallback) { std::function finalizeCallback = std::move(a_finalizeCallback); *out << "Mozilla VPN logs" << Qt::endl << "================" << Qt::endl << Qt::endl; LogHandler::writeLogs(*out); MozillaVPN::instance()->controller()->getBackendLogs( [out, finalizeCallback = std::move(finalizeCallback)](const QString& logs) { logger.log() << "Logs from the backend service received"; *out << Qt::endl << Qt::endl << "Mozilla VPN backend logs" << Qt::endl << "========================" << Qt::endl << Qt::endl; if (!logs.isEmpty()) { *out << logs; } else { *out << "No logs from the backend."; } finalizeCallback(); }); } void MozillaVPN::viewLogs() { logger.log() << "View logs"; if (writeAndShowLogs(QStandardPaths::DesktopLocation)) { return; } if (writeAndShowLogs(QStandardPaths::HomeLocation)) { return; } if (writeAndShowLogs(QStandardPaths::TempLocation)) { return; } qWarning() << "No Desktop, no Home, no Temp folder. Unable to store the log files."; } void MozillaVPN::retrieveLogs() { logger.log() << "Retrieve logs"; QString* buffer = new QString(); QTextStream* out = new QTextStream(buffer); serializeLogs(out, [this, buffer, out]() { Q_ASSERT(out); Q_ASSERT(buffer); delete out; emit logsReady(*buffer); delete buffer; }); } void MozillaVPN::storeInClipboard(const QString& text) { logger.log() << "Store in clipboard"; QApplication::clipboard()->setText(text); } void MozillaVPN::cleanupLogs() { logger.log() << "Cleanup logs"; LogHandler::instance()->cleanupLogs(); MozillaVPN::instance()->controller()->cleanupBackendLogs(); } bool MozillaVPN::modelsInitialized() const { logger.log() << "Checking model initialization"; if (!m_private->m_user.initialized()) { logger.log() << "User model not initialized"; return false; } if (!m_private->m_serverCountryModel.initialized()) { logger.log() << "Server country model not initialized"; return false; } if (!m_private->m_deviceModel.initialized()) { logger.log() << "Device model not initialized"; return false; } if (!m_private->m_deviceModel.hasCurrentDevice(&m_private->m_keys)) { logger.log() << "Current device not registered"; return false; } if (!m_private->m_keys.initialized()) { logger.log() << "Key model not initialized"; return false; } return true; } void MozillaVPN::requestSettings() { logger.log() << "Settings required"; QmlEngineHolder::instance()->showWindow(); emit settingsNeeded(); } void MozillaVPN::requestAbout() { logger.log() << "About view requested"; QmlEngineHolder::instance()->showWindow(); emit aboutNeeded(); } void MozillaVPN::requestViewLogs() { logger.log() << "View log requested"; QmlEngineHolder::instance()->showWindow(); emit viewLogsNeeded(); } void MozillaVPN::activate() { logger.log() << "VPN tunnel activation"; deleteTasks(); scheduleTask(new TaskControllerAction(TaskControllerAction::eActivate)); } void MozillaVPN::deactivate() { logger.log() << "VPN tunnel deactivation"; deleteTasks(); scheduleTask(new TaskControllerAction(TaskControllerAction::eDeactivate)); } void MozillaVPN::refreshDevices() { logger.log() << "Refresh devices"; if (m_state == StateMain) { scheduleTask(new TaskAccountAndServers()); } } void MozillaVPN::quit() { logger.log() << "quit"; deleteTasks(); qApp->quit(); } #ifdef MVPN_IOS void MozillaVPN::subscriptionStarted() { logger.log() << "Subscription started"; setState(StateSubscriptionValidation); IAPHandler* iap = IAPHandler::instance(); // If IPA is not ready yet (race condition), let's register the products // again. if (!iap->hasProductsRegistered()) { scheduleTask(new TaskIOSProducts()); scheduleTask( new TaskFunction([](MozillaVPN* vpn) { vpn->subscriptionStarted(); })); return; } iap->startSubscription(); } void MozillaVPN::subscriptionCompleted() { if (m_state != StateSubscriptionValidation) { logger.log() << "Random subscription completion received. Let's ignore it."; return; } logger.log() << "Subscription completed"; completeActivation(); } void MozillaVPN::subscriptionFailed() { subscriptionFailedInternal(false /* canceled by user */); } void MozillaVPN::subscriptionCanceled() { subscriptionFailedInternal(true /* canceled by user */); } void MozillaVPN::subscriptionFailedInternal(bool canceledByUser) { if (m_state != StateSubscriptionValidation) { logger.log() << "Random subscription failure received. Let's ignore it."; return; } logger.log() << "Subscription failed or canceled"; // Let's go back to the subscription needed. setState(StateSubscriptionNeeded); if (!canceledByUser) { errorHandle(ErrorHandler::SubscriptionFailureError); } scheduleTask(new TaskAccountAndServers()); scheduleTask(new TaskFunction([this](MozillaVPN*) { if (!m_private->m_user.subscriptionNeeded() && m_state == StateSubscriptionNeeded) { maybeStateMain(); return; } })); } void MozillaVPN::alreadySubscribed() { if (m_state != StateSubscriptionValidation) { logger.log() << "Random already-subscribed notification received. Let's ignore it."; return; } setState(StateSubscriptionBlocked); } #endif void MozillaVPN::update() { logger.log() << "Update"; setUpdating(true); if (m_private->m_controller.state() != Controller::StateOff && m_private->m_controller.state() != Controller::StateInitializing) { deactivate(); return; } m_private->m_releaseMonitor.update(); } void MozillaVPN::setUpdating(bool updating) { m_updating = updating; emit updatingChanged(); } void MozillaVPN::controllerStateChanged() { logger.log() << "Controller state changed"; if (!m_controllerInitialized) { m_controllerInitialized = true; if (SettingsHolder::instance()->startAtBoot()) { logger.log() << "Start on boot"; activate(); } } if (m_updating && m_private->m_controller.state() == Controller::StateOff) { update(); } } void MozillaVPN::backendServiceRestore() { logger.log() << "Background service restore request"; // TODO } void MozillaVPN::heartbeatCompleted(bool success) { logger.log() << "Server-side check done:" << success; if (!success) { m_private->m_controller.backendFailure(); return; } if (m_state != StateBackendFailure) { return; } if (!modelsInitialized() || !m_userAuthenticated) { setState(StateInitialize); return; } maybeStateMain(); } void MozillaVPN::triggerHeartbeat() { scheduleTask(new TaskHeartbeat()); } mozilla-vpn-client-2.2.0/src/mozillavpn.h000066400000000000000000000211011404202232700203610ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef MOZILLAVPN_H #define MOZILLAVPN_H #include "captiveportal/captiveportal.h" #include "captiveportal/captiveportaldetection.h" #include "closeeventhandler.h" #include "connectiondataholder.h" #include "connectionhealth.h" #include "controller.h" #include "errorhandler.h" #include "models/devicemodel.h" #include "models/helpmodel.h" #include "models/keys.h" #include "models/servercountrymodel.h" #include "models/serverdata.h" #include "models/user.h" #include "networkwatcher.h" #include "releasemonitor.h" #include "statusicon.h" #include #include #include #include #include class QTextStream; class Task; #ifdef UNIT_TEST class TestTasks; #endif class MozillaVPN final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(MozillaVPN) public: enum State { StateInitialize, StateAuthenticating, StatePostAuthentication, StateMain, StateUpdateRequired, StateSubscriptionNeeded, StateSubscriptionValidation, StateSubscriptionBlocked, StateDeviceLimit, StateBackendFailure, }; Q_ENUM(State); enum AlertType { NoAlert, AuthenticationFailedAlert, ConnectionFailedAlert, LogoutAlert, NoConnectionAlert, ControllerErrorAlert, RemoteServiceErrorAlert, SubscriptionFailureAlert, GeoIpRestrictionAlert, UnrecoverableErrorAlert, }; Q_ENUM(AlertType) enum LinkType { LinkAccount, LinkContact, LinkFeedback, LinkLicense, LinkHelpSupport, LinkTermsOfService, LinkPrivacyNotice, LinkUpdate, LinkSubscriptionBlocked, }; Q_ENUM(LinkType) private: Q_PROPERTY(State state READ state NOTIFY stateChanged) Q_PROPERTY(AlertType alert READ alert NOTIFY alertChanged) Q_PROPERTY(QString versionString READ versionString CONSTANT) Q_PROPERTY(QString buildNumber READ buildNumber CONSTANT) Q_PROPERTY(bool updateRecommended READ updateRecommended NOTIFY updateRecommendedChanged) Q_PROPERTY(bool userAuthenticated READ userAuthenticated NOTIFY userAuthenticationChanged) Q_PROPERTY(bool startMinimized READ startMinimized CONSTANT) Q_PROPERTY(bool updating READ updating NOTIFY updatingChanged) public: MozillaVPN(); ~MozillaVPN(); static MozillaVPN* instance(); void initialize(); State state() const; AlertType alert() const { return m_alert; } // Exposed QML methods: Q_INVOKABLE void authenticate(); Q_INVOKABLE void cancelAuthentication(); Q_INVOKABLE void openLink(LinkType linkType); Q_INVOKABLE void removeDevice(const QString& deviceName); Q_INVOKABLE void hideAlert() { setAlert(NoAlert); } Q_INVOKABLE void hideUpdateRecommendedAlert() { setUpdateRecommended(false); } Q_INVOKABLE void postAuthenticationCompleted(); Q_INVOKABLE void viewLogs(); Q_INVOKABLE void retrieveLogs(); Q_INVOKABLE void cleanupLogs(); Q_INVOKABLE void storeInClipboard(const QString& text); Q_INVOKABLE void activate(); Q_INVOKABLE void deactivate(); Q_INVOKABLE void refreshDevices(); Q_INVOKABLE void update(); Q_INVOKABLE void backendServiceRestore(); Q_INVOKABLE void triggerHeartbeat(); // Internal object getters: CaptivePortal* captivePortal() { return &m_private->m_captivePortal; } CaptivePortalDetection* captivePortalDetection() { return &m_private->m_captivePortalDetection; } CloseEventHandler* closeEventHandler() { return &m_private->m_closeEventHandler; } ConnectionDataHolder* connectionDataHolder() { return &m_private->m_connectionDataHolder; } ConnectionHealth* connectionHealth() { return &m_private->m_connectionHealth; } Controller* controller() { return &m_private->m_controller; } ServerData* currentServer() { return &m_private->m_serverData; } DeviceModel* deviceModel() { return &m_private->m_deviceModel; } Keys* keys() { return &m_private->m_keys; } HelpModel* helpModel() { return &m_private->m_helpModel; } NetworkWatcher* networkWatcher() { return &m_private->m_networkWatcher; } ReleaseMonitor* releaseMonitor() { return &m_private->m_releaseMonitor; } ServerCountryModel* serverCountryModel() { return &m_private->m_serverCountryModel; } StatusIcon* statusIcon() { return &m_private->m_statusIcon; } User* user() { return &m_private->m_user; } // Called at the end of the authentication flow. We can continue adding the // device if it doesn't exist yet, or we can go to OFF state. void authenticationCompleted(const QByteArray& json, const QString& token); void deviceAdded(const QString& deviceName, const QString& publicKey, const QString& privateKey); void deviceRemoved(const QString& deviceName); void serversFetched(const QByteArray& serverData); void accountChecked(const QByteArray& json); const QList servers() const; void errorHandle(ErrorHandler::ErrorType error); void abortAuthentication(); void changeServer(const QString& countryCode, const QString& city); const QString versionString() const { return QString(APP_VERSION); } const QString buildNumber() const { return QString(BUILD_ID); } void logout(); bool updateRecommended() const { return m_updateRecommended; } void setUpdateRecommended(bool value); bool userAuthenticated() const { return m_userAuthenticated; } bool startMinimized() const { return m_startMinimized; } void setStartMinimized(bool startMinimized) { m_startMinimized = startMinimized; } void setToken(const QString& token); [[nodiscard]] bool setServerList(const QByteArray& serverData); void reset(bool forceInitialState); bool modelsInitialized() const; void quit(); bool updating() const { return m_updating; } void setUpdating(bool updating); void heartbeatCompleted(bool success); private: void setState(State state); void maybeStateMain(); void scheduleTask(Task* task); void maybeRunTask(); void deleteTasks(); void setUserAuthenticated(bool state); void startSchedulingPeriodicOperations(); void stopSchedulingPeriodicOperations(); void setAlert(AlertType alert); bool writeAndShowLogs(QStandardPaths::StandardLocation location); bool writeLogs(QStandardPaths::StandardLocation location, std::function&& a_callback); void serializeLogs(QTextStream* out, std::function&& finalizeCallback); #ifdef MVPN_IOS void subscriptionStarted(); void subscriptionCompleted(); void subscriptionFailed(); void subscriptionCanceled(); void subscriptionFailedInternal(bool canceledByUser); void alreadySubscribed(); #endif void completeActivation(); enum RemovalDeviceOption { DeviceNotFound, DeviceStillValid, DeviceRemoved, }; RemovalDeviceOption maybeRemoveCurrentDevice(); void controllerStateChanged(); public slots: void requestSettings(); void requestAbout(); void requestViewLogs(); private slots: void taskCompleted(); signals: void stateChanged(); void alertChanged(); void updateRecommendedChanged(); void userAuthenticationChanged(); void deviceRemoving(const QString& deviceName); void settingsNeeded(); void aboutNeeded(); void viewLogsNeeded(); void updatingChanged(); // This is used only on android but, if we use #ifdef MVPN_ANDROID, qml engine // complains... void loadAndroidAuthenticationView(); void logsReady(const QString& logs); private: bool m_initialized = false; // Internal objects. struct Private { CaptivePortal m_captivePortal; CaptivePortalDetection m_captivePortalDetection; CloseEventHandler m_closeEventHandler; ConnectionDataHolder m_connectionDataHolder; ConnectionHealth m_connectionHealth; Controller m_controller; DeviceModel m_deviceModel; Keys m_keys; HelpModel m_helpModel; NetworkWatcher m_networkWatcher; ReleaseMonitor m_releaseMonitor; ServerCountryModel m_serverCountryModel; ServerData m_serverData; StatusIcon m_statusIcon; User m_user; }; Private* m_private = nullptr; // Task handling. Task* m_running_task = nullptr; QList m_tasks; State m_state = StateInitialize; AlertType m_alert = NoAlert; QTimer m_alertTimer; QTimer m_periodicOperationsTimer; bool m_updateRecommended = false; bool m_userAuthenticated = false; bool m_startMinimized = false; bool m_updating = false; bool m_controllerInitialized = false; #ifdef UNIT_TEST friend class TestTasks; #endif }; #endif // MOZILLAVPN_H mozilla-vpn-client-2.2.0/src/networkmanager.cpp000066400000000000000000000020561404202232700215550ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "networkmanager.h" #include "constants.h" #include "leakdetector.h" namespace { NetworkManager* s_instance = nullptr; } NetworkManager::NetworkManager() { MVPN_COUNT_CTOR(NetworkManager); Q_ASSERT(!s_instance); s_instance = this; } NetworkManager::~NetworkManager() { MVPN_COUNT_DTOR(NetworkManager); Q_ASSERT(s_instance == this); s_instance = nullptr; } // static NetworkManager* NetworkManager::instance() { Q_ASSERT(s_instance); return s_instance; } // static QByteArray NetworkManager::userAgent() { QByteArray userAgent; userAgent.append("MozillaVPN/" APP_VERSION " ("); #ifdef MVPN_WASM userAgent.append("WASM"); #else userAgent.append(QSysInfo::productType().toLocal8Bit()); userAgent.append(" "); userAgent.append(QSysInfo::productVersion().toLocal8Bit()); userAgent.append(")"); #endif return userAgent; } mozilla-vpn-client-2.2.0/src/networkmanager.h000066400000000000000000000011551404202232700212210ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef NETWORKMANAGER_H #define NETWORKMANAGER_H #include class QNetworkAccessManager; class NetworkManager : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(NetworkManager) public: NetworkManager(); virtual ~NetworkManager(); static NetworkManager* instance(); static QByteArray userAgent(); virtual QNetworkAccessManager* networkAccessManager() = 0; }; #endif // NETWORKMANAGER_H mozilla-vpn-client-2.2.0/src/networkrequest.cpp000066400000000000000000000262171404202232700216400ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "networkrequest.h" #include "captiveportal/captiveportal.h" #include "constants.h" #include "leakdetector.h" #include "logger.h" #include "networkmanager.h" #include "settingsholder.h" #include #include #include #include #include #include // Timeout for the network requests. constexpr uint32_t REQUEST_TIMEOUT_MSEC = 15000; namespace { Logger logger(LOG_NETWORKING, "NetworkRequest"); } NetworkRequest::NetworkRequest(QObject* parent, int status) : QObject(parent), m_status(status) { MVPN_COUNT_CTOR(NetworkRequest); logger.log() << "Network request created"; #ifndef MVPN_WASM m_request.setRawHeader("User-Agent", NetworkManager::userAgent()); #endif m_timer.setSingleShot(true); connect(&m_timer, &QTimer::timeout, this, &NetworkRequest::timeout); connect(&m_timer, &QTimer::timeout, this, &QObject::deleteLater); } NetworkRequest::~NetworkRequest() { MVPN_COUNT_DTOR(NetworkRequest); } // static NetworkRequest* NetworkRequest::createForGetUrl(QObject* parent, const QString& url, int status) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, status); r->m_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); r->m_request.setUrl(url); r->getRequest(); return r; } // static NetworkRequest* NetworkRequest::createForAuthenticationVerification( QObject* parent, const QString& pkceCodeSuccess, const QString& pkceCodeVerifier) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 200); r->m_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QUrl url(Constants::API_URL); url.setPath("/api/v2/vpn/login/verify"); r->m_request.setUrl(url); QJsonObject obj; obj.insert("code", pkceCodeSuccess); obj.insert("code_verifier", pkceCodeVerifier); QJsonDocument json; json.setObject(obj); r->postRequest(json.toJson(QJsonDocument::Compact)); return r; } // static NetworkRequest* NetworkRequest::createForDeviceCreation( QObject* parent, const QString& deviceName, const QString& pubKey) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 201); QByteArray authorizationHeader = "Bearer "; authorizationHeader.append(SettingsHolder::instance()->token().toLocal8Bit()); r->m_request.setRawHeader("Authorization", authorizationHeader); r->m_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QUrl url(Constants::API_URL); url.setPath("/api/v1/vpn/device"); r->m_request.setUrl(url); QJsonObject obj; obj.insert("name", deviceName); obj.insert("pubkey", pubKey); QJsonDocument json; json.setObject(obj); r->postRequest(json.toJson(QJsonDocument::Compact)); return r; } // static NetworkRequest* NetworkRequest::createForDeviceRemoval(QObject* parent, const QString& pubKey) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 204); QByteArray authorizationHeader = "Bearer "; authorizationHeader.append(SettingsHolder::instance()->token().toLocal8Bit()); r->m_request.setRawHeader("Authorization", authorizationHeader); QString url(Constants::API_URL); url.append("/api/v1/vpn/device/"); url.append(QUrl::toPercentEncoding(pubKey)); QUrl u(url); r->m_request.setUrl(QUrl(url)); #ifdef QT_DEBUG logger.log() << "Network starting" << r->m_request.url().toString(); #endif r->deleteRequest(); return r; } NetworkRequest* NetworkRequest::createForServers(QObject* parent) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 200); QByteArray authorizationHeader = "Bearer "; authorizationHeader.append(SettingsHolder::instance()->token().toLocal8Bit()); r->m_request.setRawHeader("Authorization", authorizationHeader); QUrl url(Constants::API_URL); url.setPath("/api/v1/vpn/servers"); r->m_request.setUrl(url); r->getRequest(); return r; } NetworkRequest* NetworkRequest::createForVersions(QObject* parent) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 200); QUrl url(Constants::API_URL); url.setPath("/api/v1/vpn/versions"); r->m_request.setUrl(url); r->getRequest(); return r; } NetworkRequest* NetworkRequest::createForAccount(QObject* parent) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 200); QByteArray authorizationHeader = "Bearer "; authorizationHeader.append(SettingsHolder::instance()->token().toLocal8Bit()); r->m_request.setRawHeader("Authorization", authorizationHeader); QUrl url(Constants::API_URL); url.setPath("/api/v1/vpn/account"); r->m_request.setUrl(url); r->getRequest(); return r; } NetworkRequest* NetworkRequest::createForIpInfo(QObject* parent) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 200); QByteArray authorizationHeader = "Bearer "; authorizationHeader.append(SettingsHolder::instance()->token().toLocal8Bit()); r->m_request.setRawHeader("Authorization", authorizationHeader); QUrl url(Constants::API_URL); url.setPath("/api/v1/vpn/ipinfo"); r->m_request.setUrl(url); r->getRequest(); return r; } NetworkRequest* NetworkRequest::createForCaptivePortalDetection( QObject* parent, const QUrl& url, const QByteArray& host) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 0); r->m_request.setUrl(url); r->m_request.setRawHeader("Host", host); r->getRequest(); return r; } NetworkRequest* NetworkRequest::createForCaptivePortalLookup(QObject* parent) { NetworkRequest* r = new NetworkRequest(parent, 200); QByteArray authorizationHeader = "Bearer "; authorizationHeader.append(SettingsHolder::instance()->token().toLocal8Bit()); r->m_request.setRawHeader("Authorization", authorizationHeader); QUrl url(Constants::API_URL); url.setPath("/api/v1/vpn/dns/detectportal"); r->m_request.setUrl(url); r->getRequest(); return r; } NetworkRequest* NetworkRequest::createForHeartbeat(QObject* parent) { NetworkRequest* r = new NetworkRequest(parent, 200); QUrl url(Constants::API_URL); url.setPath("/__heartbeat__"); r->m_request.setUrl(url); r->getRequest(); return r; } #ifdef MVPN_IOS NetworkRequest* NetworkRequest::createForIOSProducts(QObject* parent) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 200); QByteArray authorizationHeader = "Bearer "; authorizationHeader.append(SettingsHolder::instance()->token().toLocal8Bit()); r->m_request.setRawHeader("Authorization", authorizationHeader); QUrl url(Constants::API_URL); url.setPath("/api/v1/vpn/products/ios"); r->m_request.setUrl(url); r->getRequest(); return r; } NetworkRequest* NetworkRequest::createForIOSPurchase(QObject* parent, const QString& receipt) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 201); r->m_request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); QByteArray authorizationHeader = "Bearer "; authorizationHeader.append(SettingsHolder::instance()->token().toLocal8Bit()); r->m_request.setRawHeader("Authorization", authorizationHeader); QUrl url(Constants::API_URL); url.setPath("/api/v1/vpn/purchases/ios"); r->m_request.setUrl(url); QJsonObject obj; obj.insert("receipt", QJsonValue(receipt)); obj.insert("appId", "org.mozilla.ios.FirefoxVPN"); QJsonDocument json; json.setObject(obj); r->postRequest(json.toJson(QJsonDocument::Compact)); return r; } #endif void NetworkRequest::replyFinished() { Q_ASSERT(m_reply); Q_ASSERT(m_reply->isFinished()); if (m_completed) { Q_ASSERT(!m_timer.isActive()); return; } m_completed = true; m_timer.stop(); int status = statusCode(); logger.log() << "Network reply received - status:" << status << "- expected:" << m_status; QByteArray data = m_reply->readAll(); if (m_reply->error() != QNetworkReply::NoError) { logger.log() << "Network error:" << m_reply->error() << "status code:" << status << "- body:" << data; emit requestFailed(m_reply->error(), data); return; } // This is an extra check for succeeded requests (status code 200 vs 201, for // instance). The real network status check is done in the previous if-stmt. if (m_status && status != m_status) { logger.log() << "Status code unexpected - status code:" << status << "- expected:" << m_status; emit requestFailed(QNetworkReply::ConnectionRefusedError, data); return; } emit requestCompleted(data); } void NetworkRequest::handleHeaderReceived() { logger.log() << "Network header received"; emit requestHeaderReceived(this); } void NetworkRequest::timeout() { Q_ASSERT(m_reply); Q_ASSERT(!m_reply->isFinished()); Q_ASSERT(!m_completed); m_completed = true; m_reply->abort(); logger.log() << "Network request timeout"; emit requestFailed(QNetworkReply::TimeoutError, QByteArray()); } void NetworkRequest::getRequest() { QNetworkAccessManager* manager = NetworkManager::instance()->networkAccessManager(); handleReply(manager->get(m_request)); m_timer.start(REQUEST_TIMEOUT_MSEC); } void NetworkRequest::deleteRequest() { QNetworkAccessManager* manager = NetworkManager::instance()->networkAccessManager(); handleReply(manager->sendCustomRequest(m_request, "DELETE")); m_timer.start(REQUEST_TIMEOUT_MSEC); } void NetworkRequest::postRequest(const QByteArray& body) { QNetworkAccessManager* manager = NetworkManager::instance()->networkAccessManager(); handleReply(manager->post(m_request, body)); m_timer.start(REQUEST_TIMEOUT_MSEC); } void NetworkRequest::handleReply(QNetworkReply* reply) { Q_ASSERT(reply); Q_ASSERT(!m_reply); m_reply = reply; m_reply->setParent(this); connect(m_reply, &QNetworkReply::finished, this, &NetworkRequest::replyFinished); connect(m_reply, &QNetworkReply::metaDataChanged, this, &NetworkRequest::handleHeaderReceived); connect(m_reply, &QNetworkReply::finished, this, &QObject::deleteLater); } int NetworkRequest::statusCode() const { Q_ASSERT(m_reply); QVariant statusCode = m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute); if (!statusCode.isValid()) { return 0; } return statusCode.toInt(); } void NetworkRequest::disableTimeout() { m_timer.stop(); } QByteArray NetworkRequest::rawHeader(const QByteArray& headerName) const { if (!m_reply) { logger.log() << "INTERNAL ERROR! NetworkRequest::rawHeader called before " "starting the request"; return QByteArray(); } return m_reply->rawHeader(headerName); } void NetworkRequest::abort() { if (!m_reply) { logger.log() << "INTERNAL ERROR! NetworkRequest::abort called before " "starting the request"; return; } m_reply->abort(); } mozilla-vpn-client-2.2.0/src/networkrequest.h000066400000000000000000000052441404202232700213020ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef NETWORKREQUEST_H #define NETWORKREQUEST_H #include #include #include #include class QNetworkAccessManager; class NetworkRequest final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(NetworkRequest) public: ~NetworkRequest(); // This object deletes itself at the end of the operation. static NetworkRequest* createForGetUrl(QObject* parent, const QString& url, int status = 0); static NetworkRequest* createForAuthenticationVerification( QObject* parent, const QString& pkceCodeSuccess, const QString& pkceCodeVerifier); static NetworkRequest* createForDeviceCreation(QObject* parent, const QString& deviceName, const QString& pubKey); static NetworkRequest* createForDeviceRemoval(QObject* parent, const QString& pubKey); static NetworkRequest* createForServers(QObject* parent); static NetworkRequest* createForAccount(QObject* parent); static NetworkRequest* createForVersions(QObject* parent); static NetworkRequest* createForIpInfo(QObject* parent); static NetworkRequest* createForCaptivePortalDetection( QObject* parent, const QUrl& url, const QByteArray& host); static NetworkRequest* createForCaptivePortalLookup(QObject* parent); static NetworkRequest* createForHeartbeat(QObject* parent); #ifdef MVPN_IOS static NetworkRequest* createForIOSProducts(QObject* parent); static NetworkRequest* createForIOSPurchase(QObject* parent, const QString& receipt); #endif void disableTimeout(); int statusCode() const; QByteArray rawHeader(const QByteArray& headerName) const; void abort(); private: NetworkRequest(QObject* parent, int status); void deleteRequest(); void getRequest(); void postRequest(const QByteArray& body); void handleReply(QNetworkReply* reply); void handleHeaderReceived(); private slots: void replyFinished(); void timeout(); signals: void requestHeaderReceived(NetworkRequest* request); void requestFailed(QNetworkReply::NetworkError error, const QByteArray& data); void requestCompleted(const QByteArray& data); private: QNetworkRequest m_request; QTimer m_timer; QNetworkReply* m_reply = nullptr; int m_status = 0; bool m_completed = false; }; #endif // NETWORKREQUEST_H mozilla-vpn-client-2.2.0/src/networkwatcher.cpp000066400000000000000000000077331404202232700216070ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "networkwatcher.h" #include "constants.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "networkwatcherimpl.h" #include "platforms/dummy/dummynetworkwatcher.h" #include "settingsholder.h" #include "systemtrayhandler.h" #ifdef MVPN_WINDOWS # include "platforms/windows/windowsnetworkwatcher.h" #endif #ifdef MVPN_LINUX # include "platforms/linux/linuxnetworkwatcher.h" #endif #ifdef MVPN_MACOS # include "platforms/macos/macosnetworkwatcher.h" #endif #ifdef MVPN_WASM # include "platforms/wasm/wasmnetworkwatcher.h" #endif // How often we notify the same unsecured network constexpr uint32_t NETWORK_WATCHER_TIMER_MSEC = 20000; namespace { Logger logger(LOG_NETWORKING, "NetworkWatcher"); } NetworkWatcher::NetworkWatcher() { MVPN_COUNT_CTOR(NetworkWatcher); } NetworkWatcher::~NetworkWatcher() { MVPN_COUNT_DTOR(NetworkWatcher); } void NetworkWatcher::initialize() { logger.log() << "Initialize"; #if defined(MVPN_WINDOWS) m_impl = new WindowsNetworkWatcher(this); #elif defined(MVPN_LINUX) m_impl = new LinuxNetworkWatcher(this); #elif defined(MVPN_MACOS) m_impl = new MacOSNetworkWatcher(this); #elif defined(MVPN_WASM) m_impl = new WasmNetworkWatcher(this); #else m_impl = new DummyNetworkWatcher(this); #endif connect(m_impl, &NetworkWatcherImpl::unsecuredNetwork, this, &NetworkWatcher::unsecuredNetwork); m_impl->initialize(); SettingsHolder* settingsHolder = SettingsHolder::instance(); Q_ASSERT(settingsHolder); m_active = settingsHolder->unsecuredNetworkAlert(); if (m_active) { m_impl->start(); } connect(settingsHolder, &SettingsHolder::unsecuredNetworkAlertChanged, this, &NetworkWatcher::settingsChanged); } void NetworkWatcher::settingsChanged(bool active) { logger.log() << "Settings changed:" << active; if (m_active == active) { return; } m_active = active; if (m_active) { m_impl->start(); } else { m_impl->stop(); } } void NetworkWatcher::unsecuredNetwork(const QString& networkName, const QString& networkId) { logger.log() << "Unsecured network:" << networkName << "id:" << networkId; #ifndef UNIT_TEST if (!m_active) { logger.log() << "Disabled. Ignoring unsecured network"; return; } MozillaVPN* vpn = MozillaVPN::instance(); Q_ASSERT(vpn); if (vpn->state() != MozillaVPN::StateMain) { logger.log() << "VPN not ready. Ignoring unsecured network"; return; } Controller::State state = vpn->controller()->state(); if (state == Controller::StateOn || state == Controller::StateConnecting || state == Controller::StateSwitching) { logger.log() << "VPN on. Ignoring unsecured network"; return; } if (!m_networks.contains(networkId)) { m_networks.insert(networkId, QElapsedTimer()); } else if (!m_networks[networkId].hasExpired(NETWORK_WATCHER_TIMER_MSEC)) { logger.log() << "Notification already shown. Ignoring unsecured network"; return; } // Let's activate the QElapsedTimer to avoid notification loops. m_networks[networkId].start(); // We don't connect the system tray handler in the CTOR because it can be too // early. Maybe the SystemTrayHandler has not been created yet. We do it at // the first detection of an unsecured network. if (m_firstNotification) { connect(SystemTrayHandler::instance(), &SystemTrayHandler::notificationClicked, this, &NetworkWatcher::notificationClicked); m_firstNotification = false; } SystemTrayHandler::instance()->unsecuredNetworkNotification(networkName); #endif } void NetworkWatcher::notificationClicked(SystemTrayHandler::Message message) { logger.log() << "Notification clicked"; if (message == SystemTrayHandler::UnsecuredNetwork) { MozillaVPN::instance()->activate(); } } mozilla-vpn-client-2.2.0/src/networkwatcher.h000066400000000000000000000021151404202232700212410ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef NETWORKWATCHER_H #define NETWORKWATCHER_H #include "systemtrayhandler.h" #include #include class NetworkWatcherImpl; // This class watches for network changes to detect unsecured wifi. class NetworkWatcher final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(NetworkWatcher) public: NetworkWatcher(); ~NetworkWatcher(); void initialize(); // public for the inspector. void unsecuredNetwork(const QString& networkName, const QString& networkId); private: void settingsChanged(bool active); void notificationClicked(SystemTrayHandler::Message message); private: bool m_active = false; // Platform-specific implementation. NetworkWatcherImpl* m_impl = nullptr; QMap m_networks; // This is used to connect systemTrayHandler lazily. bool m_firstNotification = true; }; #endif // NETWORKWATCHER_H mozilla-vpn-client-2.2.0/src/networkwatcherimpl.h000066400000000000000000000014721404202232700221300ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef NETWORKWATCHERIMPL_H #define NETWORKWATCHERIMPL_H #include class NetworkWatcherImpl : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(NetworkWatcherImpl) public: NetworkWatcherImpl(QObject* parent) : QObject(parent) {} virtual ~NetworkWatcherImpl() = default; virtual void initialize() = 0; virtual void start() { m_active = true; } virtual void stop() { m_active = false; } bool isActive() const { return m_active; } signals: void unsecuredNetwork(const QString& networkName, const QString& networkId); private: bool m_active = false; }; #endif // NETWORKWATCHERIMPL_H mozilla-vpn-client-2.2.0/src/notificationhandler.cpp000066400000000000000000000071451404202232700225610ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "notificationhandler.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #if defined(MVPN_IOS) # include "platforms/ios/iosnotificationhandler.h" #elif defined(MVPN_ANDROID) # include "platforms/android/androidnotificationhandler.h" #else # include "systemtraynotificationhandler.h" #endif namespace { Logger logger(LOG_MAIN, "NotificationHandler"); } // static NotificationHandler* NotificationHandler::create(QObject* parent) { #if defined(MVPN_IOS) return new IOSNotificationHandler(parent); #elif defined(MVPN_ANDROID) return new AndroidNotificationHandler(parent); #else return new SystemTrayNotificationHandler(parent); #endif } NotificationHandler::NotificationHandler(QObject* parent) : QObject(parent) { MVPN_COUNT_CTOR(NotificationHandler); } NotificationHandler::~NotificationHandler() { MVPN_COUNT_DTOR(NotificationHandler); } void NotificationHandler::showNotification() { logger.log() << "Show notification"; MozillaVPN* vpn = MozillaVPN::instance(); if (vpn->state() != MozillaVPN::StateMain && // The Disconnected notification should be triggerable // on StateInitialize, in case the user was connected during a log-out // Otherwise existing notifications showing "connected" would update !(vpn->state() == MozillaVPN::StateInitialize && vpn->controller()->state() == Controller::StateOff)) { return; } QString title; QString message; switch (vpn->controller()->state()) { case Controller::StateOn: m_connected = true; if (m_switching) { m_switching = false; //% "VPN Switched Servers" title = qtTrId("vpn.systray.statusSwitch.title"); //% "Switched from %1, %2 to %3, %4" //: Shown as message body in a notification. %1 and %3 are countries, %2 //: and %4 are cities. message = qtTrId("vpn.systray.statusSwtich.message") .arg(m_switchingServerCountry) .arg(m_switchingServerCity) .arg(vpn->currentServer()->country()) .arg(vpn->currentServer()->city()); } else { //% "VPN Connected" title = qtTrId("vpn.systray.statusConnected.title"); //% "Connected to %1, %2" //: Shown as message body in a notification. %1 is the country, %2 is //: the city. message = qtTrId("vpn.systray.statusConnected.message") .arg(vpn->currentServer()->country()) .arg(vpn->currentServer()->city()); } break; case Controller::StateOff: if (m_connected) { m_connected = false; //% "VPN Disconnected" title = qtTrId("vpn.systray.statusDisconnected.title"); //% "Disconnected from %1, %2" //: Shown as message body in a notification. %1 is the country, %2 is //: the city. message = qtTrId("vpn.systray.statusDisconnected.message") .arg(vpn->currentServer()->country()) .arg(vpn->currentServer()->city()); } break; case Controller::StateSwitching: m_connected = true; m_switching = true; m_switchingServerCountry = vpn->currentServer()->country(); m_switchingServerCity = vpn->currentServer()->city(); break; default: break; } Q_ASSERT(title.isEmpty() == message.isEmpty()); if (!title.isEmpty()) { notify(title, message, 2); } } mozilla-vpn-client-2.2.0/src/notificationhandler.h000066400000000000000000000017071404202232700222240ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef NOTIFICATIONHANDLER_H #define NOTIFICATIONHANDLER_H #include class NotificationHandler : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(NotificationHandler) public: static NotificationHandler* create(QObject* parent); virtual ~NotificationHandler(); public slots: void showNotification(); protected: explicit NotificationHandler(QObject* parent); virtual void notify(const QString& title, const QString& message, int timerSec) = 0; private: QString m_switchingServerCountry; QString m_switchingServerCity; bool m_switching = false; // We want to show a 'disconnected' notification only if we were actually // connected. bool m_connected = false; }; #endif // NOTIFICATIONHANDLER_H mozilla-vpn-client-2.2.0/src/pinghelper.cpp000066400000000000000000000040621404202232700206650ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "pinghelper.h" #include "leakdetector.h" #include "logger.h" #include "pingsender.h" // Any X seconds, a new ping. constexpr uint32_t PING_TIMOUT_SEC = 1; // How many concurrent pings constexpr int PINGS_MAX = 20; namespace { Logger logger(LOG_NETWORKING, "PingHelper"); } PingHelper::PingHelper() { MVPN_COUNT_CTOR(PingHelper); connect(&m_pingTimer, &QTimer::timeout, this, &PingHelper::nextPing); m_pingThread.start(); } PingHelper::~PingHelper() { MVPN_COUNT_DTOR(PingHelper); m_pingThread.quit(); m_pingThread.wait(); } void PingHelper::start(const QString& serverIpv4Gateway) { logger.log() << "PingHelper activated for server:" << serverIpv4Gateway; m_gateway = serverIpv4Gateway; m_pingTimer.start(PING_TIMOUT_SEC * 1000); } void PingHelper::stop() { logger.log() << "PingHelper deactivated"; m_pingTimer.stop(); for (PingSender* pingSender : m_pings) { pingSender->deleteLater(); } m_pings.clear(); } void PingHelper::nextPing() { logger.log() << "Sending a new ping. Total:" << m_pings.length(); PingSender* pingSender = new PingSender(this, &m_pingThread); connect(pingSender, &PingSender::completed, this, &PingHelper::pingReceived); m_pings.append(pingSender); pingSender->send(m_gateway); while (m_pings.length() > PINGS_MAX) { m_pings.at(0)->deleteLater(); m_pings.removeAt(0); } } void PingHelper::pingReceived(PingSender* pingSender, qint64 msec) { logger.log() << "Ping answer received in msec:" << msec; if (!m_pingTimer.isActive()) { logger.log() << "Race condition. Let's ignore this ping"; return; } QMutableListIterator i(m_pings); while (i.hasNext()) { PingSender* thisPingSender = i.next(); if (thisPingSender != pingSender) { continue; } i.remove(); break; } pingSender->deleteLater(); emit pingSentAndReceived(msec); } mozilla-vpn-client-2.2.0/src/pinghelper.h000066400000000000000000000014731404202232700203350ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef PINGHELPER_H #define PINGHELPER_H #include #include #include #include class PingSender; class PingHelper final : public QObject { private: Q_OBJECT Q_DISABLE_COPY_MOVE(PingHelper) public: PingHelper(); ~PingHelper(); void start(const QString& serverIpv4Gateway); void stop(); signals: void pingSentAndReceived(qint64 msec); private: void nextPing(); void pingReceived(PingSender* pingSender, qint64 msec); private: QString m_gateway; QTimer m_pingTimer; QList m_pings; QThread m_pingThread; }; #endif // PINGHELPER_H mozilla-vpn-client-2.2.0/src/pingsender.cpp000066400000000000000000000042111404202232700206620ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "pingsender.h" #include "leakdetector.h" #include "logger.h" #include "pingsendworker.h" #if defined(MVPN_LINUX) || defined(MVPN_ANDROID) # include "platforms/linux/linuxpingsendworker.h" #elif defined(MVPN_MACOS) || defined(MVPN_IOS) # include "platforms/macos/macospingsendworker.h" #elif defined(MVPN_WINDOWS) # include "platforms/windows/windowspingsendworker.h" #elif defined(MVPN_DUMMY) || defined(UNIT_TEST) # include "platforms/dummy/dummypingsendworker.h" #else # error "Unsupported platform" #endif #include namespace { Logger logger(LOG_NETWORKING, "PingSender"); } PingSender::PingSender(QObject* parent, QThread* thread) : QObject(parent) { MVPN_COUNT_CTOR(PingSender); m_time.start(); PingSendWorker* worker = #if defined(MVPN_LINUX) || defined(MVPN_ANDROID) new LinuxPingSendWorker(); #elif defined(MVPN_MACOS) || defined(MVPN_IOS) new MacOSPingSendWorker(); #elif defined(MVPN_WINDOWS) new WindowsPingSendWorker(); #else new DummyPingSendWorker(); #endif // No multi-thread supports for wasm builds. #ifndef MVPN_WASM worker->moveToThread(thread); #endif connect(thread, &QThread::finished, worker, &QObject::deleteLater); connect(this, &PingSender::sendPing, worker, &PingSendWorker::sendPing); connect(this, &QObject::destroyed, worker, &QObject::deleteLater); connect(worker, &PingSendWorker::pingFailed, this, &PingSender::pingFailed); connect(worker, &PingSendWorker::pingSucceeded, this, &PingSender::pingSucceeded); } PingSender::~PingSender() { MVPN_COUNT_DTOR(PingSender); } void PingSender::send(const QString& destination) { logger.log() << "PingSender send to" << destination; emit sendPing(destination); } void PingSender::pingFailed() { logger.log() << "PingSender - Ping Failed"; emit completed(this, m_time.elapsed()); } void PingSender::pingSucceeded() { logger.log() << "PingSender - Ping Succeeded"; emit completed(this, m_time.elapsed()); } mozilla-vpn-client-2.2.0/src/pingsender.h000066400000000000000000000014021404202232700203260ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef PINGSENDER_H #define PINGSENDER_H #include #include class QThread; class PingSender final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(PingSender) public: PingSender(QObject* parent, QThread* thread); ~PingSender(); void send(const QString& destination); signals: void completed(PingSender* pingSender, qint64 msec); // internal only void sendPing(const QString& destination); private slots: void pingFailed(); void pingSucceeded(); private: QElapsedTimer m_time; }; #endif // PINGSENDER_H mozilla-vpn-client-2.2.0/src/pingsendworker.h000066400000000000000000000007511404202232700212370ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef PINGSENDWORKER_H #define PINGSENDWORKER_H #include class PingSendWorker : public QObject { Q_OBJECT public slots: virtual void sendPing(const QString& destination) = 0; signals: void pingSucceeded(); void pingFailed(); }; #endif // PINGSENDWORKER_H mozilla-vpn-client-2.2.0/src/platforms/000077500000000000000000000000001404202232700200315ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/platforms/android/000077500000000000000000000000001404202232700214515ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/platforms/android/androidappimageprovider.cpp000066400000000000000000000107061404202232700270600ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "androidappimageprovider.h" #include "logger.h" #include "leakdetector.h" #include #include #include #include #include namespace { Logger logger(LOG_CONTROLLER, "AndroidAppImageProvider"); } AndroidAppImageProvider::AndroidAppImageProvider(QObject* parent) : QQuickImageProvider(QQuickImageProvider::Image, QQmlImageProviderBase::ForceAsynchronousImageLoading), QObject(parent) { MVPN_COUNT_CTOR(AndroidAppImageProvider); } AndroidAppImageProvider::~AndroidAppImageProvider() { MVPN_COUNT_DTOR(AndroidAppImageProvider); } // from QQuickImageProvider QImage AndroidAppImageProvider::requestImage(const QString& id, QSize* size, const QSize& requestedSize) { QAndroidJniObject activity = QtAndroid::androidActivity(); Q_ASSERT(activity.isValid()); auto jniString = QAndroidJniObject::fromString(id); QAndroidJniObject drawable = QAndroidJniObject::callStaticObjectMethod( "com/mozilla/vpn/PackageManagerHelper", "getAppIcon", "(Landroid/content/Context;Ljava/lang/String;)Landroid/graphics/drawable/" "Drawable;", activity.object(), jniString.object()); int width = drawable.callMethod("getIntrinsicWidth"); int height = drawable.callMethod("getIntrinsicHeight"); size->setHeight(height); size->setWidth(width); if (requestedSize.isValid()) { width = requestedSize.width(); height = requestedSize.height(); } QImage out = toImage(drawable, QRect(0, 0, width, height)); logger.log() << "Created image w" << out.size().width() << " h " << out.size().height(); return out; } QImage AndroidAppImageProvider::toImage(const QAndroidJniObject& bitmap) { QAndroidJniEnvironment env; AndroidBitmapInfo info; if (AndroidBitmap_getInfo(env, bitmap.object(), &info) != ANDROID_BITMAP_RESULT_SUCCESS) return QImage(); QImage::Format format; switch (info.format) { case ANDROID_BITMAP_FORMAT_RGBA_8888: format = QImage::Format_RGBA8888; break; case ANDROID_BITMAP_FORMAT_RGB_565: format = QImage::Format_RGB16; break; case ANDROID_BITMAP_FORMAT_RGBA_4444: format = QImage::Format_ARGB4444_Premultiplied; break; case ANDROID_BITMAP_FORMAT_A_8: format = QImage::Format_Alpha8; break; default: return QImage(); } void* pixels; if (AndroidBitmap_lockPixels(env, bitmap.object(), &pixels) != ANDROID_BITMAP_RESULT_SUCCESS) return QImage(); QImage image(info.width, info.height, format); if (info.stride == uint32_t(image.bytesPerLine())) { memcpy((void*)image.constBits(), pixels, info.stride * info.height); } else { uchar* bmpPtr = static_cast(pixels); const unsigned width = std::min(info.width, (uint)image.width()); const unsigned height = std::min(info.height, (uint)image.height()); for (unsigned y = 0; y < height; y++, bmpPtr += info.stride) memcpy((void*)image.constScanLine(y), bmpPtr, width); } if (AndroidBitmap_unlockPixels(env, bitmap.object()) != ANDROID_BITMAP_RESULT_SUCCESS) return QImage(); return image; } QAndroidJniObject AndroidAppImageProvider::createBitmap(int width, int height) { QAndroidJniObject config = QAndroidJniObject::getStaticObjectField( "android/graphics/Bitmap$Config", "ARGB_8888", "Landroid/graphics/Bitmap$Config;"); return QAndroidJniObject::callStaticObjectMethod( "android/graphics/Bitmap", "createBitmap", "(IILandroid/graphics/Bitmap$Config;)Landroid/graphics/Bitmap;", width, height, config.object()); } QImage AndroidAppImageProvider::toImage(const QAndroidJniObject& drawable, const QRect& bounds) { QAndroidJniObject bitmap = createBitmap(bounds.width(), bounds.height()); QAndroidJniObject canvas("android/graphics/Canvas", "(Landroid/graphics/Bitmap;)V", bitmap.object()); drawable.callMethod("setBounds", "(IIII)V", bounds.left(), bounds.top(), bounds.right(), bounds.bottom()); drawable.callMethod("draw", "(Landroid/graphics/Canvas;)V", canvas.object()); return toImage(bitmap); } mozilla-vpn-client-2.2.0/src/platforms/android/androidappimageprovider.h000066400000000000000000000016011404202232700265170ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef ANDROIDAPPIMAGEPROVIDER_H #define ANDROIDAPPIMAGEPROVIDER_H #include #include #include class AndroidAppImageProvider final : public QQuickImageProvider, public QObject { public: AndroidAppImageProvider(QObject* parent); ~AndroidAppImageProvider(); QImage requestImage(const QString& id, QSize* size, const QSize& requestedSize) override; QAndroidJniObject createBitmap(int width, int height); QImage toImage(const QAndroidJniObject& bitmap); QImage toImage(const QAndroidJniObject& drawable, const QRect& bounds); }; #endif // ANDROIDAPPIMAGEPROVIDER_H mozilla-vpn-client-2.2.0/src/platforms/android/androidapplistprovider.cpp000066400000000000000000000025411404202232700267470ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "androidapplistprovider.h" #include "leakdetector.h" #include #include #include #include #include #include #include "logger.h" #include "leakdetector.h" namespace { Logger logger(LOG_CONTROLLER, "AndroidAppListProvider"); } AndroidAppListProvider::AndroidAppListProvider(QObject* parent) : AppListProvider(parent) { MVPN_COUNT_CTOR(AndroidAppListProvider); } void AndroidAppListProvider::getApplicationList() { logger.log() << "Fetch Application list from Android"; QAndroidJniObject activity = QtAndroid::androidActivity(); Q_ASSERT(activity.isValid()); QAndroidJniObject str = QAndroidJniObject::callStaticObjectMethod( "com/mozilla/vpn/PackageManagerHelper", "getAllAppNames", "(Landroid/content/Context;)Ljava/lang/String;", activity.object()); QJsonDocument appList = QJsonDocument::fromJson(str.toString().toLocal8Bit()); QJsonObject listObj = appList.object(); QStringList keys = listObj.keys(); QMap out; foreach (auto key, keys) { out[key] = listObj[key].toString(); } emit newAppList(out); } mozilla-vpn-client-2.2.0/src/platforms/android/androidapplistprovider.h000066400000000000000000000010071404202232700264100ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef ANDROIDAPPLISTPROVIDER_H #define ANDROIDAPPLISTPROVIDER_H #include #include class AndroidAppListProvider : public AppListProvider { Q_OBJECT public: AndroidAppListProvider(QObject* parent); void getApplicationList() override; }; #endif // ANDROIDAPPLISTPROVIDER_H mozilla-vpn-client-2.2.0/src/platforms/android/androidauthenticationlistener.cpp000066400000000000000000000035141404202232700303060ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "androidauthenticationlistener.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "platforms/android/androidutils.h" #include "tasks/authenticate/desktopauthenticationlistener.h" #include #include #include namespace { Logger logger(LOG_ANDROID, "AndroidAuthenticationListener"); } AndroidAuthenticationListener::AndroidAuthenticationListener(QObject* parent) : AuthenticationListener(parent) { MVPN_COUNT_CTOR(AndroidAuthenticationListener); logger.log() << "Android authentication listener"; } AndroidAuthenticationListener::~AndroidAuthenticationListener() { MVPN_COUNT_DTOR(AndroidAuthenticationListener); } void AndroidAuthenticationListener::start(MozillaVPN* vpn, QUrl& url, QUrlQuery& query) { Q_UNUSED(vpn); logger.log() << "Authenticationlistener initialize"; url.setQuery(query); QAndroidJniObject activity = QtAndroid::androidActivity(); jboolean supported = QAndroidJniObject::callStaticMethod( "com/mozilla/vpn/PackageManagerHelper", "isWebViewSupported", "(Landroid/content/Context;)Z", activity.object()); if (supported) { AndroidUtils::instance()->startAuthentication(this, url); return; } DesktopAuthenticationListener* legacyAuth; legacyAuth = new DesktopAuthenticationListener(this); legacyAuth->start(vpn, url, query); connect(legacyAuth, &AuthenticationListener::completed, this, &AndroidAuthenticationListener::completed); connect(legacyAuth, &AuthenticationListener::failed, this, &AndroidAuthenticationListener::failed); } mozilla-vpn-client-2.2.0/src/platforms/android/androidauthenticationlistener.h000066400000000000000000000012551404202232700277530ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef ANDROIDAUTHENTICATIONLISTENER_H #define ANDROIDAUTHENTICATIONLISTENER_H #include "authenticationlistener.h" #include class AndroidAuthenticationListener final : public AuthenticationListener { Q_DISABLE_COPY_MOVE(AndroidAuthenticationListener) public: AndroidAuthenticationListener(QObject* parent); ~AndroidAuthenticationListener(); void start(MozillaVPN* vpn, QUrl& url, QUrlQuery& query) override; }; #endif // ANDROIDAUTHENTICATIONLISTENER_H mozilla-vpn-client-2.2.0/src/platforms/android/androidauthenticationview.qml000066400000000000000000000050631404202232700274430ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.0 import QtQuick.Controls 2.15 import QtWebView 1.15 import Mozilla.VPN 1.0 import "../../ui/components" import "../../ui/themes/themes.js" as Theme Item { Item { id: menuBar width: parent.width height: 56 // Ensure that menu is on top of possible scrollable // content. z: 1 Rectangle { id: menuBackground color: Theme.bgColor y: 0 width: parent.width height: 55 } VPNIconButton { id: iconButton onClicked: { VPNAndroidUtils.abortAuthentication(); mainStackView.pop(StackView.Immediate); } anchors.top: parent.top anchors.left: parent.left anchors.topMargin: Theme.windowMargin / 2 anchors.leftMargin: Theme.windowMargin / 2 accessibleName: qsTrId("vpn.main.back") Image { id: backImage source: "../../ui/resources/close-dark.svg" sourceSize.width: Theme.iconSize fillMode: Image.PreserveAspectFit anchors.centerIn: iconButton } } VPNBoldLabel { id: title anchors.top: menuBar.top anchors.centerIn: menuBar //% "Authentication" text: qsTrId("vpn.android.authentication") } Rectangle { color: "#0C0C0D0A" y: 55 width: parent.width height: 1 } } VPNAndroidWebView { url: VPNAndroidUtils.url height: parent.height - menuBar.height width: parent.width y: menuBar.height onPageStarted: { if (VPNAndroidUtils.maybeCompleteAuthentication(url)) { mainStackView.pop(StackView.Immediate); } } onFailure: { VPNAndroidUtils.abortAuthentication(); mainStackView.pop(StackView.Immediate) } } Component.onCompleted: VPNCloseEventHandler.addView(menuBar) Connections { target: VPNCloseEventHandler function onGoBack(item) { if (item === menuBar) { VPNAndroidUtils.abortAuthentication(); mainStackView.pop(StackView.Immediate); } } } } mozilla-vpn-client-2.2.0/src/platforms/android/androidcontroller.cpp000066400000000000000000000306351404202232700257100ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "androidcontroller.h" #include "ipaddressrange.h" #include "leakdetector.h" #include "logger.h" #include "models/device.h" #include "models/keys.h" #include "models/server.h" #include "settingsholder.h" #include #include #include #include #include #include #include #include #include #include #include #include // Binder Codes for VPNServiceBinder // See also - VPNServiceBinder.kt // Actions that are Requestable const int ACTION_ACTIVATE = 1; const int ACTION_DEACTIVATE = 2; const int ACTION_REGISTERLISTENER = 3; const int ACTION_REQUEST_STATISTIC = 4; const int ACTION_REQUEST_GET_LOG = 5; const int ACTION_REQUEST_CLEANUP_LOG = 6; const int ACTION_RESUME_ACTIVATE = 7; const int ACTION_ENABLE_START_ON_BOOT = 8; const int ACTION_SET_NOTIFICATION_TEXT = 9; const int ACTION_SET_NOTIFICATION_FALLBACK = 10; // Event Types that will be Dispatched after registration const int EVENT_INIT = 0; const int EVENT_CONNECTED = 1; const int EVENT_DISCONNECTED = 2; const int EVENT_STATISTIC_UPDATE = 3; const int EVENT_BACKEND_LOGS = 4; namespace { Logger logger(LOG_ANDROID, "AndroidController"); AndroidController* s_instance = nullptr; } // namespace AndroidController::AndroidController() : m_binder(this) { MVPN_COUNT_CTOR(AndroidController); Q_ASSERT(!s_instance); s_instance = this; } AndroidController::~AndroidController() { MVPN_COUNT_DTOR(AndroidController); Q_ASSERT(s_instance == this); s_instance = nullptr; } AndroidController* AndroidController::instance() { return s_instance; } void AndroidController::initialize(const Device* device, const Keys* keys) { logger.log() << "Initializing"; Q_UNUSED(device); Q_UNUSED(keys); // Hook in the native implementation for startActivityForResult into the JNI JNINativeMethod methods[]{{"startActivityForResult", "(Landroid/content/Intent;)V", reinterpret_cast(startActivityForResult)}}; QAndroidJniObject javaClass("org/mozilla/firefox/vpn/VPNPermissionHelper"); QAndroidJniEnvironment env; jclass objectClass = env->GetObjectClass(javaClass.object()); env->RegisterNatives(objectClass, methods, sizeof(methods) / sizeof(methods[0])); env->DeleteLocalRef(objectClass); auto appContext = QtAndroid::androidActivity().callObjectMethod( "getApplicationContext", "()Landroid/content/Context;"); QAndroidJniObject::callStaticMethod( "org/mozilla/firefox/vpn/VPNService", "startService", "(Landroid/content/Context;)V", appContext.object()); // Start the VPN Service (if not yet) and Bind to it QtAndroid::bindService( QAndroidIntent(appContext.object(), "org.mozilla.firefox.vpn.VPNService"), *this, QtAndroid::BindFlag::AutoCreate); } void AndroidController::enableStartAtBoot(bool enabled) { QAndroidParcel data; data.writeData(QByteArray(1, enabled)); m_serviceBinder.transact(ACTION_ENABLE_START_ON_BOOT, data, nullptr); } /* * Sets the current notification text that is shown */ void AndroidController::setNotificationText(const QString& title, const QString& message, int timerSec) { QJsonObject args; args["title"] = title; args["message"] = message; args["sec"] = timerSec; QJsonDocument doc(args); QAndroidParcel data; data.writeData(doc.toJson()); m_serviceBinder.transact(ACTION_SET_NOTIFICATION_TEXT, data, nullptr); } /* * Sets fallback Notification text that should be shown in case the VPN * switches into the Connected state without the app open * e.g via always-on vpn */ void AndroidController::setFallbackConnectedNotification() { QJsonObject args; args["title"] = qtTrId("vpn.main.productName"); //% "Ready for you to connect" //: Refers to the app - which is currently running the background and waiting args["message"] = qtTrId("vpn.android.notification.isIDLE"); QJsonDocument doc(args); QAndroidParcel data; data.writeData(doc.toJson()); m_serviceBinder.transact(ACTION_SET_NOTIFICATION_FALLBACK, data, nullptr); } void AndroidController::activate( const Server& server, const Device* device, const Keys* keys, const QList& allowedIPAddressRanges, const QList& vpnDisabledApps, Reason reason) { logger.log() << "Activation"; logger.log() << "Prompting for VPN permission"; auto appContext = QtAndroid::androidActivity().callObjectMethod( "getApplicationContext", "()Landroid/content/Context;"); QAndroidJniObject::callStaticMethod( "org/mozilla/firefox/vpn/VPNPermissionHelper", "startService", "(Landroid/content/Context;)V", appContext.object()); m_server = server; // Serialise arguments for the VPNService QJsonObject jDevice; jDevice["publicKey"] = device->publicKey(); jDevice["name"] = device->name(); jDevice["createdAt"] = device->createdAt().toMSecsSinceEpoch(); jDevice["ipv4Address"] = device->ipv4Address(); jDevice["ipv6Address"] = device->ipv6Address(); QJsonObject jKeys; jKeys["privateKey"] = keys->privateKey(); QJsonObject jServer; jServer["ipv4AddrIn"] = server.ipv4AddrIn(); jServer["ipv4Gateway"] = server.ipv4Gateway(); jServer["ipv6AddrIn"] = server.ipv6AddrIn(); jServer["ipv6Gateway"] = server.ipv6Gateway(); jServer["publicKey"] = server.publicKey(); jServer["port"] = (int)server.choosePort(); QJsonArray allowedIPs; foreach (auto item, allowedIPAddressRanges) { QJsonValue val; val = item.toString(); allowedIPs.append(val); } QJsonArray excludedApps; foreach (auto appID, vpnDisabledApps) { excludedApps.append(QJsonValue(appID)); } QJsonObject args; args["device"] = jDevice; args["keys"] = jKeys; args["server"] = jServer; args["reason"] = (int)reason; args["allowedIPs"] = allowedIPs; args["excludedApps"] = excludedApps; QJsonDocument doc(args); QAndroidParcel sendData; sendData.writeData(doc.toJson()); m_serviceBinder.transact(ACTION_ACTIVATE, sendData, nullptr); } // Activates the tunnel that is currently set // in the VPN Service void AndroidController::resume_activate() { QAndroidParcel nullData; m_serviceBinder.transact(ACTION_RESUME_ACTIVATE, nullData, nullptr); } void AndroidController::deactivate(Reason reason) { logger.log() << "deactivation"; if (reason != ReasonNone) { // Just show that we're disconnected // we're doing the actual disconnect once // the vpn-service has the new server ready in Action->Activate emit disconnected(); logger.log() << "deactivation skipped for Switching"; return; } QAndroidParcel nullData; m_serviceBinder.transact(ACTION_DEACTIVATE, nullData, nullptr); } void AndroidController::checkStatus() { logger.log() << "check status"; QAndroidParcel nullParcel; m_serviceBinder.transact(ACTION_REQUEST_STATISTIC, nullParcel, nullptr); } void AndroidController::getBackendLogs( std::function&& a_callback) { logger.log() << "get logs"; m_logCallback = std::move(a_callback); QAndroidParcel nullData, replyData; m_serviceBinder.transact(ACTION_REQUEST_GET_LOG, nullData, &replyData); } void AndroidController::cleanupBackendLogs() { logger.log() << "cleanup logs"; QAndroidParcel nullParcel; m_serviceBinder.transact(ACTION_REQUEST_CLEANUP_LOG, nullParcel, nullptr); } void AndroidController::onServiceConnected( const QString& name, const QAndroidBinder& serviceBinder) { logger.log() << "Server connected"; Q_UNUSED(name); m_serviceBinder = serviceBinder; // Send the Service our Binder to recive incoming Events QAndroidParcel binderParcel; binderParcel.writeBinder(m_binder); m_serviceBinder.transact(ACTION_REGISTERLISTENER, binderParcel, nullptr); // Sync the StartAtBoot Pref as it might have been changed // while the controler was not connected enableStartAtBoot(SettingsHolder::instance()->startAtBoot()); } void AndroidController::onServiceDisconnected(const QString& name) { logger.log() << "Server disconnected"; m_serviceConnected = false; Q_UNUSED(name); // TODO: Maybe restart? Or crash? } /** * @brief AndroidController::VPNBinder::onTransact * @param code the Event-Type we get From the VPNService See * @param data - Might contain UTF-8 JSON in case the Event has a payload * @param reply - always null * @param flags - unused * @return Returns true is the code was a valid Event Code */ bool AndroidController::VPNBinder::onTransact(int code, const QAndroidParcel& data, const QAndroidParcel& reply, QAndroidBinder::CallType flags) { Q_UNUSED(data); Q_UNUSED(reply); Q_UNUSED(flags); QJsonDocument doc; QString buffer; switch (code) { case EVENT_INIT: logger.log() << "Transact: init"; doc = QJsonDocument::fromJson(data.readData()); emit m_controller->initialized( true, doc.object()["connected"].toBool(), QDateTime::fromMSecsSinceEpoch( doc.object()["time"].toVariant().toLongLong())); // Pass a localised version of the Fallback string for the Notification m_controller->setFallbackConnectedNotification(); break; case EVENT_CONNECTED: logger.log() << "Transact: connected"; emit m_controller->connected(); break; case EVENT_DISCONNECTED: logger.log() << "Transact: disconnected"; emit m_controller->disconnected(); break; case EVENT_STATISTIC_UPDATE: logger.log() << "Transact:: update"; // Data is here a JSON String doc = QJsonDocument::fromJson(data.readData()); emit m_controller->statusUpdated(m_controller->m_server.ipv4Gateway(), doc.object()["totalTX"].toInt(), doc.object()["totalRX"].toInt()); break; case EVENT_BACKEND_LOGS: logger.log() << "Transact: backend logs"; buffer = readUTF8Parcel(data); if (m_controller->m_logCallback) { m_controller->m_logCallback(buffer); } break; default: logger.log() << "Transact: Invalid!"; break; } return true; } QString AndroidController::VPNBinder::readUTF8Parcel(QAndroidParcel data) { // 106 is the Code for UTF-8 return QTextCodec::codecForMib(106)->toUnicode(data.readData()); } const int ACTIVITY_RESULT_OK = 0xffffffff; /** * @brief Starts the Given intent in Context of the QTActivity * @param env * @param intent */ void AndroidController::startActivityForResult(JNIEnv* env, jobject /*thiz*/, jobject intent) { logger.log() << "start activity"; Q_UNUSED(env); QtAndroid::startActivity(intent, 1337, [](int receiverRequestCode, int resultCode, const QAndroidJniObject& data) { // Currently this function just used in // VPNService.kt::checkPersmissions. So the result // we're getting is if the User gave us the // Vpn.bind permission. In case of NO we should // abort. Q_UNUSED(receiverRequestCode); Q_UNUSED(data); AndroidController* controller = AndroidController::instance(); if (!controller) { return; } if (resultCode == ACTIVITY_RESULT_OK) { logger.log() << "VPN PROMPT RESULT - Accepted"; controller->resume_activate(); return; } // If the request got rejected abort the current // connection. logger.log() << "VPN PROMPT RESULT - Rejected"; emit controller->disconnected(); }); return; } mozilla-vpn-client-2.2.0/src/platforms/android/androidcontroller.h000066400000000000000000000044241404202232700253520ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef ANDROIDCONTROLLER_H #define ANDROIDCONTROLLER_H #include "controllerimpl.h" #include #include class AndroidController final : public ControllerImpl, public QAndroidServiceConnection { Q_DISABLE_COPY_MOVE(AndroidController) public: AndroidController(); static AndroidController* instance(); ~AndroidController(); // from ControllerImpl void initialize(const Device* device, const Keys* keys) override; void activate(const Server& data, const Device* device, const Keys* keys, const QList& allowedIPAddressRanges, const QList& vpnDisabledApps, Reason reason) override; void resume_activate(); void deactivate(Reason reason) override; void checkStatus() override; void enableStartAtBoot(bool enabled); void setNotificationText(const QString& title, const QString& message, int timerSec); void setFallbackConnectedNotification(); void getBackendLogs(std::function&& callback) override; void cleanupBackendLogs() override; // from QAndroidServiceConnection void onServiceConnected(const QString& name, const QAndroidBinder& serviceBinder) override; void onServiceDisconnected(const QString& name) override; private: Server m_server; bool m_serviceConnected = false; std::function m_logCallback; QAndroidBinder m_serviceBinder; class VPNBinder : public QAndroidBinder { public: VPNBinder(AndroidController* controller) : m_controller(controller) {} bool onTransact(int code, const QAndroidParcel& data, const QAndroidParcel& reply, QAndroidBinder::CallType flags) override; QString readUTF8Parcel(QAndroidParcel data); private: AndroidController* m_controller = nullptr; }; VPNBinder m_binder; static void startActivityForResult(JNIEnv* env, jobject /*thiz*/, jobject intent); }; #endif // ANDROIDCONTROLLER_H mozilla-vpn-client-2.2.0/src/platforms/android/androiddatamigration.cpp000066400000000000000000000174451404202232700263540ustar00rootroot00000000000000 /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "androiddatamigration.h" #include "logger.h" #include "mozillavpn.h" #include "settingsholder.h" #include "tasks/accountandservers/taskaccountandservers.h" #include "androidsharedprefs.h" #include #include #include #include #include #include #include #include namespace { Logger logger(LOG_ANDROID, "AndroidDataMigration"); #ifdef QT_DEBUG const QString MIGRATION_FILE = "org.mozilla.firefox.vpn.debug_preferences.xml"; #else const QString MIGRATION_FILE = "org.mozilla.firefox.vpn_preferences.xml"; #endif } // namespace // static void AndroidDataMigration::migrate() { logger.log() << "Android Data Migration -- Start"; auto files = AndroidSharedPrefs::GetPrefFiles(); if (!files.contains(MIGRATION_FILE)) { logger.log() << "Migration" << MIGRATION_FILE << "was not file found - skip"; logger.log() << "Migration files: " << files; return; }; importDeviceInfo(); importUserInfo(); importLoginToken(); importServerList(); } void AndroidDataMigration::importDeviceInfo() { QVariant prefValue = AndroidSharedPrefs::GetValue(MIGRATION_FILE, "pref_current_device"); if (!prefValue.isValid()) { logger.log() << "Failed to read pref_current_device - abort"; return; } QString privateKey; QString pubKey; QString name; importDeviceInfoInternal(prefValue.toByteArray(), privateKey, pubKey, name); if (privateKey.isEmpty()) { logger.log() << "Invalid dvice info data"; return; } MozillaVPN::instance()->deviceAdded(name, pubKey, privateKey); logger.log() << "pref_current_device value was migrated"; } void AndroidDataMigration::importDeviceInfoInternal(const QByteArray& json, QString& privateKey, QString& publicKey, QString& name) { QJsonDocument prefValueJSON = QJsonDocument::fromJson(json); auto deviceInfo = prefValueJSON.object(); privateKey = deviceInfo["privateKeyBase64"].toString(); publicKey = deviceInfo["device"].toObject()["pubkey"].toString(); name = deviceInfo["device"].toObject()["name"].toString(); } void AndroidDataMigration::importUserInfo() { // Import User Information QVariant prefValue = AndroidSharedPrefs::GetValue(MIGRATION_FILE, "user_info"); if (!prefValue.isValid()) { logger.log() << "Failed to read user_info"; return; } QByteArray json = importUserInfoInternal(prefValue.toByteArray()); if (json.isEmpty()) { logger.log() << "Invalid user data"; return; } MozillaVPN::instance()->accountChecked(json); logger.log() << "user_info value was imported"; } QByteArray AndroidDataMigration::importUserInfoInternal( const QByteArray& json) { QJsonDocument prefValueJSON = QJsonDocument::fromJson(json); if (!prefValueJSON.isObject()) { return QByteArray(); } // We're having here {latestUpdateTime: , user: } QJsonObject obj = prefValueJSON.object(); if (!obj.contains("user")) { return QByteArray(); } QJsonValue userValue = obj["user"]; if (!userValue.isObject()) { return QByteArray(); } return QJsonDocument(userValue.toObject()).toJson(QJsonDocument::Compact); } void AndroidDataMigration::importLoginToken() { QVariant loginToken = AndroidSharedPrefs::GetValue(MIGRATION_FILE, "auth_token"); if (!loginToken.isValid()) { logger.log() << "Failed to read auth_token"; return; } MozillaVPN::instance()->setToken(loginToken.toString()); logger.log() << "auth_token value was imported"; } void AndroidDataMigration::importServerList() { QVariant prefValue = AndroidSharedPrefs::GetValue(MIGRATION_FILE, "pref_servers"); if (!prefValue.isValid()) { logger.log() << "Failed to read pref_servers - exiting"; return; } // We're having here {latestUpdateTime: , servers: [] } // Server: /* { "city": {"code": "mel","latitude": -37.815018,"longitude": 144.946014,"name": "Melbourne"}, "country": {"code": "au","name": "Australia"}, "server": { "hostname": "au3-wireguard", "include_in_country": true, "ipv4_addr_in": "103.231.88.2", "ipv4_gateway": "10.64.0.1", "ipv6_gateway": "fc00:bbbb:bbbb:bb01::1", "port_ranges": [[53,53],[4000,33433],[33565,51820],[52000,60000]], "public_key": "Rzh64qPcg8W8klJq0H4EZdVCH7iaPuQ9falc99GTgRA\u003d", "weight": 3 } */ QByteArray json = importServerListInternal(prefValue.toByteArray()); if (json.isEmpty()) { logger.log() << "Invalid server data"; return; } logger.log() << "Import JSON \n" << json; bool ok = MozillaVPN::instance()->setServerList(json); if (!ok) { logger.log() << "pref_servers value was rejected"; return; } logger.log() << "pref_servers value was imported"; } QByteArray AndroidDataMigration::importServerListInternal( const QByteArray& json) { QJsonDocument serverListDoc = QJsonDocument::fromJson(json); QJsonArray serverList = serverListDoc.object()["servers"].toArray(); if (serverList.isEmpty()) { return QByteArray(); } struct City { QString m_name; QString m_code; QJsonArray m_servers; }; struct Country { QString m_name; QString m_code; QMap m_cities; }; QMap countries; for (const QJsonValue& serverValue : serverList) { QJsonObject server = serverValue.toObject(); QJsonObject countryObj = server["country"].toObject(); QJsonObject cityObj = server["city"].toObject(); QJsonObject serverObj = server["server"].toObject(); QString countryCode = countryObj["code"].toString(); if (countryCode.isEmpty()) return QByteArray(); if (!countries.contains(countryCode)) { QString countryName = countryObj["name"].toString(); if (countryName.isEmpty()) return QByteArray(); countries.insert(countryCode, Country{countryName, countryCode, QMap()}); } Country& country = countries[countryCode]; QString cityCode = cityObj["code"].toString(); if (cityCode.isEmpty()) return QByteArray(); if (!country.m_cities.contains(cityCode)) { QString cityName = cityObj["name"].toString(); if (cityName.isEmpty()) return QByteArray(); country.m_cities.insert(cityCode, City{cityName, cityCode, QJsonArray()}); } City& city = country.m_cities[cityCode]; city.m_servers.append(serverObj); } // transform this to {countries: [{name,code,cities:[ // {name,code,servers:[server]} ]} ]} QJsonArray countryArray; QMapIterator countryIterator(countries); while (countryIterator.hasNext()) { countryIterator.next(); const Country& country = countryIterator.value(); QJsonArray citiesArray; QMapIterator cityIterator(country.m_cities); while (cityIterator.hasNext()) { cityIterator.next(); const City& city = cityIterator.value(); QJsonObject cityObj; cityObj.insert("name", city.m_name); cityObj.insert("code", city.m_code); cityObj.insert("servers", city.m_servers); citiesArray.append(cityObj); } QJsonObject countryObj; countryObj.insert("name", country.m_name); countryObj.insert("code", country.m_code); countryObj.insert("cities", citiesArray); countryArray.append(countryObj); } QJsonObject out; out.insert("countries", countryArray); return QJsonDocument(out).toJson(QJsonDocument::Compact); } mozilla-vpn-client-2.2.0/src/platforms/android/androiddatamigration.h000066400000000000000000000016221404202232700260070ustar00rootroot00000000000000 /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef ANDROIDDATAMIGRATION_H #define ANDROIDDATAMIGRATION_H #include class AndroidDataMigration final { public: static void migrate(); // For unit-tests and internals static void importDeviceInfoInternal(const QByteArray& json, QString& privateKey, QString& publicKey, QString& name); static QByteArray importUserInfoInternal(const QByteArray& json); static QByteArray importServerListInternal(const QByteArray& json); private: static void importDeviceInfo(); static void importUserInfo(); static void importLoginToken(); static void importServerList(); }; #endif // ANDROIDDATAMIGRATION_H mozilla-vpn-client-2.2.0/src/platforms/android/androidnotificationhandler.cpp000066400000000000000000000016771404202232700275550ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "platforms/android/androidnotificationhandler.h" #include "leakdetector.h" #include "logger.h" #include "androidcontroller.h" namespace { Logger logger(LOG_ANDROID, "AndroidNotificationHandler"); } AndroidNotificationHandler::AndroidNotificationHandler(QObject* parent) : NotificationHandler(parent) { MVPN_COUNT_CTOR(AndroidNotificationHandler); } AndroidNotificationHandler::~AndroidNotificationHandler() { MVPN_COUNT_DTOR(AndroidNotificationHandler); } void AndroidNotificationHandler::notify(const QString& title, const QString& message, int timerSec) { logger.log() << "Send notification - " << message; AndroidController::instance()->setNotificationText(title, message, timerSec); } mozilla-vpn-client-2.2.0/src/platforms/android/androidnotificationhandler.h000066400000000000000000000012741404202232700272130ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef ANDROIDNOTIFICATIONHANDLER_H #define ANDROIDNOTIFICATIONHANDLER_H #include "notificationhandler.h" #include class AndroidNotificationHandler final : public NotificationHandler { Q_DISABLE_COPY_MOVE(AndroidNotificationHandler) public: AndroidNotificationHandler(QObject* parent); ~AndroidNotificationHandler(); protected: void notify(const QString& title, const QString& message, int timerSec) override; }; #endif // ANDROIDNOTIFICATIONHANDLER_H mozilla-vpn-client-2.2.0/src/platforms/android/androidsharedprefs.cpp000066400000000000000000000054621404202232700260330ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "androidsharedprefs.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "platforms/android/androidutils.h" #include "QDir" #include "QVariant" #include namespace { Logger logger(LOG_ANDROID, "AndroidSharedPrefs"); #ifdef QT_DEBUG const QString SHARED_PREF_FOLDER = "/data/data/org.mozilla.firefox.vpn.debug/shared_prefs"; #else const QString SHARED_PREF_FOLDER = "/data/data/org.mozilla.firefox.vpn/shared_prefs"; #endif } // namespace AndroidSharedPrefs::AndroidSharedPrefs() { MVPN_COUNT_CTOR(AndroidSharedPrefs); } AndroidSharedPrefs::~AndroidSharedPrefs() { MVPN_COUNT_DTOR(AndroidSharedPrefs); } QList AndroidSharedPrefs::GetPrefFiles() { QDir prefFolder(SHARED_PREF_FOLDER); return prefFolder.entryList(); } /** * @brief AndroidSharedPrefs::GetValue * @param fileName - The Shared Prefrence file to open e.g "example.xml" * @param prefKey - The prefrence key to get * @return Returns the value of the Pref, QVariant::Invalid on failure */ QVariant AndroidSharedPrefs::GetValue(const QString& fileName, const QString& prefKey) { QDir prefFolder(SHARED_PREF_FOLDER); if (!prefFolder.exists()) { logger.log() << "shared_prefs folder not found: " << prefFolder.path(); return QVariant::Invalid; } QFile prefFile(prefFolder.absoluteFilePath(fileName)); if (!prefFile.exists()) { logger.log() << "pref file not found: " << prefFile.fileName(); return QVariant::Invalid; } if (!prefFile.open(QIODevice::ReadOnly)) { logger.log() << "failed to open pref file: " << prefFile.fileName(); return QVariant::Invalid; } /* Android shared prefrences format: * * value * .. * */ QDomDocument xmlBOM; xmlBOM.setContent(&prefFile); QDomElement mapNode = xmlBOM.documentElement(); QDomNodeList stringElementList = mapNode.childNodes(); for (int x = 0; x < stringElementList.count(); x++) { QDomNode stringNode = stringElementList.at(x); // value auto attribName = stringNode.attributes().namedItem("name"); if (attribName.nodeValue() == prefKey) { QString val = stringNode.nodeValue(); auto textNode = stringNode.firstChild(); if (!textNode.isText()) { logger.log() << "Non TextNode Value? " << textNode.nodeValue(); } QString value = textNode.nodeValue(); prefFile.close(); return QVariant(value); } } prefFile.close(); logger.log() << "Key was not found in the Prefrences file " << prefKey; return QVariant::Invalid; } mozilla-vpn-client-2.2.0/src/platforms/android/androidsharedprefs.h000066400000000000000000000012561404202232700254750ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef ANDROIDSHAREDPREFS_H #define ANDROIDSHAREDPREFS_H #include "authenticationlistener.h" #include class AndroidSharedPrefs final { Q_DISABLE_COPY_MOVE(AndroidSharedPrefs) public: AndroidSharedPrefs(); ~AndroidSharedPrefs(); /* * Returns all the Shared Prefrences Files that are available */ static QList GetPrefFiles(); static QVariant GetValue(const QString& filenName, const QString& prefKey); }; #endif // ANDROIDSHAREDPREFS_H mozilla-vpn-client-2.2.0/src/platforms/android/androidstartatbootwatcher.cpp000066400000000000000000000015551404202232700274500ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "androidstartatbootwatcher.h" #include "logger.h" #include "androidcontroller.h" #include "platforms/android/androidutils.h" #include #include #include #include namespace { Logger logger(LOG_ANDROID, "AndroidStartAtBootWatcher"); } AndroidStartAtBootWatcher::AndroidStartAtBootWatcher(bool startAtBoot) { startAtBootChanged(startAtBoot); } void AndroidStartAtBootWatcher::startAtBootChanged(bool startAtBoot) { logger.log() << "StartAtBoot changed:" << startAtBoot; if (AndroidController::instance() != nullptr) { AndroidController::instance()->enableStartAtBoot(startAtBoot); } } mozilla-vpn-client-2.2.0/src/platforms/android/androidstartatbootwatcher.h000066400000000000000000000007761404202232700271210ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef ANDROIDSTARTATBOOTWATCHER_H #define ANDROIDSTARTATBOOTWATCHER_H #include class AndroidStartAtBootWatcher final : public QObject { public: AndroidStartAtBootWatcher(bool startAtBoot); public slots: void startAtBootChanged(bool value); }; #endif // ANDROIDSTARTATBOOTWATCHER_H mozilla-vpn-client-2.2.0/src/platforms/android/androidutils.cpp000066400000000000000000000065041404202232700246630ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "androidutils.h" #include "constants.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "platforms/android/androidauthenticationlistener.h" #include "qmlengineholder.h" #include #include #include #include #include #include #include namespace { AndroidUtils* s_instance = nullptr; Logger logger(LOG_ANDROID, "AndroidUtils"); } // namespace // static QString AndroidUtils::GetDeviceName() { QAndroidJniEnvironment env; jclass BUILD = env->FindClass("android/os/Build"); jfieldID model = env->GetStaticFieldID(BUILD, "MODEL", "Ljava/lang/String;"); jstring value = (jstring)env->GetStaticObjectField(BUILD, model); if (!value) { return QString("Android Device"); } const char* buffer = env->GetStringUTFChars(value, nullptr); if (!buffer) { return QString("Android Device"); } QString res = QString(buffer); env->ReleaseStringUTFChars(value, buffer); return res; }; // Static bool AndroidUtils::canEnableStartOnBoot() { // On Nougat(v24) Always On VPN was introduced // and starting VPN on boot forbidden. return QtAndroid::androidSdkVersion() < 24; }; // static AndroidUtils* AndroidUtils::instance() { if (!s_instance) { Q_ASSERT(qApp); s_instance = new AndroidUtils(qApp); } return s_instance; } AndroidUtils::AndroidUtils(QObject* parent) : QObject(parent) { MVPN_COUNT_CTOR(AndroidUtils); Q_ASSERT(!s_instance); s_instance = this; } AndroidUtils::~AndroidUtils() { MVPN_COUNT_DTOR(AndroidUtils); Q_ASSERT(s_instance == this); s_instance = nullptr; } void AndroidUtils::startAuthentication(AuthenticationListener* listener, const QUrl& url) { logger.log() << "Open the authentication view"; Q_ASSERT(!m_listener); m_listener = listener; connect(listener, &QObject::destroyed, this, &AndroidUtils::resetListener); m_url = url; emit urlChanged(); emit MozillaVPN::instance()->loadAndroidAuthenticationView(); } bool AndroidUtils::maybeCompleteAuthentication(const QString& url) { logger.log() << "Maybe complete authentication - url:" << url; Q_ASSERT(m_listener); logger.log() << "AndroidWebView is about to load" << url; QString apiUrl = Constants::API_URL; if (!url.startsWith(apiUrl)) { return false; } QUrl loadingUrl(url); if (loadingUrl.path() == "/vpn/client/login/success") { QUrlQuery query(loadingUrl.query()); if (!query.hasQueryItem("code")) { emit m_listener->failed(ErrorHandler::RemoteServiceError); m_listener = nullptr; return true; } QString code = query.queryItemValue("code"); emit m_listener->completed(code); m_listener = nullptr; return true; } if (loadingUrl.path() == "/vpn/client/login/error") { emit m_listener->failed(ErrorHandler::AuthenticationError); m_listener = nullptr; return true; } return false; } void AndroidUtils::abortAuthentication() { logger.log() << "Aborting authentication"; Q_ASSERT(m_listener); emit m_listener->abortedByUser(); m_listener = nullptr; } mozilla-vpn-client-2.2.0/src/platforms/android/androidutils.h000066400000000000000000000020751404202232700243270ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef ANDROIDUTILS_H #define ANDROIDUTILS_H #include #include #include class AuthenticationListener; class AndroidUtils final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(AndroidUtils) Q_PROPERTY(QUrl url READ url NOTIFY urlChanged) public: static QString GetDeviceName(); static bool canEnableStartOnBoot(); static AndroidUtils* instance(); void startAuthentication(AuthenticationListener* listener, const QUrl& url); const QUrl& url() const { return m_url; } Q_INVOKABLE void abortAuthentication(); Q_INVOKABLE bool maybeCompleteAuthentication(const QString& url); signals: void urlChanged(); private: AndroidUtils(QObject* parent); ~AndroidUtils(); void resetListener() { m_listener = nullptr; } private: QUrl m_url; AuthenticationListener* m_listener = nullptr; }; #endif // ANDROIDUTILS_H mozilla-vpn-client-2.2.0/src/platforms/android/androidwebview.cpp000066400000000000000000000162751404202232700252010ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "androidwebview.h" #include "errorhandler.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "networkmanager.h" #include #include #include #include #include #include #include namespace { Logger logger(LOG_ANDROID, "AndroidWebView"); bool s_methodsInitialized = false; AndroidWebView* s_instance = nullptr; } // namespace // static void AndroidWebView::dispatchToMainThread(std::function callback) { QTimer* timer = new QTimer(); timer->moveToThread(qApp->thread()); timer->setSingleShot(true); QObject::connect(timer, &QTimer::timeout, [=]() { callback(); timer->deleteLater(); }); QMetaObject::invokeMethod(timer, "start", Qt::QueuedConnection); } // static void AndroidWebView::onPageStarted(JNIEnv* env, jobject thiz, jstring url, jobject icon) { Q_UNUSED(env); Q_UNUSED(thiz); Q_UNUSED(icon); QString pageUrl = env->GetStringUTFChars(url, 0); logger.log() << "Page started:" << pageUrl; dispatchToMainThread([pageUrl] { Q_ASSERT(s_instance); emit s_instance->pageStarted(pageUrl); }); } // static void AndroidWebView::onError(JNIEnv* env, jobject thiz, jint errorCode, jstring description, jstring url) { Q_UNUSED(env); Q_UNUSED(thiz); Q_UNUSED(errorCode); Q_UNUSED(description); Q_UNUSED(url); QString errorDescription = env->GetStringUTFChars(description, 0); logger.log() << "Network failure:" << errorDescription; dispatchToMainThread([errorDescription] { Q_ASSERT(s_instance); s_instance->propagateError(ErrorHandler::NoConnectionError); }); } AndroidWebView::AndroidWebView(QQuickItem* parent) : QQuickItem(parent) { MVPN_COUNT_CTOR(AndroidWebView); logger.log() << "AndroidWebView created"; // We do not support multiple android webviews. No needs for now. Q_ASSERT(!s_instance); s_instance = this; if (!s_methodsInitialized) { s_methodsInitialized = true; QAndroidJniEnvironment env; jclass javaClass = env.findClass("org/mozilla/firefox/vpn/VPNWebView"); if (!javaClass) { propagateError(ErrorHandler::RemoteServiceError); return; } JNINativeMethod methods[]{ {"nativeOnPageStarted", "(Ljava/lang/String;Landroid/graphics/Bitmap;)V", reinterpret_cast(onPageStarted)}, {"nativeOnError", "(ILjava/lang/String;Ljava/lang/String;)V", reinterpret_cast(onError)}, }; env->RegisterNatives(javaClass, methods, sizeof(methods) / sizeof(methods[0])); } QString userAgentStr = NetworkManager::userAgent(); QAndroidJniObject userAgent = QAndroidJniObject::fromString(userAgentStr); Q_ASSERT(userAgent.isValid()); QAndroidJniObject activity = QtAndroid::androidActivity(); Q_ASSERT(activity.isValid()); m_object = QAndroidJniObject("org/mozilla/firefox/vpn/VPNWebView", "(Landroid/app/Activity;Ljava/lang/String;)V", activity.object(), userAgent.object()); if (!m_object.isValid()) { propagateError(ErrorHandler::UnrecoverableError); return; } m_webView = m_object.callObjectMethod("getWebView", "()Landroid/webkit/WebView;"); if (!m_webView.isValid()) { propagateError(ErrorHandler::UnrecoverableError); return; } m_window = QWindow::fromWinId(reinterpret_cast(m_webView.object())); connect(this, &QQuickItem::windowChanged, this, &AndroidWebView::onWindowChanged); connect(this, &QQuickItem::visibleChanged, this, &AndroidWebView::onVisibleChanged); } AndroidWebView::~AndroidWebView() { MVPN_COUNT_DTOR(AndroidWebView); logger.log() << "AndroidWebView destroyed"; Q_ASSERT(s_instance == this); s_instance = nullptr; if (m_window) { m_window->setVisible(false); m_window->setParent(0); delete m_window; } if (m_object.isValid()) { m_object.callMethod("destroy"); } } QUrl AndroidWebView::url() const { if (!m_object.isValid()) { logger.log() << "Invalid object. Returning an empty URL"; return QUrl(); } return QUrl::fromUserInput( m_object.callObjectMethod("getUrl").toString()); } void AndroidWebView::setUrl(const QUrl& url) { logger.log() << "Set URL:" << url.toString(); if (!m_object.isValid()) { logger.log() << "Invalid object. Failed the loading."; return; } QAndroidJniObject urlString = QAndroidJniObject::fromString(url.toString()); m_object.callMethod("setUrl", "(Ljava/lang/String;)V", urlString.object()); emit urlChanged(); } void AndroidWebView::componentComplete() { if (m_window) { m_window->setVisibility(QWindow::Windowed); } } void AndroidWebView::updatePolish() { QSize itemSize = QSize(width(), height()); if (!itemSize.isValid()) { return; } QQuickWindow* w = window(); if (!w) { return; } QRect itemGeometry = mapRectToScene(QRect(QPoint(0, 0), itemSize)).toRect(); const QPoint& tl = w->mapToGlobal(itemGeometry.topLeft()); QWindow* rw = QQuickRenderControl::renderWindowFor(w); m_window->setGeometry(rw ? QRect(rw->mapFromGlobal(tl), itemSize) : itemGeometry); m_window->setVisible(isVisible()); } void AndroidWebView::geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) { QQuickItem::geometryChanged(newGeometry, oldGeometry); if (newGeometry.isValid()) { polish(); } } void AndroidWebView::propagateError(ErrorHandler::ErrorType error) { MozillaVPN::instance()->errorHandle(error); emit failure(); } void AndroidWebView::onWindowChanged(QQuickWindow* window) { logger.log() << "window changed"; QQuickWindow* oldParent = qobject_cast(m_window->parent()); if (oldParent) { oldParent->disconnect(this); } if (!window) { m_window->setParent(nullptr); return; } // Check if there's an actual native window available. QWindow* rw = QQuickRenderControl::renderWindowFor(window); if (!rw) { rw = window; } connect(rw, &QWindow::widthChanged, this, &AndroidWebView::polish); connect(rw, &QWindow::heightChanged, this, &AndroidWebView::polish); connect(rw, &QWindow::xChanged, this, &AndroidWebView::polish); connect(rw, &QWindow::yChanged, this, &AndroidWebView::polish); connect(rw, &QWindow::visibleChanged, this, [this](bool visible) { m_window->setVisible(visible); }); connect(window, &QQuickWindow::sceneGraphInitialized, this, &AndroidWebView::polish); connect(window, &QQuickWindow::sceneGraphInvalidated, this, &AndroidWebView::invalidateSceneGraph); m_window->setParent(rw); } void AndroidWebView::onVisibleChanged() { logger.log() << "visible changed"; m_window->setVisible(isVisible()); } void AndroidWebView::invalidateSceneGraph() { if (m_window) { m_window->setVisible(false); } } mozilla-vpn-client-2.2.0/src/platforms/android/androidwebview.h000066400000000000000000000030521404202232700246330ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef ANDROIDWEBVIEW_H #define ANDROIDWEBVIEW_H #include "errorhandler.h" #include #include #include class QWindow; class AndroidWebView : public QQuickItem { Q_OBJECT Q_DISABLE_COPY_MOVE(AndroidWebView) Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged) QML_ELEMENT public: AndroidWebView(QQuickItem* parent = 0); virtual ~AndroidWebView(); QUrl url() const; void setUrl(const QUrl& url); protected: void componentComplete() override; void updatePolish() override; void geometryChanged(const QRectF& newGeometry, const QRectF& oldGeometry) override; signals: void urlChanged(); void pageStarted(const QString& url); void failure(); private slots: void onWindowChanged(QQuickWindow* window); void onVisibleChanged(); void invalidateSceneGraph(); private: static void onPageStarted(JNIEnv* env, jobject thiz, jstring url, jobject icon); static void onError(JNIEnv* env, jobject thiz, jint errorCode, jstring description, jstring url); static void dispatchToMainThread(std::function callback); void propagateError(ErrorHandler::ErrorType error); private: QWindow* m_window = nullptr; QAndroidJniObject m_object; QAndroidJniObject m_webView; }; #endif // ANDROIDWEBVIEW_H mozilla-vpn-client-2.2.0/src/platforms/dummy/000077500000000000000000000000001404202232700211645ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/platforms/dummy/dummyappimageprovider.cpp000066400000000000000000000022471404202232700263070ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "dummyappimageprovider.h" #include "logger.h" #include "leakdetector.h" namespace { Logger logger(LOG_CONTROLLER, "DummyAppImageProvider"); } DummyAppImageProvider::DummyAppImageProvider(QObject* parent) : QQuickImageProvider(QQuickImageProvider::Pixmap), QObject(parent) { MVPN_COUNT_CTOR(DummyAppImageProvider); } DummyAppImageProvider::~DummyAppImageProvider() { MVPN_COUNT_DTOR(DummyAppImageProvider); } // Returns an example image for any provided APPID (a red square) QPixmap DummyAppImageProvider::requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) { logger.log() << "Request Image for " << id; int width = 50; int height = 50; if (size) *size = QSize(width, height); QPixmap pixmap(requestedSize.width() > 0 ? requestedSize.width() : width, requestedSize.height() > 0 ? requestedSize.height() : height); pixmap.fill(QColor("red").rgba()); return pixmap; } mozilla-vpn-client-2.2.0/src/platforms/dummy/dummyappimageprovider.h000066400000000000000000000011711404202232700257470ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef DUMMYAPPIMAGEPROVIDER_H #define DUMMYAPPIMAGEPROVIDER_H #include #include class DummyAppImageProvider : public QQuickImageProvider, public QObject { public: DummyAppImageProvider(QObject* parent); ~DummyAppImageProvider(); QPixmap requestPixmap(const QString& id, QSize* size, const QSize& requestedSize) override; }; #endif // DUMMYAPPIMAGEPROVIDER_H mozilla-vpn-client-2.2.0/src/platforms/dummy/dummyapplistprovider.cpp000066400000000000000000000017311404202232700261750ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "dummyapplistprovider.h" #include "leakdetector.h" DummyAppListProvider::DummyAppListProvider(QObject* parent) : AppListProvider(parent) { MVPN_COUNT_CTOR(DummyAppListProvider); } DummyAppListProvider::~DummyAppListProvider() { MVPN_COUNT_DTOR(DummyAppListProvider); } void DummyAppListProvider::getApplicationList() { QMap appList; appList["com.example.one"] = "Example App 1"; appList["com.example.two"] = "Example App 2"; appList["org.example.one"] = "Example App 3"; appList["org.example.two"] = "Example App 4"; appList["com.example.a"] = "Example App 5"; appList["com.example.b"] = "Example App 6"; appList["org.example.c"] = "Example App 7"; appList["org.example.d"] = "Example App 8"; emit newAppList(appList); } mozilla-vpn-client-2.2.0/src/platforms/dummy/dummyapplistprovider.h000066400000000000000000000010301404202232700256320ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef DUMMYAPPLISTPROVIDER_H #define DUMMYAPPLISTPROVIDER_H #include "applistprovider.h" #include class DummyAppListProvider : public AppListProvider { Q_OBJECT public: DummyAppListProvider(QObject* parent); ~DummyAppListProvider(); void getApplicationList() override; }; #endif // DUMMYAPPLISTPROVIDER_H mozilla-vpn-client-2.2.0/src/platforms/dummy/dummycontroller.cpp000066400000000000000000000031231404202232700251260ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "dummycontroller.h" #include "leakdetector.h" #include "logger.h" #include "models/server.h" #include namespace { Logger logger(LOG_CONTROLLER, "DummyController"); } DummyController::DummyController() { MVPN_COUNT_CTOR(DummyController); } DummyController::~DummyController() { MVPN_COUNT_DTOR(DummyController); } void DummyController::activate( const Server& server, const Device* device, const Keys* keys, const QList& allowedIPAddressRanges, const QList& vpnDisabledApps, Reason reason) { Q_UNUSED(device); Q_UNUSED(keys); Q_UNUSED(allowedIPAddressRanges); Q_UNUSED(reason); Q_UNUSED(vpnDisabledApps); logger.log() << "DummyController activated" << server.hostname(); emit connected(); } void DummyController::deactivate(Reason reason) { Q_UNUSED(reason); logger.log() << "DummyController deactivated"; emit disconnected(); } void DummyController::checkStatus() { m_txBytes += QRandomGenerator::global()->generate() % 100000; m_rxBytes += QRandomGenerator::global()->generate() % 100000; emit statusUpdated("127.0.0.1", m_txBytes, m_rxBytes); } void DummyController::getBackendLogs( std::function&& a_callback) { std::function callback = std::move(a_callback); callback("DummyController is always happy"); } void DummyController::cleanupBackendLogs() {} mozilla-vpn-client-2.2.0/src/platforms/dummy/dummycontroller.h000066400000000000000000000022041404202232700245720ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef DUMMYCONTROLLER_H #define DUMMYCONTROLLER_H #include "controllerimpl.h" #include #include class DummyController final : public ControllerImpl { Q_DISABLE_COPY_MOVE(DummyController) public: DummyController(); ~DummyController(); void initialize(const Device* device, const Keys* keys) override { Q_UNUSED(device); Q_UNUSED(keys); emit initialized(true, false, QDateTime()); } void activate(const Server& data, const Device* device, const Keys* keys, const QList& allowedIPAddressRanges, const QList& vpnDisabledApps, Reason reason) override; void deactivate(Reason reason) override; void checkStatus() override; void getBackendLogs(std::function&& callback) override; void cleanupBackendLogs() override; private: int64_t m_txBytes = 0; int64_t m_rxBytes = 0; }; #endif // DUMMYCONTROLLER_H mozilla-vpn-client-2.2.0/src/platforms/dummy/dummycryptosettings.cpp000066400000000000000000000007531404202232700260520ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "cryptosettings.h" void CryptoSettings::resetKey() {} bool CryptoSettings::getKey(uint8_t key[CRYPTO_SETTINGS_KEY_SIZE]) { Q_UNUSED(key); return false; } // static CryptoSettings::Version CryptoSettings::getSupportedVersion() { return CryptoSettings::NoEncryption; } mozilla-vpn-client-2.2.0/src/platforms/dummy/dummynetworkwatcher.cpp000066400000000000000000000010251404202232700260110ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "dummynetworkwatcher.h" #include "leakdetector.h" DummyNetworkWatcher::DummyNetworkWatcher(QObject* parent) : NetworkWatcherImpl(parent) { MVPN_COUNT_CTOR(DummyNetworkWatcher); } DummyNetworkWatcher::~DummyNetworkWatcher() { MVPN_COUNT_DTOR(DummyNetworkWatcher); } void DummyNetworkWatcher::initialize() {} mozilla-vpn-client-2.2.0/src/platforms/dummy/dummynetworkwatcher.h000066400000000000000000000007711404202232700254650ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef DUMMYNETWORKWATCHER_H #define DUMMYNETWORKWATCHER_H #include "networkwatcherimpl.h" class DummyNetworkWatcher final : public NetworkWatcherImpl { public: DummyNetworkWatcher(QObject* parent); ~DummyNetworkWatcher(); void initialize() override; }; #endif // DUMMYNETWORKWATCHER_H mozilla-vpn-client-2.2.0/src/platforms/dummy/dummypingsendworker.cpp000066400000000000000000000012421404202232700260040ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "dummypingsendworker.h" #include "leakdetector.h" #include "logger.h" namespace { Logger logger(LOG_NETWORKING, "DummyPingSendWorker"); } DummyPingSendWorker::DummyPingSendWorker() { MVPN_COUNT_CTOR(DummyPingSendWorker); } DummyPingSendWorker::~DummyPingSendWorker() { MVPN_COUNT_DTOR(DummyPingSendWorker); } void DummyPingSendWorker::sendPing(const QString& destination) { logger.log() << "Dummy ping to:" << destination; emit pingSucceeded(); } mozilla-vpn-client-2.2.0/src/platforms/dummy/dummypingsendworker.h000066400000000000000000000011001404202232700254420ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef DUMMYPINGSENDWORKER_H #define DUMMYPINGSENDWORKER_H #include "pingsendworker.h" class DummyPingSendWorker final : public PingSendWorker { Q_OBJECT Q_DISABLE_COPY_MOVE(DummyPingSendWorker) public: DummyPingSendWorker(); ~DummyPingSendWorker(); public slots: void sendPing(const QString& destination) override; }; #endif // DUMMYPINGSENDWORKER_H mozilla-vpn-client-2.2.0/src/platforms/ios/000077500000000000000000000000001404202232700206235ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/platforms/ios/iaphandler.h000066400000000000000000000031611404202232700231040ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef IAPHANDLER_H #define IAPHANDLER_H #include class IAPHandler final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(IAPHandler) Q_PROPERTY(QString priceValue READ priceValue NOTIFY priceValueChanged) public: static IAPHandler* createInstance(); static IAPHandler* instance(); Q_INVOKABLE void subscribe(); bool hasProductsRegistered() const { return m_productsRegistrationState == eRegistered; } void registerProducts(const QStringList& products); void startSubscription(); const QString& priceValue() const { return m_priceValue; } signals: void productsRegistered(); void subscriptionStarted(); void subscriptionFailed(); void subscriptionCanceled(); void subscriptionCompleted(); void alreadySubscribed(); void priceValueChanged(); public slots: void stopSubscription(); // Called by the delegate void unknownProductRegistered(const QString& identifier); void productRegistered(void* product); void processCompletedTransactions(const QStringList& ids); private: IAPHandler(QObject* parent); ~IAPHandler(); private: enum { eNotRegistered, eRegistering, eRegistered, } m_productsRegistrationState = eNotRegistered; QString m_productName; QString m_priceValue; enum State { eActive, eInactive, } m_subscriptionState = eInactive; void* m_delegate = nullptr; void* m_product = nullptr; }; #endif // IAPHANDLER_H mozilla-vpn-client-2.2.0/src/platforms/ios/iaphandler.mm000066400000000000000000000315571404202232700233000ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "platforms/ios/iaphandler.h" #include "constants.h" #include "iosutils.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "networkrequest.h" #include "settingsholder.h" #include #include #include #include #import #import namespace { Logger logger(LOG_IAP, "IAPHandler"); IAPHandler* s_instance = nullptr; } // namespace @interface IAPHandlerDelegate : NSObject { IAPHandler* m_handler; } @end @implementation IAPHandlerDelegate - (id)initWithObject:(IAPHandler*)handler { self = [super init]; if (self) { m_handler = handler; } return self; } - (void)productsRequest:(nonnull SKProductsRequest*)request didReceiveResponse:(nonnull SKProductsResponse*)response { NSArray* products = response.products; logger.log() << "Products registered"; if ([products count] != 1) { NSString* identifier = [response.invalidProductIdentifiers firstObject]; QMetaObject::invokeMethod(m_handler, "unknownProductRegistered", Qt::QueuedConnection, Q_ARG(QString, QString::fromNSString(identifier))); } else { SKProduct* product = [[products firstObject] retain]; QMetaObject::invokeMethod(m_handler, "productRegistered", Qt::QueuedConnection, Q_ARG(void*, product)); } [request release]; } - (void)paymentQueue:(nonnull SKPaymentQueue*)queue updatedTransactions:(nonnull NSArray*)transactions { logger.log() << "payment queue:" << [transactions count]; QStringList completedTransactionIds; bool failedTransactions = false; bool canceledTransactions = false; bool completedTransactions = false; for (SKPaymentTransaction* transaction in transactions) { switch (transaction.transactionState) { case SKPaymentTransactionStateFailed: logger.log() << "transaction failed"; if (transaction.error.code == SKErrorPaymentCancelled) { canceledTransactions = true; } else { failedTransactions = true; } break; case SKPaymentTransactionStateRestored: [[fallthrough]]; case SKPaymentTransactionStatePurchased: { QString identifier = QString::fromNSString(transaction.transactionIdentifier); QDateTime date = QDateTime::fromNSDate(transaction.transactionDate); logger.log() << "transaction purchased - identifier: " << identifier << "- date:" << date.toString(); if (transaction.transactionState == SKPaymentTransactionStateRestored) { SKPaymentTransaction* originalTransaction = transaction.originalTransaction; if (originalTransaction) { QString originalIdentifier = QString::fromNSString(originalTransaction.transactionIdentifier); QDateTime originalDate = QDateTime::fromNSDate(originalTransaction.transactionDate); logger.log() << "original transaction identifier: " << originalIdentifier << "- date:" << originalDate.toString(); } } completedTransactions = true; SettingsHolder* settingsHolder = SettingsHolder::instance(); if (settingsHolder->hasSubscriptionTransaction(identifier)) { logger.log() << "This transaction has already been processed. Let's ignore it."; } else { completedTransactionIds.append(identifier); } break; } case SKPaymentTransactionStatePurchasing: logger.log() << "transaction purchasing"; break; case SKPaymentTransactionStateDeferred: logger.log() << "transaction deferred"; break; default: logger.log() << "transaction unknwon state"; break; } } if (!completedTransactions && !canceledTransactions && !failedTransactions) { // Nothing completed, nothing restored, nothing failed. Just purchasing transactions. return; } if (canceledTransactions) { logger.log() << "Subscription canceled"; QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection); QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection); } else if (failedTransactions) { logger.log() << "Subscription failed"; QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection); QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection); } else if (completedTransactionIds.isEmpty()) { Q_ASSERT(completedTransactions); logger.log() << "Subscription completed - but all the transactions are known"; QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection); QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection); } else if (MozillaVPN::instance()->userAuthenticated()) { Q_ASSERT(completedTransactions); logger.log() << "Subscription completed. Let's start the validation"; QMetaObject::invokeMethod(m_handler, "processCompletedTransactions", Qt::QueuedConnection, Q_ARG(QStringList, completedTransactionIds)); } else { Q_ASSERT(completedTransactions); logger.log() << "Subscription completed - but the user is not authenticated yet"; QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection); QMetaObject::invokeMethod(m_handler, "subscriptionCanceled", Qt::QueuedConnection); } for (SKPaymentTransaction* transaction in transactions) { switch (transaction.transactionState) { case SKPaymentTransactionStateFailed: [[fallthrough]]; case SKPaymentTransactionStateRestored: [[fallthrough]]; case SKPaymentTransactionStatePurchased: [queue finishTransaction:transaction]; break; default: break; } } } - (void)requestDidFinish:(SKRequest*)request { logger.log() << "Receipt refreshed correctly"; QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection); QMetaObject::invokeMethod(m_handler, "processCompletedTransactions", Qt::QueuedConnection, Q_ARG(QStringList, QStringList())); } - (void)request:(SKRequest*)request didFailWithError:(NSError*)error { logger.log() << "Failed to refresh the receipt" << QString::fromNSString(error.localizedDescription); QMetaObject::invokeMethod(m_handler, "stopSubscription", Qt::QueuedConnection); QMetaObject::invokeMethod(m_handler, "subscriptionFailed", Qt::QueuedConnection); } @end // static IAPHandler* IAPHandler::createInstance() { Q_ASSERT(!s_instance); new IAPHandler(qApp); Q_ASSERT(s_instance); return instance(); } // static IAPHandler* IAPHandler::instance() { Q_ASSERT(s_instance); return s_instance; } IAPHandler::IAPHandler(QObject* parent) : QObject(parent), m_priceValue(Constants::SUBSCRIPTION_CURRENCY_VALUE_USD) { MVPN_COUNT_CTOR(IAPHandler); Q_ASSERT(!s_instance); s_instance = this; m_delegate = [[IAPHandlerDelegate alloc] initWithObject:this]; [[SKPaymentQueue defaultQueue] addTransactionObserver:static_cast(m_delegate)]; } IAPHandler::~IAPHandler() { MVPN_COUNT_DTOR(IAPHandler); Q_ASSERT(s_instance == this); s_instance = nullptr; IAPHandlerDelegate* delegate = static_cast(m_delegate); [[SKPaymentQueue defaultQueue] removeTransactionObserver:delegate]; [delegate dealloc]; m_delegate = nullptr; } void IAPHandler::registerProducts(const QStringList& products) { logger.log() << "Maybe register products"; Q_ASSERT(!products.isEmpty()); Q_ASSERT(products.length() == 1); Q_ASSERT(m_productsRegistrationState == eRegistered || m_productsRegistrationState == eNotRegistered); if (m_productsRegistrationState == eRegistered) { emit productsRegistered(); return; } m_productsRegistrationState = eRegistering; m_productName = products.at(0); logger.log() << "Registration product:" << m_productName; IAPHandlerDelegate* delegate = static_cast(m_delegate); NSSet* productId = [NSSet setWithObject:m_productName.toNSString()]; SKProductsRequest* productsRequest = [[SKProductsRequest alloc] initWithProductIdentifiers:productId]; productsRequest.delegate = delegate; [productsRequest start]; logger.log() << "Waiting for the products registration"; } void IAPHandler::startSubscription() { Q_ASSERT(m_productsRegistrationState == eRegistered); Q_ASSERT(!m_productName.isEmpty()); if (m_subscriptionState != eInactive) { logger.log() << "No multiple IAP!"; return; } if (!m_product) { logger.log() << "No product registered"; // The product registration failed, for unknown reasons. emit subscriptionFailed(); return; } m_subscriptionState = eActive; logger.log() << "Starting the subscription"; SKProduct* product = static_cast(m_product); SKPayment* payment = [SKPayment paymentWithProduct:product]; [[SKPaymentQueue defaultQueue] addPayment:payment]; } void IAPHandler::stopSubscription() { logger.log() << "Stop subscription"; m_subscriptionState = eInactive; } void IAPHandler::unknownProductRegistered(const QString& identifier) { Q_ASSERT(m_productsRegistrationState == eRegistering); m_productsRegistrationState = eRegistered; logger.log() << "Product registration failed:" << identifier; emit productsRegistered(); } void IAPHandler::productRegistered(void* a_product) { SKProduct* product = static_cast(a_product); Q_ASSERT(m_productsRegistrationState == eRegistering); m_productsRegistrationState = eRegistered; Q_ASSERT(!m_product); m_product = product; logger.log() << "Product registered"; logger.log() << "Title:" << QString::fromNSString([product localizedTitle]); logger.log() << "Description:" << QString::fromNSString([product localizedDescription]); NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init]; [numberFormatter setFormatterBehavior:NSNumberFormatterBehavior10_4]; [numberFormatter setNumberStyle:(NSNumberFormatterStyle)NSNumberFormatterCurrencyStyle]; [numberFormatter setLocale:product.priceLocale]; NSString* price = [numberFormatter stringFromNumber:product.price]; m_priceValue = QString::fromNSString(price); logger.log() << "Price:" << m_priceValue; [numberFormatter release]; emit priceValueChanged(); emit productsRegistered(); } void IAPHandler::processCompletedTransactions(const QStringList& ids) { logger.log() << "process completed transactions"; if (m_subscriptionState != eActive) { logger.log() << "Random transaction to be completed. Let's ignore it"; return; } QString receipt = IOSUtils::IAPReceipt(); if (receipt.isEmpty()) { logger.log() << "Empty receipt found"; emit subscriptionFailed(); return; } NetworkRequest* request = NetworkRequest::createForIOSPurchase(this, receipt); connect(request, &NetworkRequest::requestFailed, [this](QNetworkReply::NetworkError error, const QByteArray& data) { logger.log() << "Purchase request failed" << error; if (m_subscriptionState != eActive) { logger.log() << "We have been canceled in the meantime"; return; } stopSubscription(); QJsonDocument json = QJsonDocument::fromJson(data); if (!json.isObject()) { MozillaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); emit subscriptionFailed(); return; } QJsonObject obj = json.object(); QJsonValue errorValue = obj.value("errno"); if (!errorValue.isDouble()) { MozillaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); emit subscriptionFailed(); return; } int errorNumber = errorValue.toInt(); if (errorNumber != 145) { MozillaVPN::instance()->errorHandle(ErrorHandler::toErrorType(error)); emit subscriptionFailed(); return; } emit alreadySubscribed(); }); connect(request, &NetworkRequest::requestCompleted, [this, ids](const QByteArray&) { logger.log() << "Purchase request completed"; SettingsHolder::instance()->addSubscriptionTransactions(ids); if (m_subscriptionState != eActive) { logger.log() << "We have been canceled in the meantime"; return; } stopSubscription(); emit subscriptionCompleted(); }); } void IAPHandler::subscribe() { logger.log() << "Subscription required"; emit subscriptionStarted(); } mozilla-vpn-client-2.2.0/src/platforms/ios/iosauthenticationlistener.h000066400000000000000000000012221404202232700262710ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef IOSAUTHENTICATIONLISTENER_H #define IOSAUTHENTICATIONLISTENER_H #include "authenticationlistener.h" #include class IOSAuthenticationListener final : public AuthenticationListener { Q_DISABLE_COPY_MOVE(IOSAuthenticationListener) public: IOSAuthenticationListener(QObject* parent); ~IOSAuthenticationListener(); void start(MozillaVPN* vpn, QUrl& url, QUrlQuery& query) override; }; #endif // IOSAUTHENTICATIONLISTENER_H mozilla-vpn-client-2.2.0/src/platforms/ios/iosauthenticationlistener.mm000066400000000000000000000075461404202232700264720ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "iosauthenticationlistener.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "qmlengineholder.h" #include #include #include #include #include #include #include #import #import namespace { Logger logger({LOG_IOS, LOG_MAIN}, "IOSAuthenticationListener"); ASWebAuthenticationSession* session = nullptr; } // namespace #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 @interface ContextProvider : NSObject { UIView* m_view; } @end @implementation ContextProvider - (id)initWithUIView:(UIView*)uiView { self = [super init]; if (self) { m_view = uiView; } return self; } # pragma mark - ASWebAuthenticationPresentationContextProviding - (nonnull ASPresentationAnchor)presentationAnchorForWebAuthenticationSession: (nonnull ASWebAuthenticationSession*)session API_AVAILABLE(ios(13.0)) { return m_view.window; } @end #endif IOSAuthenticationListener::IOSAuthenticationListener(QObject* parent) : AuthenticationListener(parent) { MVPN_COUNT_CTOR(IOSAuthenticationListener); } IOSAuthenticationListener::~IOSAuthenticationListener() { MVPN_COUNT_DTOR(IOSAuthenticationListener); } void IOSAuthenticationListener::start(MozillaVPN* vpn, QUrl& url, QUrlQuery& query) { Q_UNUSED(vpn); logger.log() << "IOSAuthenticationListener initialize"; query.addQueryItem("platform", "ios"); url.setQuery(query); #ifdef QT_DEBUG logger.log() << "Authentication URL:" << url.toString(); #endif if (session) { [session dealloc]; session = nullptr; } session = [[ASWebAuthenticationSession alloc] initWithURL:url.toNSURL() callbackURLScheme:@"mozilla-vpn" completionHandler:^(NSURL* _Nullable callbackURL, NSError* _Nullable error) { [session dealloc]; session = nullptr; if (error) { logger.log() << "Authentication failed:" << QString::fromNSString([error localizedDescription]); logger.log() << "Code:" << [error code]; logger.log() << "Suggestion:" << QString::fromNSString([error localizedRecoverySuggestion]); logger.log() << "Reason:" << QString::fromNSString([error localizedFailureReason]); if ([error code] == ASWebAuthenticationSessionErrorCodeCanceledLogin) { emit abortedByUser(); } else { emit failed(ErrorHandler::RemoteServiceError); } return; } QUrl callbackUrl = QUrl::fromNSURL(callbackURL); logger.log() << "Authentication completed"; Q_ASSERT(callbackUrl.hasQuery()); QUrlQuery callbackUrlQuery(callbackUrl.query()); Q_ASSERT(callbackUrlQuery.hasQueryItem("code")); QString code = callbackUrlQuery.queryItemValue("code"); emit completed(code); }]; #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 QObject* rootObject = QmlEngineHolder::instance()->engine()->rootObjects().first(); QWindow* window = qobject_cast(rootObject); Q_ASSERT(window); UIView* view = static_cast( QGuiApplication::platformNativeInterface()->nativeResourceForWindow("uiview", window)); if (@available(iOS 13, *)) { session.presentationContextProvider = [[ContextProvider alloc] initWithUIView:view]; } #endif if (![session start]) { [session dealloc]; session = nullptr; logger.log() << "Authentication failed: session doesn't start."; emit failed(ErrorHandler::RemoteServiceError); } } mozilla-vpn-client-2.2.0/src/platforms/ios/ioscontroller.h000066400000000000000000000017601404202232700236760ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef IOSCONTROLLER_H #define IOSCONTROLLER_H #include "controllerimpl.h" #include class IOSController final : public ControllerImpl { Q_DISABLE_COPY_MOVE(IOSController) public: IOSController(); ~IOSController(); void initialize(const Device* device, const Keys* keys) override; void activate(const Server& data, const Device* device, const Keys* keys, const QList& allowedIPAddressRanges, const QList& vpnDisabledApps, Reason reason) override; void deactivate(Reason reason) override; void checkStatus() override; void getBackendLogs(std::function&& callback) override; void cleanupBackendLogs() override; private: bool m_checkingStatus = false; }; #endif // IOSCONTROLLER_H mozilla-vpn-client-2.2.0/src/platforms/ios/ioscontroller.mm000066400000000000000000000154501404202232700240610ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "ioscontroller.h" #include "Mozilla_VPN-Swift.h" #include "device.h" #include "ipaddressrange.h" #include "keys.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "server.h" #include "settingsholder.h" #include #include namespace { Logger logger({LOG_IOS, LOG_CONTROLLER}, "IOSController"); // Our Swift singleton. IOSControllerImpl* impl = nullptr; } // namespace IOSController::IOSController() { MVPN_COUNT_CTOR(IOSController); logger.log() << "created"; Q_ASSERT(!impl); } IOSController::~IOSController() { MVPN_COUNT_DTOR(IOSController); logger.log() << "deallocated"; if (impl) { [impl dealloc]; impl = nullptr; } } void IOSController::initialize(const Device* device, const Keys* keys) { Q_ASSERT(!impl); Q_UNUSED(device); logger.log() << "Initializing Swift Controller"; static bool creating = false; // No nested creation! Q_ASSERT(creating == false); creating = true; QByteArray key = QByteArray::fromBase64(keys->privateKey().toLocal8Bit()); impl = [[IOSControllerImpl alloc] initWithBundleID:@VPN_NE_BUNDLEID privateKey:key.toNSData() deviceIpv4Address:device->ipv4Address().toNSString() deviceIpv6Address:device->ipv6Address().toNSString() closure:^(ConnectionState state, NSDate* date) { logger.log() << "Creation completed with connection state:" << state; creating = false; switch (state) { case ConnectionStateError: { [impl dealloc]; impl = nullptr; emit initialized(false, false, QDateTime()); return; } case ConnectionStateConnected: { Q_ASSERT(date); QDateTime qtDate(QDateTime::fromNSDate(date)); emit initialized(true, true, qtDate); return; } case ConnectionStateDisconnected: // Just in case we are connecting, let's call disconnect. [impl disconnect]; emit initialized(true, false, QDateTime()); return; } } callback:^(BOOL a_connected) { logger.log() << "State changed: " << a_connected; if (a_connected) { emit connected(); return; } emit disconnected(); }]; } void IOSController::activate(const Server& server, const Device* device, const Keys* keys, const QList& allowedIPAddressRanges, const QList& vpnDisabledApps, Reason reason) { Q_UNUSED(device); Q_UNUSED(keys); // This feature is not supported on macos/ios yet. Q_ASSERT(vpnDisabledApps.isEmpty()); logger.log() << "IOSController activating" << server.hostname(); if (!impl) { logger.log() << "Controller not correctly initialized"; emit disconnected(); return; } NSMutableArray* allowedIPAddressRangesNS = [NSMutableArray arrayWithCapacity:allowedIPAddressRanges.length()]; for (const IPAddressRange& i : allowedIPAddressRanges) { VPNIPAddressRange* range = [[VPNIPAddressRange alloc] initWithAddress:i.ipAddress().toNSString() networkPrefixLength:i.range() isIpv6:i.type() == IPAddressRange::IPv6]; [allowedIPAddressRangesNS addObject:[range autorelease]]; } [impl connectWithServerIpv4Gateway:server.ipv4Gateway().toNSString() serverIpv6Gateway:server.ipv6Gateway().toNSString() serverPublicKey:server.publicKey().toNSString() serverIpv4AddrIn:server.ipv4AddrIn().toNSString() serverPort:server.choosePort() allowedIPAddressRanges:allowedIPAddressRangesNS ipv6Enabled:SettingsHolder::instance()->ipv6Enabled() reason:reason failureCallback:^() { logger.log() << "IOSSWiftController - connection failed"; emit disconnected(); }]; } void IOSController::deactivate(Reason reason) { logger.log() << "IOSController deactivated"; if (reason != ReasonNone) { logger.log() << "We do not need to disable the VPN for switching or connection check."; emit disconnected(); return; } if (!impl) { logger.log() << "Controller not correctly initialized"; emit disconnected(); return; } [impl disconnect]; } void IOSController::checkStatus() { logger.log() << "Checking status"; if (m_checkingStatus) { logger.log() << "We are still waiting for the previous status."; return; } if (!impl) { logger.log() << "Controller not correctly initialized"; return; } m_checkingStatus = true; [impl checkStatusWithCallback:^(NSString* serverIpv4Gateway, NSString* configString) { QString config = QString::fromNSString(configString); m_checkingStatus = false; if (config.isEmpty()) { return; } uint64_t txBytes = 0; uint64_t rxBytes = 0; QStringList lines = config.split("\n"); for (const QString& line : lines) { if (line.startsWith("tx_bytes=")) { txBytes = line.split("=")[1].toULongLong(); } else if (line.startsWith("rx_bytes=")) { rxBytes = line.split("=")[1].toULongLong(); } if (txBytes && rxBytes) { break; } } logger.log() << "ServerIpv4Gateway:" << QString::fromNSString(serverIpv4Gateway) << "RxBytes:" << rxBytes << "TxBytes:" << txBytes; emit statusUpdated(QString::fromNSString(serverIpv4Gateway), txBytes, rxBytes); }]; } void IOSController::getBackendLogs(std::function&& a_callback) { std::function callback = std::move(a_callback); QString groupId(GROUP_ID); NSURL* groupPath = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupId.toNSString()]; NSURL* path = [groupPath URLByAppendingPathComponent:@"networkextension.log"]; QFile file(QString::fromNSString([path path])); if (!file.open(QIODevice::ReadOnly)) { callback("Network extension log file missing or unreadable."); return; } QByteArray content = file.readAll(); callback(content); } void IOSController::cleanupBackendLogs() { QString groupId(GROUP_ID); NSURL* groupPath = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupId.toNSString()]; NSURL* path = [groupPath URLByAppendingPathComponent:@"networkextension.log"]; QFile file(QString::fromNSString([path path])); file.remove(); } mozilla-vpn-client-2.2.0/src/platforms/ios/ioscontroller.swift000066400000000000000000000263231404202232700246050ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import Foundation import NetworkExtension let vpnName = "Mozilla VPN" var vpnBundleID = ""; @objc class VPNIPAddressRange : NSObject { public var address: NSString = "" public var networkPrefixLength: UInt8 = 0 public var isIpv6: Bool = false @objc init(address: NSString, networkPrefixLength: UInt8, isIpv6: Bool) { super.init() self.address = address self.networkPrefixLength = networkPrefixLength self.isIpv6 = isIpv6 } } public class IOSControllerImpl : NSObject { private var tunnel: NETunnelProviderManager? = nil private var stateChangeCallback: ((Bool) -> Void?)? = nil private var privateKey : Data? = nil private var deviceIpv4Address: String? = nil private var deviceIpv6Address: String? = nil private var switchingServer: Bool = false private var switchingServerConfig: TunnelConfiguration? = nil private var switchingServerFailureCallback: (() -> Void)? = nil @objc enum ConnectionState: Int { case Error, Connected, Disconnected } @objc init(bundleID: String, privateKey: Data, deviceIpv4Address: String, deviceIpv6Address: String, closure: @escaping (ConnectionState, Date?) -> Void, callback: @escaping (Bool) -> Void) { super.init() assert(privateKey.count == TunnelConfiguration.keyLength) Logger.configureGlobal(tagged: "APP", withFilePath: "") vpnBundleID = bundleID; precondition(!vpnBundleID.isEmpty) stateChangeCallback = callback self.privateKey = privateKey self.deviceIpv4Address = deviceIpv4Address self.deviceIpv6Address = deviceIpv6Address NotificationCenter.default.addObserver(self, selector: #selector(self.vpnStatusDidChange(notification:)), name: Notification.Name.NEVPNStatusDidChange, object: nil) NETunnelProviderManager.loadAllFromPreferences { [weak self] managers, error in if let error = error { Logger.global?.log(message: "Loading from preference failed: \(error)") closure(ConnectionState.Error, nil) return } if self == nil { Logger.global?.log(message: "We are shutting down.") return } let nsManagers = managers ?? [] Logger.global?.log(message: "We have received \(nsManagers.count) managers.") let tunnel = nsManagers.first(where: IOSControllerImpl.isOurManager(_:)) if tunnel == nil { Logger.global?.log(message: "Creating the tunnel") self!.tunnel = NETunnelProviderManager() closure(ConnectionState.Disconnected, nil) return } Logger.global?.log(message: "Tunnel already exists") self!.tunnel = tunnel if tunnel?.connection.status == .connected { closure(ConnectionState.Connected, tunnel?.connection.connectedDate) } else { closure(ConnectionState.Disconnected, nil) } } } @objc private func vpnStatusDidChange(notification: Notification) { guard let session = (notification.object as? NETunnelProviderSession), tunnel?.connection == session else { return } switch session.status { case .connected: Logger.global?.log(message: "STATE CHANGED: connected") case .connecting: Logger.global?.log(message: "STATE CHANGED: connecting") case .disconnected: Logger.global?.log(message: "STATE CHANGED: disconnected") case .disconnecting: Logger.global?.log(message: "STATE CHANGED: disconnecting") case .invalid: Logger.global?.log(message: "STATE CHANGED: invalid") case .reasserting: Logger.global?.log(message: "STATE CHANGED: reasserting") default: Logger.global?.log(message: "STATE CHANGED: unknown status") } // We care about "unknown" state changes. if (session.status != .connected && session.status != .disconnected) { return } // No notifications when switching server if (switchingServer) { if (switchingServerConfig == nil || switchingServerFailureCallback == nil) { Logger.global?.log(message: "Internal error! No switching-server data found") } else { configureTunnel(config: switchingServerConfig!, failureCallback: switchingServerFailureCallback!) switchingServerConfig = nil switchingServerFailureCallback = nil } return } stateChangeCallback?(session.status == .connected) } private static func isOurManager(_ manager: NETunnelProviderManager) -> Bool { guard let proto = manager.protocolConfiguration, let tunnelProto = proto as? NETunnelProviderProtocol else { Logger.global?.log(message: "Ignoring manager because the proto is invalid.") return false } if (tunnelProto.providerBundleIdentifier == nil) { Logger.global?.log(message: "Ignoring manager because the bundle identifier is null.") return false } if (tunnelProto.providerBundleIdentifier != vpnBundleID) { Logger.global?.log(message: "Ignoring manager because the bundle identifier doesn't match.") return false; } Logger.global?.log(message: "Found the manager with the correct bundle identifier: \(tunnelProto.providerBundleIdentifier!)") return true } @objc func connect(serverIpv4Gateway: String, serverIpv6Gateway: String, serverPublicKey: String, serverIpv4AddrIn: String, serverPort: Int, allowedIPAddressRanges: Array, ipv6Enabled: Bool, reason: Int, failureCallback: @escaping () -> Void) { Logger.global?.log(message: "Connecting") assert(tunnel != nil) // Let's remove the previous config if it exists. (tunnel!.protocolConfiguration as? NETunnelProviderProtocol)?.destroyConfigurationReference() let keyData = Data(base64Key: serverPublicKey)! let ipv4GatewayIP = IPv4Address(serverIpv4Gateway) let ipv6GatewayIP = IPv6Address(serverIpv6Gateway) var peerConfiguration = PeerConfiguration(publicKey: keyData) peerConfiguration.endpoint = Endpoint(from: serverIpv4AddrIn + ":\(serverPort )") peerConfiguration.allowedIPs = [] allowedIPAddressRanges.forEach { if (!$0.isIpv6) { peerConfiguration.allowedIPs.append(IPAddressRange(address: IPv4Address($0.address as String)!, networkPrefixLength: $0.networkPrefixLength)) } else if (ipv6Enabled) { peerConfiguration.allowedIPs.append(IPAddressRange(address: IPv6Address($0.address as String)!, networkPrefixLength: $0.networkPrefixLength)) } } var peerConfigurations: [PeerConfiguration] = [] peerConfigurations.append(peerConfiguration) var interface = InterfaceConfiguration(privateKey: privateKey!) if let ipv4Address = IPAddressRange(from: deviceIpv4Address!), let ipv6Address = IPAddressRange(from: deviceIpv6Address!) { interface.addresses = [ipv4Address] if (ipv6Enabled) { interface.addresses.append(ipv6Address) } } interface.dns = [ DNSServer(address: ipv4GatewayIP!)] if (ipv6Enabled) { interface.dns.append(DNSServer(address: ipv6GatewayIP!)) } let config = TunnelConfiguration(name: vpnName, interface: interface, peers: peerConfigurations) if (reason != 0) { switchingServer = true switchingServerConfig = config switchingServerFailureCallback = failureCallback (tunnel!.connection as? NETunnelProviderSession)?.stopTunnel() return; } self.configureTunnel(config: config, failureCallback: failureCallback) } func configureTunnel(config: TunnelConfiguration, failureCallback: @escaping () -> Void) { let proto = NETunnelProviderProtocol(tunnelConfiguration: config) proto!.providerBundleIdentifier = vpnBundleID tunnel!.protocolConfiguration = proto tunnel!.localizedDescription = vpnName tunnel!.isEnabled = true tunnel!.saveToPreferences { [unowned self] saveError in if let error = saveError { Logger.global?.log(message: "Connect Tunnel Save Error: \(error)") failureCallback() return } Logger.global?.log(message: "Saving the tunnel succeeded") self.tunnel!.loadFromPreferences { error in if let error = error { Logger.global?.log(message: "Connect Tunnel Load Error: \(error)") failureCallback() return } Logger.global?.log(message: "Loading the tunnel succeeded") // If we were switching server, now it's time to consider the operation completed. switchingServer = false do { try (self.tunnel!.connection as? NETunnelProviderSession)?.startTunnel() } catch let error { Logger.global?.log(message: "Something went wrong: \(error)") failureCallback() return } } } } @objc func disconnect() { Logger.global?.log(message: "Disconnecting") assert(tunnel != nil) (tunnel!.connection as? NETunnelProviderSession)?.stopTunnel() } @objc func checkStatus(callback: @escaping (String, String) -> Void) { Logger.global?.log(message: "Check status") assert(tunnel != nil) let proto = tunnel!.protocolConfiguration as? NETunnelProviderProtocol if proto == nil { callback("", "") return } let tunnelConfiguration = proto?.asTunnelConfiguration() if tunnelConfiguration == nil { callback("", "") return } let serverIpv4Gateway = tunnelConfiguration?.interface.dns[0].address if serverIpv4Gateway == nil { callback("", "") return } guard let session = tunnel?.connection as? NETunnelProviderSession else { callback("", "") return } do { try session.sendProviderMessage(Data([UInt8(0)])) { [callback] data in guard let data = data, let configString = String(data: data, encoding: .utf8) else { Logger.global?.log(message: "Failed to convert data to string") callback("", "") return } callback("\(serverIpv4Gateway!)", configString) } } catch { Logger.global?.log(message: "Failed to retrieve data from session") callback("", "") } } } mozilla-vpn-client-2.2.0/src/platforms/ios/iosdatamigration.h000066400000000000000000000005741404202232700243400ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef IOSDATAMIGRATION_H #define IOSDATAMIGRATION_H #include class IOSDataMigration final { public: static void migrate(); }; #endif // IOSDATAMIGRATION_H mozilla-vpn-client-2.2.0/src/platforms/ios/iosdatamigration.mm000066400000000000000000000121031404202232700245110ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "iosdatamigration.h" #include "device.h" #include "logger.h" #include "mozillavpn.h" #include "user.h" #include #include #include #include #include #import namespace { Logger logger(LOG_IOS, "IOSDataMigration"); void migrateUserDefaultData() { MozillaVPN* vpn = MozillaVPN::instance(); Q_ASSERT(vpn); NSUserDefaults* sud = [NSUserDefaults standardUserDefaults]; if (!sud) { return; } NSData* userData = [sud dataForKey:@"user"]; if (userData) { QByteArray json = QByteArray::fromNSData(userData); if (!json.isEmpty()) { logger.log() << "User data to be migrated"; vpn->accountChecked(json); } } NSData* deviceData = [sud dataForKey:@"device"]; if (deviceData) { QByteArray json = QByteArray::fromNSData(deviceData); logger.log() << "Device data to be migrated"; // Nothing has to be done here because the device data is part of the user data. } NSData* serversData = [sud dataForKey:@"vpnServers"]; if (serversData) { QByteArray json = QByteArray::fromNSData(serversData); if (!json.isEmpty()) { logger.log() << "Server list data to be migrated"; // We need to wrap the server list in a object to make it similar to the REST API response. QJsonDocument serverList = QJsonDocument::fromJson(json); if (!serverList.isArray()) { logger.log() << "Server list should be an array!"; return; } QJsonObject countriesObj; countriesObj.insert("countries", QJsonValue(serverList.array())); QJsonDocument doc; doc.setObject(countriesObj); if (!vpn->setServerList(doc.toJson())) { logger.log() << "Server list cannot be imported"; return; } } } NSData* selectedCityData = [sud dataForKey:@"selectedCity"]; if (selectedCityData) { QByteArray json = QByteArray::fromNSData(selectedCityData); logger.log() << "SelectedCity data to be migrated" << json; // Nothing has to be done here because the device data is part of the user data. QJsonDocument doc = QJsonDocument::fromJson(json); if (!doc.isObject()) { logger.log() << "SelectedCity should be an object"; return; } QJsonObject obj = doc.object(); QJsonValue code = obj.value("flagCode"); if (!code.isString()) { logger.log() << "SelectedCity code should be a string"; return; } QJsonValue name = obj.value("code"); if (!name.isString()) { logger.log() << "SelectedCity name should be a string"; return; } ServerData serverData; if (vpn->serverCountryModel()->pickIfExists(code.toString(), name.toString(), serverData)) { logger.log() << "ServerCity found"; serverData.writeSettings(); } } } void migrateKeychainData() { NSData* service = [@"org.mozilla.guardian.credentials" dataUsingEncoding:NSUTF8StringEncoding]; NSMutableDictionary* query = [[NSMutableDictionary alloc] init]; [query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; [query setObject:service forKey:(id)kSecAttrService]; [query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData]; [query setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit]; NSData* dataNS = NULL; OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef*)(void*)&dataNS); [query release]; if (status != noErr) { logger.log() << "No credentials found"; return; } QByteArray data = QByteArray::fromNSData(dataNS); #ifdef QT_DEBUG logger.log() << "Credentials:" << data; #endif QJsonDocument json = QJsonDocument::fromJson(data); if (!json.isObject()) { logger.log() << "JSON object expected"; return; } QJsonObject obj = json.object(); QJsonValue deviceKeyValue = obj.value("deviceKeys"); if (!deviceKeyValue.isObject()) { logger.log() << "JSON object should have a deviceKeys object"; return; } QJsonObject deviceKeyObj = deviceKeyValue.toObject(); QJsonValue publicKey = deviceKeyObj.value("publicKey"); if (!publicKey.isString()) { logger.log() << "JSON deviceKey object should contain a publicKey value as string"; return; } QJsonValue privateKey = deviceKeyObj.value("privateKey"); if (!privateKey.isString()) { logger.log() << "JSON deviceKey object should contain a privateKey value as string"; return; } QJsonValue token = obj.value("verificationToken"); if (!token.isString()) { logger.log() << "JSON object should contain a verificationToken value s string"; return; } MozillaVPN::instance()->deviceAdded(Device::currentDeviceName(), publicKey.toString(), privateKey.toString()); MozillaVPN::instance()->setToken(token.toString()); } } // static void IOSDataMigration::migrate() { logger.log() << "IOS Data Migration"; migrateKeychainData(); migrateUserDefaultData(); } mozilla-vpn-client-2.2.0/src/platforms/ios/iosglue.mm000066400000000000000000000143761404202232700226400ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // This file contains all the C functions needed by the Wireguard swift code. #include #include #ifndef NETWORK_EXTENSION # include "logger.h" #else # import # import #endif #define MAX_LOG_FILE_SIZE 204800 // Key base64/hex functions // ------------------------ #define WG_KEY_LEN (32) #define WG_KEY_LEN_BASE64 (45) #define WG_KEY_LEN_HEX (65) #define EXPORT __attribute__((visibility("default"))) extern "C" { EXPORT void key_to_base64(char base64[WG_KEY_LEN_BASE64], const uint8_t key[WG_KEY_LEN]); EXPORT bool key_from_base64(uint8_t key[WG_KEY_LEN], const char* base64); EXPORT void key_to_hex(char hex[WG_KEY_LEN_HEX], const uint8_t key[WG_KEY_LEN]); EXPORT bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex); EXPORT void write_msg_to_log(const char* tag, const char* msg); } EXPORT void key_to_base64(char base64[WG_KEY_LEN_BASE64], const uint8_t key[WG_KEY_LEN]) { const char range[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; const char padchar = '='; int padlen = 0; char* out = base64; const uint8_t* in = key; for (int i = 0; i < WG_KEY_LEN;) { int chunk = 0; chunk |= int(in[i++]) << 16; if (i == WG_KEY_LEN) { padlen = 2; } else { chunk |= int(in[i++]) << 8; if (i == WG_KEY_LEN) { padlen = 1; } else { chunk |= int(in[i++]); } } int j = (chunk & 0x00fc0000) >> 18; int k = (chunk & 0x0003f000) >> 12; int l = (chunk & 0x00000fc0) >> 6; int m = (chunk & 0x0000003f); *out++ = range[j]; *out++ = range[k]; if (padlen > 1) { *out++ = padchar; } else { *out++ = range[l]; } if (padlen > 0) { *out++ = padchar; } else { *out++ = range[m]; } } base64[WG_KEY_LEN_BASE64 - 1] = 0; } EXPORT bool key_from_base64(uint8_t key[WG_KEY_LEN], const char* base64) { if (strlen(base64) != WG_KEY_LEN_BASE64 - 1 || base64[WG_KEY_LEN_BASE64 - 2] != '=') { return false; } unsigned int buf = 0; int nbits = 0; uint8_t* out = key; int offset = 0; for (int i = 0; i < WG_KEY_LEN_BASE64; ++i) { int ch = base64[i]; int d; if (ch >= 'A' && ch <= 'Z') { d = ch - 'A'; } else if (ch >= 'a' && ch <= 'z') { d = ch - 'a' + 26; } else if (ch >= '0' && ch <= '9') { d = ch - '0' + 52; } else if (ch == '+') { d = 62; } else if (ch == '/') { d = 63; } else { d = -1; } if (d != -1) { buf = (buf << 6) | d; nbits += 6; if (nbits >= 8) { nbits -= 8; out[offset++] = buf >> nbits; buf &= (1 << nbits) - 1; } } } return true; } inline char toHex(uint8_t value) { return "0123456789abcdef"[value & 0xF]; } inline int fromHex(uint8_t c) { return ((c >= '0') && (c <= '9')) ? int(c - '0') : ((c >= 'A') && (c <= 'F')) ? int(c - 'A' + 10) : ((c >= 'a') && (c <= 'f')) ? int(c - 'a' + 10) : -1; } EXPORT void key_to_hex(char hex[WG_KEY_LEN_HEX], const uint8_t key[WG_KEY_LEN]) { char* hexData = hex; const unsigned char* data = (const unsigned char*)key; for (int i = 0, o = 0; i < WG_KEY_LEN; ++i) { hexData[o++] = toHex(data[i] >> 4); hexData[o++] = toHex(data[i] & 0xf); } hex[WG_KEY_LEN_HEX - 1] = 0; } EXPORT bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex) { if (strlen(hex) != WG_KEY_LEN_HEX - 1) { return false; } bool odd_digit = true; unsigned char* result = (unsigned char*)key + WG_KEY_LEN; for (int i = WG_KEY_LEN_HEX - 1; i >= 0; --i) { int tmp = fromHex((unsigned char)(hex[i])); if (tmp == -1) { continue; } if (odd_digit) { --result; *result = tmp; odd_digit = false; } else { *result |= tmp << 4; odd_digit = true; } } return true; } // Logging functions // ----------------- #ifndef NETWORK_EXTENSION namespace { Logger logger(LOG_IOS, "IOSSGlue"); } #endif EXPORT void write_msg_to_log(const char* tag, const char* msg) { #ifndef NETWORK_EXTENSION logger.log() << "Swift log - tag:" << tag << "msg: " << msg; #else os_log_with_type(OS_LOG_DEFAULT, OS_LOG_TYPE_DEBUG, "tag: %s - msg: %s", tag, msg); @autoreleasepool { NSString* groupId = [NSString stringWithUTF8String:GROUP_ID]; NSURL* groupPath = [[NSFileManager defaultManager] containerURLForSecurityApplicationGroupIdentifier:groupId]; NSURL* pathUrl = [groupPath URLByAppendingPathComponent:@"networkextension.log"]; NSString* path = [pathUrl path]; if (![[NSFileManager defaultManager] fileExistsAtPath:path]) { [[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil]; } else { NSError* error = nil; NSDictionary* fileAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:path error:&error]; if (error) { return; } NSNumber* fileSizeNumber = [fileAttributes objectForKey:NSFileSize]; long long fileSize = [fileSizeNumber longLongValue]; if (fileSize > MAX_LOG_FILE_SIZE) { [[NSFileManager defaultManager] removeItemAtPath:path error:&error]; [[NSFileManager defaultManager] createFileAtPath:path contents:nil attributes:nil]; } } NSError* error = nil; NSFileHandle* fh = [NSFileHandle fileHandleForWritingToURL:pathUrl error:&error]; if (!fh) { return; } NSString* dateString = [NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterShortStyle timeStyle:NSDateFormatterFullStyle]; NSString* str = [NSString stringWithFormat:@" - %s\n", msg]; NSData* data = [[dateString stringByAppendingString:str] dataUsingEncoding:NSUTF8StringEncoding]; @try { [fh seekToEndOfFile]; [fh writeData:data]; } @catch (NSException* exception) { } [fh closeFile]; } #endif } mozilla-vpn-client-2.2.0/src/platforms/ios/ioslogger.swift000066400000000000000000000027521404202232700237010ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import Foundation import os.log public class Logger { static var global: Logger? var tag: String init(tagged tag: String) { self.tag = tag } deinit {} func log(message: String) { write_msg_to_log(tag, message.trimmingCharacters(in: .newlines)) } func writeLog(to targetFile: String) -> Bool { return true; } static func configureGlobal(tagged tag: String, withFilePath filePath: String?) { if Logger.global != nil { return } Logger.global = Logger(tagged: tag) var appVersion = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown version" if let appBuild = Bundle.main.infoDictionary?["CFBundleVersion"] as? String { appVersion += " (\(appBuild))" } let goBackendVersion = WIREGUARD_GO_VERSION Logger.global?.log(message: "App version: \(appVersion); Go backend version: \(goBackendVersion)") } } func wg_log(_ type: OSLogType, staticMessage msg: StaticString) { os_log(msg, log: OSLog.default, type: type) Logger.global?.log(message: "\(msg)") } func wg_log(_ type: OSLogType, message msg: String) { os_log("%{public}s", log: OSLog.default, type: type, msg) Logger.global?.log(message: msg) } mozilla-vpn-client-2.2.0/src/platforms/ios/iosnotificationhandler.h000066400000000000000000000013111404202232700255270ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef IOSNOTIFICATIONHANDLER_H #define IOSNOTIFICATIONHANDLER_H #include "notificationhandler.h" #include class IOSNotificationHandler final : public NotificationHandler { Q_DISABLE_COPY_MOVE(IOSNotificationHandler) public: IOSNotificationHandler(QObject* parent); ~IOSNotificationHandler(); protected: void notify(const QString& title, const QString& message, int timerSec) override; private: void* m_delegate = nullptr; }; #endif // IOSNOTIFICATIONHANDLER_H mozilla-vpn-client-2.2.0/src/platforms/ios/iosnotificationhandler.mm000066400000000000000000000063411404202232700257210ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "platforms/ios/iosnotificationhandler.h" #include "leakdetector.h" #import #import #import @interface IOSNotificationDelegate : UIResponder { IOSNotificationHandler* m_iosNotificationHandler; } @end @implementation IOSNotificationDelegate - (id)initWithObject:(IOSNotificationHandler*)notification { self = [super init]; if (self) { m_iosNotificationHandler = notification; } return self; } - (void)userNotificationCenter:(UNUserNotificationCenter*)center willPresentNotification:(UNNotification*)notification withCompletionHandler: (void (^)(UNNotificationPresentationOptions options))completionHandler { Q_UNUSED(center) completionHandler(UNNotificationPresentationOptionAlert); } - (void)userNotificationCenter:(UNUserNotificationCenter*)center didReceiveNotificationResponse:(UNNotificationResponse*)response withCompletionHandler:(void (^)())completionHandler { Q_UNUSED(center) Q_UNUSED(response) completionHandler(); } @end IOSNotificationHandler::IOSNotificationHandler(QObject* parent) : NotificationHandler(parent) { MVPN_COUNT_CTOR(IOSNotificationHandler); UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; [center requestAuthorizationWithOptions:(UNAuthorizationOptionSound | UNAuthorizationOptionAlert | UNAuthorizationOptionBadge) completionHandler:^(BOOL granted, NSError* _Nullable error) { Q_UNUSED(granted); if (!error) { m_delegate = [[IOSNotificationDelegate alloc] initWithObject:this]; } }]; } IOSNotificationHandler::~IOSNotificationHandler() { MVPN_COUNT_DTOR(IOSNotificationHandler); } void IOSNotificationHandler::notify(const QString& title, const QString& message, int timerSec) { if (!m_delegate) { return; } UNMutableNotificationContent* content = [[UNMutableNotificationContent alloc] init]; content.title = title.toNSString(); content.body = message.toNSString(); content.sound = [UNNotificationSound defaultSound]; UNTimeIntervalNotificationTrigger* trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:timerSec repeats:NO]; UNNotificationRequest* request = [UNNotificationRequest requestWithIdentifier:@"mozillavpn" content:content trigger:trigger]; UNUserNotificationCenter* center = [UNUserNotificationCenter currentNotificationCenter]; center.delegate = id(m_delegate); [center addNotificationRequest:request withCompletionHandler:^(NSError* _Nullable error) { if (error) { NSLog(@"Local Notification failed"); } }]; } mozilla-vpn-client-2.2.0/src/platforms/ios/iosutils.h000066400000000000000000000006041404202232700226470ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef IOSUTILS_H #define IOSUTILS_H #include class IOSUtils final { public: static QString computerName(); static QString IAPReceipt(); }; #endif // IOSUTILS_H mozilla-vpn-client-2.2.0/src/platforms/ios/iosutils.mm000066400000000000000000000034141404202232700230330ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "iosutils.h" #include "logger.h" #include #include #import namespace { Logger logger(LOG_IOS, "IOSUtils"); } // static QString IOSUtils::computerName() { NSString* name = [[UIDevice currentDevice] name]; return QString::fromNSString(name); } // static QString IOSUtils::IAPReceipt() { logger.log() << "Retrieving IAP receipt"; NSURL* receiptURL = [[NSBundle mainBundle] appStoreReceiptURL]; NSData* receipt = [NSData dataWithContentsOfURL:receiptURL]; // All the following is for debug only. NSString* path = [receiptURL path]; Q_ASSERT(path); logger.log() << "Receipt URL:" << QString::fromNSString(path); NSFileManager* fileManager = [NSFileManager defaultManager]; Q_ASSERT(fileManager); NSDictionary* fileAttributes = [fileManager attributesOfItemAtPath:path error:NULL]; if (fileAttributes) { NSNumber* fileSize = [fileAttributes objectForKey:NSFileSize]; if (fileSize) { logger.log() << "File size:" << [fileSize unsignedLongLongValue]; } NSString* fileOwner = [fileAttributes objectForKey:NSFileOwnerAccountName]; if (fileOwner) { logger.log() << "Owner:" << QString::fromNSString(fileOwner); } NSDate* fileModDate = [fileAttributes objectForKey:NSFileModificationDate]; if (fileModDate) { logger.log() << "Modification date:" << QDateTime::fromNSDate(fileModDate).toString(); } } if (!receipt) { return QString(); } NSString* encodedReceipt = [receipt base64EncodedStringWithOptions:0]; return QString::fromNSString(encodedReceipt); } mozilla-vpn-client-2.2.0/src/platforms/ios/taskiosproducts.cpp000066400000000000000000000042641404202232700245760ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "taskiosproducts.h" #include "iaphandler.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "networkrequest.h" #include "settingsholder.h" #include #include #include #include namespace { Logger logger(LOG_IAP, "TaskIOSProducts"); } TaskIOSProducts::TaskIOSProducts() : Task("TaskIOSProducts") { MVPN_COUNT_CTOR(TaskIOSProducts); } TaskIOSProducts::~TaskIOSProducts() { MVPN_COUNT_DTOR(TaskIOSProducts); } void TaskIOSProducts::run(MozillaVPN* vpn) { NetworkRequest* request = NetworkRequest::createForIOSProducts(this); connect(request, &NetworkRequest::requestFailed, [this, vpn](QNetworkReply::NetworkError error, const QByteArray&) { logger.log() << "IOS product request failed" << error; vpn->errorHandle(ErrorHandler::toErrorType(error)); emit completed(); }); connect(request, &NetworkRequest::requestCompleted, [this](const QByteArray& data) { logger.log() << "IOS product request completed" << data; QJsonDocument json = QJsonDocument::fromJson(data); Q_ASSERT(json.isObject()); QJsonObject obj = json.object(); Q_ASSERT(obj.contains("products")); QJsonValue productsValue = obj.value("products"); Q_ASSERT(productsValue.isArray()); QJsonArray productsArray = productsValue.toArray(); QStringList products; for (QJsonValue product : productsArray) { Q_ASSERT(product.isString()); products.append(product.toString()); } SettingsHolder::instance()->setIapProducts(products); IAPHandler* ipaHandler = IAPHandler::instance(); Q_ASSERT(ipaHandler); connect(ipaHandler, &IAPHandler::productsRegistered, this, &TaskIOSProducts::completed); ipaHandler->registerProducts(products); }); } mozilla-vpn-client-2.2.0/src/platforms/ios/taskiosproducts.h000066400000000000000000000007721404202232700242430ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef TASKIOSPRODUCTS_H #define TASKIOSPRODUCTS_H #include "task.h" #include class TaskIOSProducts final : public Task { Q_DISABLE_COPY_MOVE(TaskIOSProducts) public: TaskIOSProducts(); ~TaskIOSProducts(); void run(MozillaVPN* vpn) override; }; #endif // TASKIOSPRODUCTS_H mozilla-vpn-client-2.2.0/src/platforms/linux/000077500000000000000000000000001404202232700211705ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/platforms/linux/backendlogsobserver.cpp000066400000000000000000000021021404202232700257130ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "backendlogsobserver.h" #include "leakdetector.h" #include "logger.h" #include #include namespace { Logger logger({LOG_LINUX, LOG_CONTROLLER}, "BackendLogsObserver"); } BackendLogsObserver::BackendLogsObserver( QObject* parent, std::function&& callback) : QObject(parent), m_callback(std::move(callback)) { MVPN_COUNT_CTOR(BackendLogsObserver); } BackendLogsObserver::~BackendLogsObserver() { MVPN_COUNT_DTOR(BackendLogsObserver); } void BackendLogsObserver::completed(QDBusPendingCallWatcher* call) { QDBusPendingReply reply = *call; if (reply.isError()) { logger.log() << "Error received from the DBus service"; m_callback("Failed to retrieve logs from the mozillavpn linuxdaemon."); return; } QString status = reply.argumentAt<0>(); m_callback(status); } mozilla-vpn-client-2.2.0/src/platforms/linux/backendlogsobserver.h000066400000000000000000000013631404202232700253700ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef BACKENDLOGSOBSERVER_H #define BACKENDLOGSOBSERVER_H #include #include class QDBusPendingCallWatcher; class BackendLogsObserver final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(BackendLogsObserver) public: BackendLogsObserver(QObject* parent, std::function&& callback); ~BackendLogsObserver(); public slots: void completed(QDBusPendingCallWatcher* call); private: std::function m_callback; }; #endif // BACKENDLOGSOBSERVER_H mozilla-vpn-client-2.2.0/src/platforms/linux/daemon/000077500000000000000000000000001404202232700224335ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/platforms/linux/daemon/dbusservice.cpp000066400000000000000000000207261404202232700254640ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "dbusservice.h" #include "dbus_adaptor.h" #include "leakdetector.h" #include "logger.h" #include "loghandler.h" #include "polkithelper.h" #include "wgquickprocess.h" #include #include #include #include #include #include #include #include #if defined(__cplusplus) extern "C" { #endif #include "../../3rdparty/wireguard-tools/contrib/embeddable-wg-library/wireguard.h" #if defined(__cplusplus) } #endif namespace { Logger logger(LOG_LINUX, "DBusService"); } DBusService::DBusService(QObject* parent) : Daemon(parent) { MVPN_COUNT_CTOR(DBusService); } DBusService::~DBusService() { MVPN_COUNT_DTOR(DBusService); } WireguardUtils* DBusService::wgutils() { if (!m_wgutils) { m_wgutils = new WireguardUtilsLinux(this); } return m_wgutils; } void DBusService::setAdaptor(DbusAdaptor* adaptor) { Q_ASSERT(!m_adaptor); m_adaptor = adaptor; } bool DBusService::checkInterface() { logger.log() << "Checking interface"; wg_device* device = nullptr; if (wg_get_device(&device, WG_INTERFACE) == 0) { logger.log() << "Device already exists. Let's remove it."; wg_free_device(device); if (wg_del_device(WG_INTERFACE) != 0) { logger.log() << "Failed to remove the device."; return false; } } return true; } QString DBusService::version() { logger.log() << "Version request"; return PROTOCOL_VERSION; } bool DBusService::activate(const QString& jsonConfig) { logger.log() << "Activate"; if (!PolkitHelper::instance()->checkAuthorization( "org.mozilla.vpn.activate")) { logger.log() << "Polkit rejected"; return false; } QJsonDocument json = QJsonDocument::fromJson(jsonConfig.toLocal8Bit()); if (!json.isObject()) { logger.log() << "Invalid input"; return false; } QJsonObject obj = json.object(); InterfaceConfig config; if (!parseConfig(obj, config)) { logger.log() << "Invalid configuration"; return false; } return Daemon::activate(config); } bool DBusService::deactivate(bool emitSignals) { logger.log() << "Deactivate"; return Daemon::deactivate(emitSignals); } QString DBusService::status() { return QString(getStatus()); } QByteArray DBusService::getStatus() { logger.log() << "Status request"; QJsonObject json; wg_device* device = nullptr; if (wg_get_device(&device, WG_INTERFACE) != 0) { logger.log() << "Unable to get device"; json.insert("status", QJsonValue(false)); return QJsonDocument(json).toJson(QJsonDocument::Compact); } uint64_t txBytes = 0; uint64_t rxBytes = 0; wg_peer* peer; wg_for_each_peer(device, peer) { txBytes += peer->tx_bytes; rxBytes += peer->rx_bytes; } wg_free_device(device); json.insert("status", QJsonValue(true)); json.insert("serverIpv4Gateway", QJsonValue(m_lastConfig.m_serverIpv4Gateway)); json.insert("txBytes", QJsonValue(double(txBytes))); json.insert("rxBytes", QJsonValue(double(rxBytes))); return QJsonDocument(json).toJson(QJsonDocument::Compact); } QString DBusService::getLogs() { logger.log() << "Log request"; return Daemon::logs(); } bool DBusService::run(Op op, const InterfaceConfig& config) { QStringList addresses; for (const IPAddressRange& ip : config.m_allowedIPAddressRanges) { addresses.append(ip.toString()); } return WgQuickProcess::run( op, config.m_privateKey, config.m_deviceIpv4Address, config.m_deviceIpv6Address, config.m_serverIpv4Gateway, config.m_serverIpv6Gateway, config.m_serverPublicKey, config.m_serverIpv4AddrIn, config.m_serverIpv6AddrIn, addresses.join(", "), config.m_serverPort, config.m_ipv6Enabled); } static inline bool endpointStringToSockaddr(const QString& address, int port, struct sockaddr* endpoint) { QString portString = QString::number(port); struct addrinfo hints; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = IPPROTO_UDP; struct addrinfo* resolved = nullptr; int retries = 15; for (unsigned int timeout = 1000000;; timeout = std::min((unsigned int)20000000, timeout * 6 / 5)) { int rv = getaddrinfo(address.toLocal8Bit(), portString.toLocal8Bit(), &hints, &resolved); if (!rv) { break; } /* The set of return codes that are "permanent failures". All other * possibilities are potentially transient. * * This is according to https://sourceware.org/glibc/wiki/NameResolver which * states: "From the perspective of the application that calls getaddrinfo() * it perhaps doesn't matter that much since EAI_FAIL, EAI_NONAME and * EAI_NODATA are all permanent failure codes and the causes are all * permanent failures in the sense that there is no point in retrying * later." * * So this is what we do, except FreeBSD removed EAI_NODATA some time ago, * so that's conditional. */ if (rv == EAI_NONAME || rv == EAI_FAIL || #ifdef EAI_NODATA rv == EAI_NODATA || #endif (retries >= 0 && !retries--)) { logger.log() << "Failed to resolve the address endpoint"; return false; } logger.log() << "Trying again in" << (timeout / 1000000.0) << "seconds"; usleep(timeout); } if ((resolved->ai_family == AF_INET && resolved->ai_addrlen == sizeof(struct sockaddr_in)) || (resolved->ai_family == AF_INET6 && resolved->ai_addrlen == sizeof(struct sockaddr_in6))) { memcpy(endpoint, resolved->ai_addr, resolved->ai_addrlen); freeaddrinfo(resolved); return true; } logger.log() << "Invalid endpoint" << address; freeaddrinfo(resolved); return false; } bool DBusService::switchServer(const InterfaceConfig& config) { logger.log() << "Switching server"; wg_device* device = static_cast(calloc(1, sizeof(*device))); if (!device) { logger.log() << "Allocation failure"; return false; } auto guard = qScopeGuard([&] { wg_free_device(device); }); strncpy(device->name, WG_INTERFACE, IFNAMSIZ - 1); device->name[IFNAMSIZ - 1] = '\0'; wg_peer* peer = static_cast(calloc(1, sizeof(*peer))); if (!peer) { logger.log() << "Allocation failure"; return false; } peer->flags = (wg_peer_flags)(WGPEER_HAS_PUBLIC_KEY | WGPEER_REPLACE_ALLOWEDIPS); device->first_peer = device->last_peer = peer; device->flags = WGDEVICE_REPLACE_PEERS; if (!endpointStringToSockaddr(config.m_serverIpv4AddrIn, config.m_serverPort, &peer->endpoint.addr)) { return false; } wg_key_from_base64(peer->public_key, config.m_serverPublicKey.toLocal8Bit()); for (const IPAddressRange& ip : config.m_allowedIPAddressRanges) { wg_allowedip* allowedip = static_cast(calloc(1, sizeof(*allowedip))); if (!allowedip) { logger.log() << "Allocation failure"; return false; } if (!peer->first_allowedip) { peer->first_allowedip = allowedip; } else { peer->last_allowedip->next_allowedip = allowedip; } peer->last_allowedip = allowedip; allowedip->cidr = ip.range(); bool ok = false; if (ip.type() == IPAddressRange::IPv4) { allowedip->family = AF_INET; ok = inet_pton(AF_INET, ip.ipAddress().toLocal8Bit(), &allowedip->ip4) == 1; } else if (ip.type() == IPAddressRange::IPv6) { allowedip->family = AF_INET6; ok = inet_pton(AF_INET6, ip.ipAddress().toLocal8Bit(), &allowedip->ip6) == 1; } else { logger.log() << "Invalid IPAddressRange type"; return false; } if (!ok) { logger.log() << "Invalid IP address:" << ip.ipAddress(); return false; } } if (wg_set_device(device) != 0) { logger.log() << "Failed to set the new peer"; return false; } return true; } bool DBusService::supportServerSwitching(const InterfaceConfig& config) const { return m_lastConfig.m_privateKey == config.m_privateKey && m_lastConfig.m_deviceIpv4Address == config.m_deviceIpv4Address && m_lastConfig.m_deviceIpv6Address == config.m_deviceIpv6Address && m_lastConfig.m_serverIpv4Gateway == config.m_serverIpv4Gateway && m_lastConfig.m_serverIpv6Gateway == config.m_serverIpv6Gateway; } mozilla-vpn-client-2.2.0/src/platforms/linux/daemon/dbusservice.h000066400000000000000000000023671404202232700251320ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef DBUSSERVICE_H #define DBUSSERVICE_H #include "daemon/daemon.h" #include "wireguardutilslinux.h" class DbusAdaptor; class DBusService final : public Daemon { Q_OBJECT Q_DISABLE_COPY_MOVE(DBusService) Q_CLASSINFO("D-Bus Interface", "org.mozilla.vpn.dbus") public: DBusService(QObject* parent); ~DBusService(); void setAdaptor(DbusAdaptor* adaptor); bool checkInterface(); using Daemon::activate; public slots: bool activate(const QString& jsonConfig); bool deactivate(bool emitSignals = true) override; QString status(); QString version(); QString getLogs(); protected: bool run(Op op, const InterfaceConfig& config) override; bool supportServerSwitching(const InterfaceConfig& config) const override; bool switchServer(const InterfaceConfig& config) override; bool supportWGUtils() const override { return true; } WireguardUtils* wgutils() override; QByteArray getStatus() override; private: DbusAdaptor* m_adaptor = nullptr; WireguardUtilsLinux* m_wgutils = nullptr; }; #endif // DBUSSERVICE_H mozilla-vpn-client-2.2.0/src/platforms/linux/daemon/linuxdaemon.cpp000066400000000000000000000033401404202232700254620ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "command.h" #include "dbusservice.h" #include "dbus_adaptor.h" #include "leakdetector.h" #include "logger.h" #include "loghandler.h" #include "signalhandler.h" namespace { Logger logger(LOG_LINUX, "main"); } class CommandLinuxDaemon final : public Command { public: explicit CommandLinuxDaemon(QObject* parent) : Command(parent, "linuxdaemon", "Starts the linux daemon") { MVPN_COUNT_CTOR(CommandLinuxDaemon); } ~CommandLinuxDaemon() { MVPN_COUNT_DTOR(CommandLinuxDaemon); } int run(QStringList& tokens) override { Q_ASSERT(!tokens.isEmpty()); return runCommandLineApp([&]() { DBusService* dbus = new DBusService(qApp); DbusAdaptor* adaptor = new DbusAdaptor(dbus); dbus->setAdaptor(adaptor); QDBusConnection connection = QDBusConnection::systemBus(); logger.log() << "Connecting to DBus..."; if (!connection.registerService("org.mozilla.vpn.dbus") || !connection.registerObject("/", dbus)) { logger.log() << "Connection failed - name:" << connection.lastError().name() << "message:" << connection.lastError().message(); return 1; } if (!dbus->checkInterface()) { return 1; } SignalHandler sh; QObject::connect(&sh, &SignalHandler::quitRequested, [&]() { dbus->deactivate(); qApp->quit(); }); logger.log() << "Ready!"; return qApp->exec(); }); } }; static Command::RegistrationProxy s_commandLinuxDaemon; mozilla-vpn-client-2.2.0/src/platforms/linux/daemon/org.mozilla.vpn.conf000066400000000000000000000015731404202232700263470ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/platforms/linux/daemon/org.mozilla.vpn.dbus.service000066400000000000000000000001311404202232700300030ustar00rootroot00000000000000[D-BUS Service] User=root Name=org.mozilla.vpn.dbus Exec=/usr/bin/mozillavpn linuxdaemon mozilla-vpn-client-2.2.0/src/platforms/linux/daemon/org.mozilla.vpn.dbus.xml000066400000000000000000000015111404202232700271460ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/platforms/linux/daemon/org.mozilla.vpn.policy000066400000000000000000000014331404202232700267140ustar00rootroot00000000000000 Activate the Mozilla VPN Activate the Mozilla VPN no auth_admin Deactivate the Mozilla VPN Deactivate the Mozilla VPN no auth_admin mozilla-vpn-client-2.2.0/src/platforms/linux/daemon/polkithelper.cpp000066400000000000000000000030511404202232700256400ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "polkithelper.h" #include "polkit/polkit.h" #include class Helper final { public: Helper() = default; ~Helper() { if (m_error) { g_error_free(m_error); } if (m_subject) { g_object_unref(m_subject); } if (m_result) { g_object_unref(m_result); } } public: GError* m_error = nullptr; PolkitSubject* m_subject = nullptr; PolkitAuthorizationResult* m_result = nullptr; }; // static PolkitHelper* PolkitHelper::instance() { static PolkitHelper s_instance; return &s_instance; } bool PolkitHelper::checkAuthorization(const QString& actionId) { qDebug() << "Check Authorization for" << actionId; Helper h; PolkitAuthority* authority = polkit_authority_get_sync(NULL, &h.m_error); if (h.m_error) { qDebug() << "Fail to generate a polkit authority object:" << h.m_error->message; return false; } h.m_subject = polkit_unix_process_new_for_owner(getpid(), 0, -1); h.m_result = polkit_authority_check_authorization_sync( authority, h.m_subject, actionId.toLatin1().data(), nullptr, POLKIT_CHECK_AUTHORIZATION_FLAGS_ALLOW_USER_INTERACTION, nullptr, &h.m_error); if (h.m_error) { qDebug() << "Authorization sync failed:" << h.m_error->message; return false; } return polkit_authorization_result_get_is_authorized(h.m_result); } mozilla-vpn-client-2.2.0/src/platforms/linux/daemon/polkithelper.h000066400000000000000000000010171404202232700253050ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef POLKITHELPER_H #define POLKITHELPER_H #include class PolkitHelper final { public: static PolkitHelper* instance(); bool checkAuthorization(const QString& actionId); private: PolkitHelper() = default; ~PolkitHelper() = default; Q_DISABLE_COPY(PolkitHelper) }; #endif // POLKITHELPER_H mozilla-vpn-client-2.2.0/src/platforms/linux/daemon/wireguardutilslinux.cpp000066400000000000000000000027051404202232700272750ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "wireguardutilslinux.h" #include "leakdetector.h" #include "logger.h" // Import wireguard C library for Linux #if defined(__cplusplus) extern "C" { #endif #include "../../3rdparty/wireguard-tools/contrib/embeddable-wg-library/wireguard.h" #if defined(__cplusplus) } #endif // End import wireguard namespace { Logger logger(LOG_LINUX, "WireguardUtilsLinux"); } WireguardUtilsLinux::WireguardUtilsLinux(QObject* parent) : WireguardUtils(parent) { MVPN_COUNT_CTOR(WireguardUtilsLinux); logger.log() << "WireguardUtilsLinux created."; } WireguardUtilsLinux::~WireguardUtilsLinux() { MVPN_COUNT_DTOR(WireguardUtilsLinux); logger.log() << "WireguardUtilsLinux destroyed."; } bool WireguardUtilsLinux::interfaceExists() { // As currentInterfaces only gets wireguard interfaces, this method // also confirms an interface as being a wireguard interface. return currentInterfaces().contains(WG_INTERFACE); }; QStringList WireguardUtilsLinux::currentInterfaces() { char* deviceNames = wg_list_device_names(); QStringList devices; if (!deviceNames) { return devices; } char* deviceName; size_t len; wg_for_each_device_name(deviceNames, deviceName, len) { devices.append(deviceName); } free(deviceNames); return devices; } mozilla-vpn-client-2.2.0/src/platforms/linux/daemon/wireguardutilslinux.h000066400000000000000000000010771404202232700267430ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WIREGUARDUTILSLINUX_H #define WIREGUARDUTILSLINUX_H #include "daemon/wireguardutils.h" #include class WireguardUtilsLinux final : public WireguardUtils { public: WireguardUtilsLinux(QObject* parent); ~WireguardUtilsLinux(); bool interfaceExists() override; QStringList currentInterfaces() override; }; #endif // WIREGUARDUTILSLINUX_H mozilla-vpn-client-2.2.0/src/platforms/linux/dbusclient.cpp000066400000000000000000000112471404202232700240350ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "dbusclient.h" #include "ipaddressrange.h" #include "leakdetector.h" #include "logger.h" #include "models/device.h" #include "models/keys.h" #include "models/server.h" #include "mozillavpn.h" #include "settingsholder.h" #include #include #include constexpr const char* DBUS_SERVICE = "org.mozilla.vpn.dbus"; constexpr const char* DBUS_PATH = "/"; namespace { Logger logger(LOG_LINUX, "DBusClient"); } DBusClient::DBusClient(QObject* parent) : QObject(parent) { MVPN_COUNT_CTOR(DBusClient); m_dbus = new OrgMozillaVpnDbusInterface(DBUS_SERVICE, DBUS_PATH, QDBusConnection::systemBus(), this); connect(m_dbus, &OrgMozillaVpnDbusInterface::connected, this, &DBusClient::connected); connect(m_dbus, &OrgMozillaVpnDbusInterface::disconnected, this, &DBusClient::disconnected); } DBusClient::~DBusClient() { MVPN_COUNT_DTOR(DBusClient); } QDBusPendingCallWatcher* DBusClient::version() { logger.log() << "Version via DBus"; QDBusPendingReply reply = m_dbus->version(); QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, &QDBusPendingCallWatcher::deleteLater); return watcher; } QDBusPendingCallWatcher* DBusClient::activate( const Server& server, const Device* device, const Keys* keys, const QList& allowedIPAddressRanges) { QJsonObject json; json.insert("privateKey", QJsonValue(keys->privateKey())); json.insert("deviceIpv4Address", QJsonValue(device->ipv4Address())); json.insert("deviceIpv6Address", QJsonValue(device->ipv6Address())); json.insert("serverIpv4Gateway", QJsonValue(server.ipv4Gateway())); json.insert("serverIpv6Gateway", QJsonValue(server.ipv6Gateway())); json.insert("serverPublicKey", QJsonValue(server.publicKey())); json.insert("serverIpv4AddrIn", QJsonValue(server.ipv4AddrIn())); json.insert("serverIpv6AddrIn", QJsonValue(server.ipv6AddrIn())); json.insert("serverPort", QJsonValue((double)server.choosePort())); json.insert("ipv6Enabled", QJsonValue(SettingsHolder::instance()->ipv6Enabled())); QJsonArray allowedIPAddesses; for (const IPAddressRange& i : allowedIPAddressRanges) { QJsonObject range; range.insert("address", QJsonValue(i.ipAddress())); range.insert("range", QJsonValue((double)i.range())); range.insert("isIpv6", QJsonValue(i.type() == IPAddressRange::IPv6)); allowedIPAddesses.append(range); }; json.insert("allowedIPAddressRanges", allowedIPAddesses); logger.log() << "Activate via DBus"; QDBusPendingReply reply = m_dbus->activate(QJsonDocument(json).toJson(QJsonDocument::Compact)); QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, &QDBusPendingCallWatcher::deleteLater); return watcher; } QDBusPendingCallWatcher* DBusClient::deactivate() { logger.log() << "Deactivate via DBus"; QDBusPendingReply reply = m_dbus->deactivate(); QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, &QDBusPendingCallWatcher::deleteLater); return watcher; } QDBusPendingCallWatcher* DBusClient::status() { logger.log() << "Status via DBus"; QDBusPendingReply reply = m_dbus->status(); QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, &QDBusPendingCallWatcher::deleteLater); return watcher; } QDBusPendingCallWatcher* DBusClient::getLogs() { logger.log() << "Get logs via DBus"; QDBusPendingReply reply = m_dbus->getLogs(); QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, &QDBusPendingCallWatcher::deleteLater); return watcher; } QDBusPendingCallWatcher* DBusClient::cleanupLogs() { logger.log() << "Cleanup logs via DBus"; QDBusPendingReply reply = m_dbus->cleanupLogs(); QDBusPendingCallWatcher* watcher = new QDBusPendingCallWatcher(reply, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, &QDBusPendingCallWatcher::deleteLater); return watcher; } mozilla-vpn-client-2.2.0/src/platforms/linux/dbusclient.h000066400000000000000000000020341404202232700234740ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef DBUSCLIENT_H #define DBUSCLIENT_H #include "dbus_interface.h" #include #include class Server; class Device; class Keys; class IPAddressRange; class QDBusPendingCallWatcher; class DBusClient final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(DBusClient) public: DBusClient(QObject* parent); ~DBusClient(); QDBusPendingCallWatcher* version(); QDBusPendingCallWatcher* activate( const Server& server, const Device* device, const Keys* keys, const QList& allowedIPAddressRanges); QDBusPendingCallWatcher* deactivate(); QDBusPendingCallWatcher* status(); QDBusPendingCallWatcher* getLogs(); QDBusPendingCallWatcher* cleanupLogs(); signals: void connected(); void disconnected(); private: OrgMozillaVpnDbusInterface* m_dbus; }; #endif // DBUSCLIENT_H mozilla-vpn-client-2.2.0/src/platforms/linux/linuxcontroller.cpp000066400000000000000000000124241404202232700251420ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "linuxcontroller.h" #include "backendlogsobserver.h" #include "dbusclient.h" #include "errorhandler.h" #include "leakdetector.h" #include "logger.h" #include "models/device.h" #include "models/keys.h" #include "models/server.h" #include "mozillavpn.h" #include #include #include #include #include #include namespace { Logger logger({LOG_LINUX, LOG_CONTROLLER}, "LinuxController"); } LinuxController::LinuxController() { MVPN_COUNT_CTOR(LinuxController); m_dbus = new DBusClient(this); connect(m_dbus, &DBusClient::connected, this, &LinuxController::connected); connect(m_dbus, &DBusClient::disconnected, this, &LinuxController::disconnected); } LinuxController::~LinuxController() { MVPN_COUNT_DTOR(LinuxController); } void LinuxController::initialize(const Device* device, const Keys* keys) { Q_UNUSED(device); Q_UNUSED(keys); QDBusPendingCallWatcher* watcher = m_dbus->status(); connect(watcher, &QDBusPendingCallWatcher::finished, this, &LinuxController::initializeCompleted); } void LinuxController::initializeCompleted(QDBusPendingCallWatcher* call) { QDBusPendingReply reply = *call; if (reply.isError()) { logger.log() << "Error received from the DBus service"; emit initialized(false, false, QDateTime()); return; } QString status = reply.argumentAt<0>(); logger.log() << "Status:" << status; QJsonDocument json = QJsonDocument::fromJson(status.toLocal8Bit()); Q_ASSERT(json.isObject()); QJsonObject obj = json.object(); Q_ASSERT(obj.contains("status")); QJsonValue statusValue = obj.value("status"); Q_ASSERT(statusValue.isBool()); emit initialized(true, statusValue.toBool(), QDateTime::currentDateTime()); } void LinuxController::activate( const Server& server, const Device* device, const Keys* keys, const QList& allowedIPAddressRanges, const QList& vpnDisabledApps, Reason reason) { Q_UNUSED(reason); Q_UNUSED(vpnDisabledApps); logger.log() << "LinuxController activated"; connect(m_dbus->activate(server, device, keys, allowedIPAddressRanges), &QDBusPendingCallWatcher::finished, this, &LinuxController::operationCompleted); } void LinuxController::deactivate(Reason reason) { logger.log() << "LinuxController deactivated"; if (reason == ReasonSwitching) { logger.log() << "No disconnect for quick server switching"; emit disconnected(); return; } connect(m_dbus->deactivate(), &QDBusPendingCallWatcher::finished, this, &LinuxController::operationCompleted); } void LinuxController::operationCompleted(QDBusPendingCallWatcher* call) { QDBusPendingReply reply = *call; if (reply.isError()) { logger.log() << "Error received from the DBus service"; MozillaVPN::instance()->errorHandle(ErrorHandler::ControllerError); emit disconnected(); return; } bool status = reply.argumentAt<0>(); if (status) { logger.log() << "DBus service says: all good."; // we will receive the connected/disconnected() signal; return; } logger.log() << "DBus service says: error."; MozillaVPN::instance()->errorHandle(ErrorHandler::ControllerError); emit disconnected(); } void LinuxController::checkStatus() { logger.log() << "Check status"; QDBusPendingCallWatcher* watcher = m_dbus->status(); connect(watcher, &QDBusPendingCallWatcher::finished, this, &LinuxController::checkStatusCompleted); } void LinuxController::checkStatusCompleted(QDBusPendingCallWatcher* call) { QDBusPendingReply reply = *call; if (reply.isError()) { logger.log() << "Error received from the DBus service"; return; } QString status = reply.argumentAt<0>(); logger.log() << "Status:" << status; QJsonDocument json = QJsonDocument::fromJson(status.toLocal8Bit()); Q_ASSERT(json.isObject()); QJsonObject obj = json.object(); Q_ASSERT(obj.contains("status")); QJsonValue statusValue = obj.value("status"); Q_ASSERT(statusValue.isBool()); if (!statusValue.toBool()) { logger.log() << "Unable to retrieve the status from the interface."; return; } Q_ASSERT(obj.contains("serverIpv4Gateway")); QJsonValue serverIpv4Gateway = obj.value("serverIpv4Gateway"); Q_ASSERT(serverIpv4Gateway.isString()); Q_ASSERT(obj.contains("txBytes")); QJsonValue txBytes = obj.value("txBytes"); Q_ASSERT(txBytes.isDouble()); Q_ASSERT(obj.contains("rxBytes")); QJsonValue rxBytes = obj.value("rxBytes"); Q_ASSERT(rxBytes.isDouble()); emit statusUpdated(serverIpv4Gateway.toString(), txBytes.toDouble(), rxBytes.toDouble()); } void LinuxController::getBackendLogs( std::function&& a_callback) { std::function callback = std::move(a_callback); QDBusPendingCallWatcher* watcher = m_dbus->getLogs(); connect(watcher, &QDBusPendingCallWatcher::finished, new BackendLogsObserver(this, std::move(callback)), &BackendLogsObserver::completed); } void LinuxController::cleanupBackendLogs() { m_dbus->cleanupLogs(); } mozilla-vpn-client-2.2.0/src/platforms/linux/linuxcontroller.h000066400000000000000000000023631404202232700246100ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef LINUXCONTROLLER_H #define LINUXCONTROLLER_H #include "controllerimpl.h" #include class DBusClient; class QDBusPendingCallWatcher; class LinuxController final : public ControllerImpl { Q_DISABLE_COPY_MOVE(LinuxController) public: LinuxController(); ~LinuxController(); void initialize(const Device* device, const Keys* keys) override; void activate(const Server& server, const Device* device, const Keys* keys, const QList& allowedIPAddressRanges, const QList& vpnDisabledApps, Reason reason) override; void deactivate(Reason reason) override; void checkStatus() override; void getBackendLogs(std::function&& callback) override; void cleanupBackendLogs() override; private slots: void checkStatusCompleted(QDBusPendingCallWatcher* call); void initializeCompleted(QDBusPendingCallWatcher* call); void operationCompleted(QDBusPendingCallWatcher* call); private: DBusClient* m_dbus = nullptr; }; #endif // LINUXCONTROLLER_H mozilla-vpn-client-2.2.0/src/platforms/linux/linuxcryptosettings.cpp000066400000000000000000000007531404202232700260620ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "cryptosettings.h" void CryptoSettings::resetKey() {} bool CryptoSettings::getKey(uint8_t key[CRYPTO_SETTINGS_KEY_SIZE]) { Q_UNUSED(key); return false; } // static CryptoSettings::Version CryptoSettings::getSupportedVersion() { return CryptoSettings::NoEncryption; } mozilla-vpn-client-2.2.0/src/platforms/linux/linuxdependencies.cpp000066400000000000000000000044701404202232700254070ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "linuxdependencies.h" #include "dbusclient.h" #include "logger.h" #include #include #include #include constexpr const char* WG_QUICK = "wg-quick"; namespace { Logger logger(LOG_LINUX, "LinuxDependencies"); void showAlert(const QString& message) { logger.log() << "Show alert:" << message; QMessageBox alert; alert.setText(message); alert.exec(); } bool findInPath(const char* what) { char* path = getenv("PATH"); Q_ASSERT(path); QStringList parts = QString(path).split(":"); for (const QString& part : parts) { QDir pathDir(part); QFileInfo file(pathDir.filePath(what)); if (file.exists()) { logger.log() << what << "found" << file.filePath(); return true; } } return false; } bool checkDaemonVersion() { logger.log() << "Check Daemon Version"; DBusClient* dbus = new DBusClient(nullptr); QDBusPendingCallWatcher* watcher = dbus->version(); bool completed = false; bool value = false; QObject::connect( watcher, &QDBusPendingCallWatcher::finished, [completed = &completed, value = &value](QDBusPendingCallWatcher* call) { *completed = true; QDBusPendingReply reply = *call; if (reply.isError()) { logger.log() << "DBus message received - error"; *value = false; return; } QString version = reply.argumentAt<0>(); *value = version == PROTOCOL_VERSION; logger.log() << "DBus message received - daemon version:" << version << " - current version:" << PROTOCOL_VERSION; }); while (!completed) { QCoreApplication::processEvents(); } delete dbus; return value; } } // namespace // static bool LinuxDependencies::checkDependencies() { char* path = getenv("PATH"); if (!path) { showAlert("No PATH env found."); return false; } if (!findInPath(WG_QUICK)) { showAlert("Unable to locate wg-quick"); return false; } if (!checkDaemonVersion()) { showAlert("mozillavpn linuxdaemon needs to be updated or restarted."); return false; } return true; } mozilla-vpn-client-2.2.0/src/platforms/linux/linuxdependencies.h000066400000000000000000000007751404202232700250600ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef LINUXDEPENDENCIES_H #define LINUXDEPENDENCIES_H #include class LinuxDependencies final { public: static bool checkDependencies(); private: LinuxDependencies() = default; ~LinuxDependencies() = default; Q_DISABLE_COPY(LinuxDependencies) }; #endif // LINUXDEPENDENCIES_H mozilla-vpn-client-2.2.0/src/platforms/linux/linuxnetworkwatcher.cpp000066400000000000000000000131761404202232700260330ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "linuxnetworkwatcher.h" #include "leakdetector.h" #include "logger.h" #include "timersingleshot.h" #include // https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NMDeviceType #ifndef NM_DEVICE_TYPE_WIFI # define NM_DEVICE_TYPE_WIFI 2 #endif // https://developer.gnome.org/NetworkManager/stable/nm-dbus-types.html#NM80211ApFlags // Wifi network has no security #ifndef NM_802_11_AP_SEC_NONE # define NM_802_11_AP_SEC_NONE 0x00000000 #endif // Wifi network has WEP (40 bits) #ifndef NM_802_11_AP_SEC_PAIR_WEP40 # define NM_802_11_AP_SEC_PAIR_WEP40 0x00000001 #endif // Wifi network has WEP (104 bits) #ifndef NM_802_11_AP_SEC_PAIR_WEP104 # define NM_802_11_AP_SEC_PAIR_WEP104 0x00000002 #endif constexpr const char* DBUS_NETWORKMANAGER = "org.freedesktop.NetworkManager"; namespace { Logger logger(LOG_LINUX, "LinuxNetworkWatcher"); } static inline bool checkSecurityFlags(int securityFlags) { return securityFlags == NM_802_11_AP_SEC_NONE || (securityFlags & NM_802_11_AP_SEC_PAIR_WEP40 || securityFlags & NM_802_11_AP_SEC_PAIR_WEP104); } LinuxNetworkWatcher::LinuxNetworkWatcher(QObject* parent) : NetworkWatcherImpl(parent) { MVPN_COUNT_CTOR(LinuxNetworkWatcher); } LinuxNetworkWatcher::~LinuxNetworkWatcher() { MVPN_COUNT_DTOR(LinuxNetworkWatcher); } void LinuxNetworkWatcher::initialize() { logger.log() << "initialize"; // Let's wait a few seconds to allow the UI to be fully loaded and shown. // This is not strictly needed, but it's better for user experience because // it makes the UI faster to appear, plus it gives a bit of delay between the // UI to appear and the first notification. TimerSingleShot::create(this, 2000, [this]() { logger.log() << "Retrieving the list of wifi network devices from NetworkManager"; // To know the NeworkManager DBus methods and properties, read the official // documentation: // https://developer.gnome.org/NetworkManager/stable/gdbus-org.freedesktop.NetworkManager.html QDBusInterface nm(DBUS_NETWORKMANAGER, "/org/freedesktop/NetworkManager", DBUS_NETWORKMANAGER, QDBusConnection::systemBus()); if (!nm.isValid()) { logger.log() << "Failed to connect to the network manager via system dbus"; return; } QDBusMessage msg = nm.call("GetDevices"); QDBusArgument arg = msg.arguments().at(0).value(); if (arg.currentType() != QDBusArgument::ArrayType) { logger.log() << "Expected an array of devices"; return; } QList paths = qdbus_cast >(arg); for (const QDBusObjectPath& path : paths) { QString devicePath = path.path(); QDBusInterface device(DBUS_NETWORKMANAGER, devicePath, "org.freedesktop.NetworkManager.Device", QDBusConnection::systemBus()); if (device.property("DeviceType").toInt() != NM_DEVICE_TYPE_WIFI) { continue; } logger.log() << "Found a wifi device:" << devicePath; m_devicePaths.append(devicePath); // Here we monitor the changes. QDBusConnection::systemBus().connect( DBUS_NETWORKMANAGER, devicePath, "org.freedesktop.DBus.Properties", "PropertiesChanged", this, SLOT(propertyChanged(QString, QVariantMap, QStringList))); } if (m_devicePaths.isEmpty()) { logger.log() << "No wifi devices found"; return; } // We could be already be activated. checkDevices(); }); } void LinuxNetworkWatcher::start() { logger.log() << "actived"; NetworkWatcherImpl::start(); checkDevices(); } void LinuxNetworkWatcher::propertyChanged(QString interface, QVariantMap properties, QStringList list) { Q_UNUSED(list); logger.log() << "Properties changed for interface" << interface; if (!isActive()) { logger.log() << "Not active. Ignoring the changes"; return; } if (!properties.contains("ActiveAccessPoint")) { logger.log() << "Access point did not changed. Ignoring the changes"; return; } checkDevices(); } void LinuxNetworkWatcher::checkDevices() { logger.log() << "Checking devices"; if (!isActive()) { logger.log() << "Not active"; return; } for (const QString& devicePath : m_devicePaths) { QDBusInterface wifiDevice(DBUS_NETWORKMANAGER, devicePath, "org.freedesktop.NetworkManager.Device.Wireless", QDBusConnection::systemBus()); // Check the access point path QString accessPointPath = wifiDevice.property("ActiveAccessPoint") .value() .path(); if (accessPointPath.isEmpty()) { logger.log() << "No access point found"; continue; } QDBusInterface ap(DBUS_NETWORKMANAGER, accessPointPath, "org.freedesktop.NetworkManager.AccessPoint", QDBusConnection::systemBus()); if (!checkSecurityFlags(ap.property("RsnFlags").toInt()) && !checkSecurityFlags(ap.property("WpaFlags").toInt())) { QString ssid = ap.property("Ssid").toString(); QString bssid = ap.property("HwAddress").toString(); // We have found 1 unsecured network. We don't need to check other wifi // network devices. emit unsecuredNetwork(ssid, bssid); break; } } } mozilla-vpn-client-2.2.0/src/platforms/linux/linuxnetworkwatcher.h000066400000000000000000000016671404202232700255020ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef LINUXNETWORKWATCHER_H #define LINUXNETWORKWATCHER_H #include "networkwatcherimpl.h" #include #include class LinuxNetworkWatcher final : public NetworkWatcherImpl { Q_OBJECT public: LinuxNetworkWatcher(QObject* parent); ~LinuxNetworkWatcher(); void initialize() override; void start() override; private slots: void propertyChanged(QString interface, QVariantMap properties, QStringList list); void checkDevices(); private: // We collect the list of DBus wifi network device paths during the // initialization. When a property of them changes, we check if the access // point is active and unsecure. QStringList m_devicePaths; }; #endif // LINUXNETWORKWATCHER_H mozilla-vpn-client-2.2.0/src/platforms/linux/linuxpingsendworker.cpp000066400000000000000000000051661404202232700260250ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "linuxpingsendworker.h" #include "leakdetector.h" #include "logger.h" #include #include #include #include #include #include #include namespace { Logger logger({LOG_LINUX, LOG_NETWORKING}, "LinuxPingSendWorker"); } LinuxPingSendWorker::LinuxPingSendWorker() { MVPN_COUNT_CTOR(LinuxPingSendWorker); } LinuxPingSendWorker::~LinuxPingSendWorker() { MVPN_COUNT_DTOR(LinuxPingSendWorker); } void LinuxPingSendWorker::sendPing(const QString& destination) { logger.log() << "LinuxPingSendWorker - start" << destination; struct in_addr dst; if (inet_aton(destination.toLocal8Bit().constData(), &dst) == 0) { logger.log() << "Lookup error"; emit pingFailed(); return; } Q_ASSERT(m_socket == 0); m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); if (m_socket < 0) { logger.log() << "Socket creation error"; emit pingFailed(); releaseObjects(); return; } struct sockaddr_in addr; memset(&addr, 0, sizeof addr); addr.sin_family = AF_INET; addr.sin_addr = dst; struct icmphdr packet; memset(&packet, 0, sizeof packet); packet.type = ICMP_ECHO; int rc = sendto(m_socket, &packet, sizeof packet, 0, (struct sockaddr*)&addr, sizeof addr); if (rc <= 0) { logger.log() << "Sending ping failed"; emit pingFailed(); releaseObjects(); return; } logger.log() << "Ping sent"; m_socketNotifier = new QSocketNotifier(m_socket, QSocketNotifier::Read, this); connect(m_socketNotifier, &QSocketNotifier::activated, [this]( #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) QSocketDescriptor socket, QSocketNotifier::Type #else int socket #endif ) { socklen_t slen = 0; unsigned char data[2048]; int rc = recvfrom(socket, data, sizeof data, 0, NULL, &slen); if (rc <= 0) { logger.log() << "Recvfrom failed"; emit pingFailed(); releaseObjects(); return; } logger.log() << "Ping reply received"; emit pingSucceeded(); releaseObjects(); }); } void LinuxPingSendWorker::releaseObjects() { if (m_socket > 0) { close(m_socket); m_socket = 0; } m_socket = 0; if (m_socketNotifier) { delete m_socketNotifier; m_socketNotifier = nullptr; } } mozilla-vpn-client-2.2.0/src/platforms/linux/linuxpingsendworker.h000066400000000000000000000013361404202232700254650ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef LINUXPINGSENDWORKER_H #define LINUXPINGSENDWORKER_H #include "pingsendworker.h" #include class QSocketNotifier; class LinuxPingSendWorker final : public PingSendWorker { Q_OBJECT Q_DISABLE_COPY_MOVE(LinuxPingSendWorker) public: LinuxPingSendWorker(); ~LinuxPingSendWorker(); public slots: void sendPing(const QString& destination) override; private: void releaseObjects(); private: QSocketNotifier* m_socketNotifier = nullptr; int m_socket = 0; }; #endif // LINUXPINGSENDWORKER_H mozilla-vpn-client-2.2.0/src/platforms/linux/linuxsystemtrayhandler.cpp000066400000000000000000000065711404202232700265470ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "platforms/linux/linuxsystemtrayhandler.h" #include "constants.h" #include "leakdetector.h" #include "logger.h" #include constexpr const char* DBUS_ITEM = "org.freedesktop.Notifications"; constexpr const char* DBUS_PATH = "/org/freedesktop/Notifications"; constexpr const char* DBUS_INTERFACE = "org.freedesktop.Notifications"; constexpr const char* ACTION_ID = "mozilla_vpn_notification"; namespace { Logger logger(LOG_LINUX, "LinuxSystemTrayHandler"); } // namespace // static bool LinuxSystemTrayHandler::requiredCustomImpl() { if (!QDBusConnection::sessionBus().isConnected()) { return false; } QDBusConnectionInterface* interface = QDBusConnection::sessionBus().interface(); if (!interface) { return false; } // This custom systemTrayHandler implementation is required only on Unity. QStringList registeredServices = interface->registeredServiceNames().value(); return registeredServices.contains("com.canonical.Unity"); } LinuxSystemTrayHandler::LinuxSystemTrayHandler(QObject* parent) : SystemTrayHandler(parent) { MVPN_COUNT_CTOR(LinuxSystemTrayHandler); QDBusConnection::sessionBus().connect(DBUS_ITEM, DBUS_PATH, DBUS_INTERFACE, "ActionInvoked", this, SLOT(actionInvoked(uint, QString))); } LinuxSystemTrayHandler::~LinuxSystemTrayHandler() { MVPN_COUNT_DTOR(LinuxSystemTrayHandler); } void LinuxSystemTrayHandler::showNotificationInternal(Message type, const QString& title, const QString& message, int timerMsec) { QString actionMessage; switch (type) { case None: return SystemTrayHandler::showNotificationInternal(type, title, message, timerMsec); case UnsecuredNetwork: actionMessage = qtTrId("vpn.toggle.on"); break; case CaptivePortalBlock: actionMessage = qtTrId("vpn.toggle.off"); break; case CaptivePortalUnblock: actionMessage = qtTrId("vpn.toggle.on"); break; default: Q_ASSERT(false); } m_lastMessage = type; emit notificationShown(title, message); QDBusInterface n(DBUS_ITEM, DBUS_PATH, DBUS_INTERFACE, QDBusConnection::sessionBus()); if (!n.isValid()) { qWarning("Failed to connect to the notification manager via system dbus"); return; } uint32_t replacesId = 0; // Don't replace. const char* appIcon = MVPN_ICON_PATH; QStringList actions{ACTION_ID, actionMessage}; QMap hints; QDBusReply reply = n.call("Notify", "Mozilla VPN", replacesId, appIcon, title, message, actions, hints, timerMsec); if (!reply.isValid()) { logger.log() << "Failed to show the notification"; } m_lastNotificationId = reply; } void LinuxSystemTrayHandler::actionInvoked(uint actionId, QString action) { logger.log() << "Notification clicked" << actionId << action; if (action == ACTION_ID && m_lastNotificationId == actionId) { messageClickHandle(); } } mozilla-vpn-client-2.2.0/src/platforms/linux/linuxsystemtrayhandler.h000066400000000000000000000015651404202232700262120ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef LINUXNOTIFICATIONHANDLER_H #define LINUXNOTIFICATIONHANDLER_H #include "systemtrayhandler.h" #include class LinuxSystemTrayHandler final : public SystemTrayHandler { Q_OBJECT Q_DISABLE_COPY_MOVE(LinuxSystemTrayHandler) public: static bool requiredCustomImpl(); LinuxSystemTrayHandler(QObject* parent); ~LinuxSystemTrayHandler(); private: void showNotificationInternal(Message type, const QString& title, const QString& message, int timerMsec) override; private slots: void actionInvoked(uint actionId, QString action); private: uint m_lastNotificationId = 0; }; #endif // LINUXNOTIFICATIONHANDLER_H mozilla-vpn-client-2.2.0/src/platforms/macos/000077500000000000000000000000001404202232700211335ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/platforms/macos/daemon/000077500000000000000000000000001404202232700223765ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/platforms/macos/daemon/macosdaemon.cpp000066400000000000000000000120621404202232700253710ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "macosdaemon.h" #include "leakdetector.h" #include "logger.h" #include "wgquickprocess.h" #include #include #include #include #include #include #include #include #include #include constexpr const char* LATEST_IPV4GATEWAY = "latestServerIpv4Gateway"; constexpr const char* LATEST_IPV6GATEWAY = "latestServerIpv6Gateway"; constexpr const char* LATEST_IPV6ENABLED = "latestIpv6Enabled"; namespace { Logger logger(LOG_MACOS, "MacOSDaemon"); MacOSDaemon* s_daemon = nullptr; } // namespace MacOSDaemon::MacOSDaemon() : Daemon(nullptr) { MVPN_COUNT_CTOR(MacOSDaemon); logger.log() << "Daemon created"; Q_ASSERT(s_daemon == nullptr); s_daemon = this; } MacOSDaemon::~MacOSDaemon() { MVPN_COUNT_DTOR(MacOSDaemon); logger.log() << "Daemon released"; Q_ASSERT(s_daemon == this); s_daemon = nullptr; } // static MacOSDaemon* MacOSDaemon::instance() { Q_ASSERT(s_daemon); return s_daemon; } QByteArray MacOSDaemon::getStatus() { logger.log() << "Status request"; QJsonObject obj; obj.insert("type", "status"); obj.insert("connected", m_connected); if (m_connected) { uint64_t txBytes = 0; uint64_t rxBytes = 0; QDir appPath(QCoreApplication::applicationDirPath()); appPath.cdUp(); appPath.cd("Resources"); appPath.cd("utils"); QString wgPath = appPath.filePath("wg"); QStringList arguments{"show", "all", "transfer"}; logger.log() << "Start:" << wgPath << " - arguments:" << arguments; QProcess wgProcess; wgProcess.start(wgPath, arguments); if (!wgProcess.waitForFinished(-1)) { logger.log() << "Error occurred" << wgProcess.errorString(); } else { QByteArray output = wgProcess.readAllStandardOutput(); logger.log() << "wg-quick stdout:" << Qt::endl << qUtf8Printable(output) << Qt::endl; logger.log() << "wg-quick stderr:" << Qt::endl << qUtf8Printable(wgProcess.readAllStandardError()) << Qt::endl; QStringList lines = QString(output).split("\n"); for (const QString& line : lines) { QStringList parts = line.split("\t"); if (parts.length() == 4) { rxBytes = parts[2].toLongLong(); txBytes = parts[3].toLongLong(); } } } obj.insert("status", true); obj.insert("serverIpv4Gateway", m_lastConfig.m_serverIpv4Gateway); obj.insert("date", m_connectionDate.toString()); obj.insert("txBytes", QJsonValue(double(txBytes))); obj.insert("rxBytes", QJsonValue(double(rxBytes))); } return QJsonDocument(obj).toJson(QJsonDocument::Compact); } bool MacOSDaemon::run(Daemon::Op op, const InterfaceConfig& config) { QStringList addresses; for (const IPAddressRange& ip : config.m_allowedIPAddressRanges) { addresses.append(ip.toString()); } QSettings settings; if (op == Daemon::Up) { settings.setValue(LATEST_IPV4GATEWAY, config.m_serverIpv4Gateway); settings.setValue(LATEST_IPV6GATEWAY, config.m_serverIpv6Gateway); settings.setValue(LATEST_IPV6ENABLED, config.m_ipv6Enabled); } else { settings.remove(LATEST_IPV4GATEWAY); settings.remove(LATEST_IPV6GATEWAY); settings.remove(LATEST_IPV6ENABLED); } return WgQuickProcess::run( op, config.m_privateKey, config.m_deviceIpv4Address, config.m_deviceIpv6Address, config.m_serverIpv4Gateway, config.m_serverIpv6Gateway, config.m_serverPublicKey, config.m_serverIpv4AddrIn, config.m_serverIpv6AddrIn, addresses.join(", "), config.m_serverPort, config.m_ipv6Enabled); } void MacOSDaemon::maybeCleanup() { logger.log() << "Cleanup"; QString app = WgQuickProcess::scriptPath(); QSettings settings; if (!settings.contains(LATEST_IPV4GATEWAY)) { return; } QString serverIpv4Gateway = settings.value(LATEST_IPV4GATEWAY).toString(); QString serverIpv6Gateway = settings.value(LATEST_IPV6GATEWAY).toString(); bool ipv6Enabled = settings.value(LATEST_IPV6ENABLED).toBool(); QStringList arguments; arguments.append("cleanup"); arguments.append(WG_INTERFACE); arguments.append(serverIpv4Gateway.toUtf8()); if (ipv6Enabled) { arguments.append(serverIpv6Gateway.toUtf8()); } logger.log() << "Start:" << app << " - arguments:" << arguments; QProcess wgQuickProcess; wgQuickProcess.start(app, arguments); if (!wgQuickProcess.waitForFinished(-1)) { logger.log() << "Error occurred:" << wgQuickProcess.errorString(); return; } logger.log() << "Execution finished" << wgQuickProcess.exitCode(); logger.log() << "wg-quick stdout:" << Qt::endl << qUtf8Printable(wgQuickProcess.readAllStandardOutput()) << Qt::endl; logger.log() << "wg-quick stderr:" << Qt::endl << qUtf8Printable(wgQuickProcess.readAllStandardError()) << Qt::endl; } mozilla-vpn-client-2.2.0/src/platforms/macos/daemon/macosdaemon.h000066400000000000000000000010521404202232700250330ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef MACOSDAEMON_H #define MACOSDAEMON_H #include "daemon.h" class MacOSDaemon final : public Daemon { public: MacOSDaemon(); ~MacOSDaemon(); static MacOSDaemon* instance(); QByteArray getStatus() override; void maybeCleanup(); protected: bool run(Op op, const InterfaceConfig& config) override; }; #endif // MACOSDAEMON_H mozilla-vpn-client-2.2.0/src/platforms/macos/daemon/macosdaemonserver.cpp000066400000000000000000000035421404202232700266230ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "macosdaemonserver.h" #include "commandlineparser.h" #include "daemon/daemonlocalserver.h" #include "leakdetector.h" #include "logger.h" #include "macosdaemon.h" #include "mozillavpn.h" #include "signalhandler.h" #include namespace { Logger logger(LOG_MACOS, "MacOSDaemonServer"); } MacOSDaemonServer::MacOSDaemonServer(QObject* parent) : Command(parent, "macosdaemon", "Activate the macos daemon") { MVPN_COUNT_CTOR(MacOSDaemonServer); } MacOSDaemonServer::~MacOSDaemonServer() { MVPN_COUNT_DTOR(MacOSDaemonServer); } int MacOSDaemonServer::run(QStringList& tokens) { Q_ASSERT(!tokens.isEmpty()); QString appName = tokens[0]; QCoreApplication app(CommandLineParser::argc(), CommandLineParser::argv()); QCoreApplication::setApplicationName("Mozilla VPN Daemon"); QCoreApplication::setApplicationVersion(APP_VERSION); if (tokens.length() > 1) { QList options; return CommandLineParser::unknownOption(this, appName, tokens[1], options, false); } MacOSDaemon daemon; daemon.maybeCleanup(); DaemonLocalServer server(qApp); if (!server.initialize()) { logger.log() << "Failed to initialize the server"; return 1; } // Signal handling for a proper shutdown. SignalHandler sh; QObject::connect(&sh, &SignalHandler::quitRequested, []() { MacOSDaemon::instance()->deactivate(); }); QObject::connect(&sh, &SignalHandler::quitRequested, &app, &QCoreApplication::quit, Qt::QueuedConnection); return app.exec(); } static Command::RegistrationProxy s_commandMacOSDaemon; mozilla-vpn-client-2.2.0/src/platforms/macos/daemon/macosdaemonserver.h000066400000000000000000000007531404202232700262710ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef MACOSDAEMONSERVER_H #define MACOSDAEMONSERVER_H #include "command.h" class MacOSDaemonServer final : public Command { public: explicit MacOSDaemonServer(QObject* parent); ~MacOSDaemonServer(); int run(QStringList& tokens) override; }; #endif // MACOSDAEMONSERVER_H mozilla-vpn-client-2.2.0/src/platforms/macos/macoscryptosettings.mm000066400000000000000000000077071404202232700256250ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "cryptosettings.h" #include "logger.h" #include constexpr const NSString* SERVICE = @"Mozilla VPN"; #import namespace { Logger logger({LOG_MACOS, LOG_MAIN}, "MacOSCryptoSettings"); bool initialized = false; QByteArray key; } // anonymous // static void CryptoSettings::resetKey() { logger.log() << "Reset the key in the keychain"; NSData* service = [SERVICE dataUsingEncoding:NSUTF8StringEncoding]; NSString* appId = [[NSBundle mainBundle] bundleIdentifier]; NSMutableDictionary* query = [[NSMutableDictionary alloc] init]; [query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; [query setObject:service forKey:(id)kSecAttrGeneric]; [query setObject:service forKey:(id)kSecAttrAccount]; [query setObject:appId forKey:(id)kSecAttrService]; SecItemDelete((CFDictionaryRef)query); [query release]; initialized = false; } // static bool CryptoSettings::getKey(uint8_t output[CRYPTO_SETTINGS_KEY_SIZE]) { #if defined(MVPN_IOS) || defined(MVPN_MACOS_NETWORKEXTENSION) || defined(MVPN_MACOS_DAEMON) if (!initialized) { initialized = true; logger.log() << "Retrieving the key from the keychain"; NSData* service = [SERVICE dataUsingEncoding:NSUTF8StringEncoding]; NSString* appId = [[NSBundle mainBundle] bundleIdentifier]; NSMutableDictionary* query = [[NSMutableDictionary alloc] init]; [query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; [query setObject:service forKey:(id)kSecAttrGeneric]; [query setObject:service forKey:(id)kSecAttrAccount]; [query setObject:appId forKey:(id)kSecAttrService]; [query setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData]; [query setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit]; NSData* keyData = NULL; OSStatus status = SecItemCopyMatching((CFDictionaryRef)query, (CFTypeRef*)(void*)&keyData); [query release]; if (status == noErr) { key = QByteArray::fromNSData(keyData); logger.log() << "Key found with length:" << key.length(); if (key.length() == CRYPTO_SETTINGS_KEY_SIZE) { memcpy(output, key.data(), CRYPTO_SETTINGS_KEY_SIZE); return true; } } logger.log() << "Key not found. Let's create it. Error:" << status; key = QByteArray(CRYPTO_SETTINGS_KEY_SIZE, 0x00); QRandomGenerator* rg = QRandomGenerator::system(); for (int i = 0; i < CRYPTO_SETTINGS_KEY_SIZE; ++i) { key[i] = rg->generate() & 0xFF; } query = [[NSMutableDictionary alloc] init]; [query setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; [query setObject:service forKey:(id)kSecAttrGeneric]; [query setObject:service forKey:(id)kSecAttrAccount]; [query setObject:appId forKey:(id)kSecAttrService]; SecItemDelete((CFDictionaryRef)query); keyData = key.toNSData(); [query setObject:keyData forKey:(id)kSecValueData]; status = SecItemAdd((CFDictionaryRef)query, NULL); if (status != noErr) { logger.log() << "Failed to store the key. Error:" << status; key = QByteArray(); } [query release]; } if (key.length() == CRYPTO_SETTINGS_KEY_SIZE) { memcpy(output, key.data(), CRYPTO_SETTINGS_KEY_SIZE); return true; } logger.log() << "Invalid key"; #else Q_UNUSED(output); #endif return false; } // static CryptoSettings::Version CryptoSettings::getSupportedVersion() { logger.log() << "Get supported settings method"; #if defined(MVPN_IOS) || defined(MVPN_MACOS_NETWORKEXTENSION) || defined(MVPN_MACOS_DAEMON) uint8_t key[CRYPTO_SETTINGS_KEY_SIZE]; if (getKey(key)) { logger.log() << "Encryption supported!"; return CryptoSettings::EncryptionChachaPolyV1; } #endif logger.log() << "No encryption"; return CryptoSettings::NoEncryption; } mozilla-vpn-client-2.2.0/src/platforms/macos/macosmenubar.cpp000066400000000000000000000051641404202232700243210ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "macosmenubar.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include #include #include namespace { Logger logger(LOG_MACOS, "MacOSManuBar"); MacOSMenuBar* s_instance = nullptr; } // namespace MacOSMenuBar::MacOSMenuBar() { MVPN_COUNT_CTOR(MacOSMenuBar); Q_ASSERT(!s_instance); s_instance = this; } MacOSMenuBar::~MacOSMenuBar() { MVPN_COUNT_DTOR(MacOSMenuBar); Q_ASSERT(s_instance == this); s_instance = nullptr; } // static MacOSMenuBar* MacOSMenuBar::instance() { Q_ASSERT(s_instance); return s_instance; } void MacOSMenuBar::initialize() { logger.log() << "Creating menubar"; MozillaVPN* vpn = MozillaVPN::instance(); m_menuBar = new QMenuBar(nullptr); //% "File" QMenu* fileMenu = m_menuBar->addMenu(qtTrId("menubar.file.title")); // Do not use qtTrId here! QAction* quit = fileMenu->addAction("quit", vpn->controller(), &Controller::quit); quit->setMenuRole(QAction::QuitRole); // Do not use qtTrId here! m_aboutAction = fileMenu->addAction("about.vpn", vpn, &MozillaVPN::requestAbout); m_aboutAction->setMenuRole(QAction::AboutRole); m_aboutAction->setVisible(vpn->state() == MozillaVPN::StateMain); // Do not use qtTrId here! m_preferencesAction = fileMenu->addAction("preferences", vpn, &MozillaVPN::requestSettings); m_preferencesAction->setMenuRole(QAction::PreferencesRole); m_preferencesAction->setVisible(vpn->state() == MozillaVPN::StateMain); m_closeAction = fileMenu->addAction("", vpn->controller(), &Controller::quit); m_closeAction->setShortcut(QKeySequence::Close); m_helpMenu = m_menuBar->addMenu(""); retranslate(); }; void MacOSMenuBar::controllerStateChanged() { MozillaVPN* vpn = MozillaVPN::instance(); m_preferencesAction->setVisible(vpn->state() == MozillaVPN::StateMain); m_aboutAction->setVisible(vpn->state() == MozillaVPN::StateMain); } void MacOSMenuBar::retranslate() { logger.log() << "Retranslate"; //% "Close" m_closeAction->setText(qtTrId("menubar.file.close")); //% "Help" m_helpMenu->setTitle(qtTrId("menubar.help.title")); for (QAction* action : m_helpMenu->actions()) { m_helpMenu->removeAction(action); } MozillaVPN* vpn = MozillaVPN::instance(); vpn->helpModel()->forEach([&](const char* nameId, int id) { m_helpMenu->addAction(qtTrId(nameId), [help = vpn->helpModel(), id]() { help->open(id); }); }); } mozilla-vpn-client-2.2.0/src/platforms/macos/macosmenubar.h000066400000000000000000000015311404202232700237600ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef MACOSMENUBAR_H #define MACOSMENUBAR_H #include class QAction; class QMenu; class QMenuBar; class MacOSMenuBar final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(MacOSMenuBar) public: MacOSMenuBar(); ~MacOSMenuBar(); static MacOSMenuBar* instance(); void initialize(); void retranslate(); QMenuBar* menuBar() const { return m_menuBar; } public slots: void controllerStateChanged(); private: QMenuBar* m_menuBar = nullptr; QAction* m_aboutAction = nullptr; QAction* m_preferencesAction = nullptr; QAction* m_closeAction = nullptr; QMenu* m_helpMenu = nullptr; }; #endif // MACOSMENUBAR_H mozilla-vpn-client-2.2.0/src/platforms/macos/macosnetworkwatcher.h000066400000000000000000000011261404202232700253760ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef MACOSNETWORKWATCHER_H #define MACOSNETWORKWATCHER_H #include "networkwatcherimpl.h" class MacOSNetworkWatcher final : public NetworkWatcherImpl { public: MacOSNetworkWatcher(QObject* parent); ~MacOSNetworkWatcher(); void initialize() override; void start() override; void checkInterface(); private: void* m_delegate = nullptr; }; #endif // MACOSNETWORKWATCHER_H mozilla-vpn-client-2.2.0/src/platforms/macos/macosnetworkwatcher.mm000066400000000000000000000062351404202232700255660ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "macosnetworkwatcher.h" #include "leakdetector.h" #include "logger.h" #import namespace { Logger logger(LOG_MACOS, "MacOSNetworkWatcher"); } @interface MacOSNetworkWatcherDelegate : NSObject { MacOSNetworkWatcher* m_watcher; } @end @implementation MacOSNetworkWatcherDelegate - (id)initWithObject:(MacOSNetworkWatcher*)watcher { self = [super init]; if (self) { m_watcher = watcher; } return self; } - (void)bssidDidChangeForWiFiInterfaceWithName:(NSString*)interfaceName { logger.log() << "BSSID changed!" << QString::fromNSString(interfaceName); if (m_watcher) { m_watcher->checkInterface(); } } @end MacOSNetworkWatcher::MacOSNetworkWatcher(QObject* parent) : NetworkWatcherImpl(parent) { MVPN_COUNT_CTOR(MacOSNetworkWatcher); } MacOSNetworkWatcher::~MacOSNetworkWatcher() { MVPN_COUNT_DTOR(MacOSNetworkWatcher); if (m_delegate) { CWWiFiClient* client = CWWiFiClient.sharedWiFiClient; if (!client) { logger.log() << "Unable to retrieve the CWWiFiClient shared instance"; return; } [client stopMonitoringAllEventsAndReturnError:nullptr]; [static_cast(m_delegate) dealloc]; m_delegate = nullptr; } } void MacOSNetworkWatcher::initialize() { // Nothing to do here } void MacOSNetworkWatcher::start() { NetworkWatcherImpl::start(); checkInterface(); if (m_delegate) { logger.log() << "Delegate already registered"; return; } CWWiFiClient* client = CWWiFiClient.sharedWiFiClient; if (!client) { logger.log() << "Unable to retrieve the CWWiFiClient shared instance"; return; } logger.log() << "Registering delegate"; m_delegate = [[MacOSNetworkWatcherDelegate alloc] initWithObject:this]; [client setDelegate:static_cast(m_delegate)]; [client startMonitoringEventWithType:CWEventTypeBSSIDDidChange error:nullptr]; } void MacOSNetworkWatcher::checkInterface() { logger.log() << "Checking interface"; if (!isActive()) { logger.log() << "Feature disabled"; return; } CWWiFiClient* client = CWWiFiClient.sharedWiFiClient; if (!client) { logger.log() << "Unable to retrieve the CWWiFiClient shared instance"; return; } CWInterface* interface = [client interface]; if (!interface) { logger.log() << "No default wifi interface"; return; } if (![interface powerOn]) { logger.log() << "The interface is off"; return; } NSString* ssidNS = [interface ssid]; if (!ssidNS) { logger.log() << "WiFi is not in used"; return; } QString ssid = QString::fromNSString(ssidNS); if (ssid.isEmpty()) { logger.log() << "WiFi doesn't have a valid SSID"; return; } CWSecurity security = [interface security]; if (security == kCWSecurityNone || security == kCWSecurityWEP) { logger.log() << "Unsecured network found!"; emit unsecuredNetwork(ssid, ssid); return; } logger.log() << "Secure WiFi interface"; } mozilla-vpn-client-2.2.0/src/platforms/macos/macospingsendworker.cpp000066400000000000000000000105571404202232700257330ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "macospingsendworker.h" #include "leakdetector.h" #include "logger.h" #include #include #include #include #include #include #include #include namespace { Logger logger({LOG_MACOS, LOG_NETWORKING}, "MacOSPingSendWorker"); int identifier() { return (getpid() & 0xFFFF); } // From ping.c implementation: u_short in_cksum(u_short* addr, int len) { int nleft, sum; u_short* w; union { u_short us; u_char uc[2]; } last; u_short answer; nleft = len; sum = 0; w = addr; /* * Our algorithm is simple, using a 32 bit accumulator (sum), we add * sequential 16 bit words to it, and at the end, fold back all the * carry bits from the top 16 bits into the lower 16 bits. */ while (nleft > 1) { sum += *w++; nleft -= 2; } /* mop up an odd byte, if necessary */ if (nleft == 1) { last.uc[0] = *(u_char*)w; last.uc[1] = 0; sum += last.us; } /* add back carry outs from top 16 bits to low 16 bits */ sum = (sum >> 16) + (sum & 0xffff); /* add hi 16 to low 16 */ sum += (sum >> 16); /* add carry */ answer = ~sum; /* truncate to 16 bits */ return (answer); } }; // namespace MacOSPingSendWorker::MacOSPingSendWorker() { MVPN_COUNT_CTOR(MacOSPingSendWorker); } MacOSPingSendWorker::~MacOSPingSendWorker() { MVPN_COUNT_DTOR(MacOSPingSendWorker); } void MacOSPingSendWorker::sendPing(const QString& destination) { logger.log() << "MacOSPingSendWorker - sending ping to:" << destination; Q_ASSERT(m_socket == 0); if (getuid()) { m_socket = socket(AF_INET, SOCK_DGRAM, IPPROTO_ICMP); } else { m_socket = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP); } if (m_socket < 0) { logger.log() << "Socket creation failed"; emit pingFailed(); releaseObjects(); return; } struct sockaddr_in dst; bzero(&dst, sizeof(dst)); dst.sin_family = AF_INET; dst.sin_len = sizeof(dst); if (inet_aton(destination.toLocal8Bit().constData(), &dst.sin_addr) == 0) { logger.log() << "DNS lookup failed"; emit pingFailed(); releaseObjects(); return; } struct icmp packet; bzero(&packet, sizeof packet); packet.icmp_type = ICMP_ECHO; packet.icmp_id = identifier(); packet.icmp_cksum = in_cksum((u_short*)&packet, sizeof(packet)); if (sendto(m_socket, (char*)&packet, sizeof(packet), 0, (struct sockaddr*)&dst, sizeof(dst)) != sizeof(packet)) { logger.log() << "Package sending failed"; emit pingFailed(); releaseObjects(); return; } logger.log() << "Ping sent"; m_socketNotifier = new QSocketNotifier(m_socket, QSocketNotifier::Read, this); connect(m_socketNotifier, &QSocketNotifier::activated, [this]( #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)) QSocketDescriptor socket, QSocketNotifier::Type #else int socket #endif ) { struct msghdr msg; bzero(&msg, sizeof(msg)); struct sockaddr_in addr; msg.msg_name = (caddr_t)&addr; msg.msg_namelen = sizeof(addr); struct iovec iov; msg.msg_iov = &iov; msg.msg_iovlen = 1; u_char packet[IP_MAXPACKET]; iov.iov_base = packet; iov.iov_len = IP_MAXPACKET; ssize_t rc = recvmsg(socket, &msg, 0); if (rc <= 0) { logger.log() << "Recvmsg failed"; emit pingFailed(); releaseObjects(); return; } struct ip* ip = (struct ip*)packet; int hlen = ip->ip_hl << 2; struct icmp* icmp = (struct icmp*)(((char*)packet) + hlen); if (icmp->icmp_type == ICMP_ECHOREPLY && icmp->icmp_id == identifier()) { logger.log() << "Ping reply received"; emit pingSucceeded(); releaseObjects(); } }); } void MacOSPingSendWorker::releaseObjects() { if (m_socket > 0) { close(m_socket); } m_socket = 0; if (m_socketNotifier) { delete m_socketNotifier; m_socketNotifier = nullptr; } } mozilla-vpn-client-2.2.0/src/platforms/macos/macospingsendworker.h000066400000000000000000000013121404202232700253650ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef MACOSPINGSENDWORKER_H #define MACOSPINGSENDWORKER_H #include "pingsendworker.h" class QSocketNotifier; class MacOSPingSendWorker final : public PingSendWorker { Q_OBJECT Q_DISABLE_COPY_MOVE(MacOSPingSendWorker) public: MacOSPingSendWorker(); ~MacOSPingSendWorker(); public slots: void sendPing(const QString& destination) override; private: void releaseObjects(); private: QSocketNotifier* m_socketNotifier = nullptr; int m_socket = 0; }; #endif // MACOSPINGSENDWORKER_H mozilla-vpn-client-2.2.0/src/platforms/macos/macosstartatbootwatcher.cpp000066400000000000000000000015311404202232700266060ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "macosstartatbootwatcher.h" #include "leakdetector.h" #include "logger.h" #include "macosutils.h" namespace { Logger logger(LOG_MACOS, "MacOSStartAtBootWatcher"); } MacOSStartAtBootWatcher::MacOSStartAtBootWatcher(bool startAtBoot) { MVPN_COUNT_CTOR(MacOSStartAtBootWatcher); logger.log() << "StartAtBoot watcher"; MacOSUtils::enableLoginItem(startAtBoot); } MacOSStartAtBootWatcher::~MacOSStartAtBootWatcher() { MVPN_COUNT_DTOR(MacOSStartAtBootWatcher); } void MacOSStartAtBootWatcher::startAtBootChanged(bool startAtBoot) { logger.log() << "StartAtBoot changed:" << startAtBoot; MacOSUtils::enableLoginItem(startAtBoot); } mozilla-vpn-client-2.2.0/src/platforms/macos/macosstartatbootwatcher.h000066400000000000000000000011261404202232700262530ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef MACOSSTARTATBOOTWATCHER_H #define MACOSSTARTATBOOTWATCHER_H #include class MacOSStartAtBootWatcher final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(MacOSStartAtBootWatcher) public: explicit MacOSStartAtBootWatcher(bool startAtBoot); ~MacOSStartAtBootWatcher(); public slots: void startAtBootChanged(bool value); }; #endif // MACOSSTARTATBOOTWATCHER_H mozilla-vpn-client-2.2.0/src/platforms/macos/macosutils.h000066400000000000000000000010241404202232700234640ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef MACOSUTILS_H #define MACOSUTILS_H #include #include class MacOSUtils final { public: static QString computerName(); static void enableLoginItem(bool startAtBoot); static void setDockClickHandler(); static void hideDockIcon(); static void showDockIcon(); }; #endif // MACOSUTILS_H mozilla-vpn-client-2.2.0/src/platforms/macos/macosutils.mm000066400000000000000000000045761404202232700236650ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "macosutils.h" #include "logger.h" #include "models/helpmodel.h" #include "qmlengineholder.h" #include #include #include #include #import #import namespace { Logger logger(LOG_MACOS, "MacOSUtils"); } // static QString MacOSUtils::computerName() { NSString* name = [[NSHost currentHost] localizedName]; return QString::fromNSString(name); } // static void MacOSUtils::enableLoginItem(bool startAtBoot) { logger.log() << "Enabling login-item"; NSString* appId = [[NSBundle mainBundle] bundleIdentifier]; NSString* loginItemAppId = QString("%1.login-item").arg(QString::fromNSString(appId)).toNSString(); CFStringRef cfs = (__bridge CFStringRef)loginItemAppId; Boolean ok = SMLoginItemSetEnabled(cfs, startAtBoot ? YES : NO); logger.log() << "Result: " << ok; } namespace { bool dockClickHandler(id self, SEL cmd, ...) { Q_UNUSED(self); Q_UNUSED(cmd); logger.log() << "Dock icon clicked."; QmlEngineHolder::instance()->showWindow(); return FALSE; } } // namespace // static void MacOSUtils::setDockClickHandler() { NSApplication* app = [NSApplication sharedApplication]; if (!app) { logger.log() << "No sharedApplication"; return; } id delegate = [app delegate]; if (!delegate) { logger.log() << "No delegate"; return; } Class delegateClass = [delegate class]; if (!delegateClass) { logger.log() << "No delegate class"; return; } SEL shouldHandle = sel_registerName("applicationShouldHandleReopen:hasVisibleWindows:"); if (class_getInstanceMethod(delegateClass, shouldHandle)) { if (!class_replaceMethod(delegateClass, shouldHandle, (IMP)dockClickHandler, "B@:")) { logger.log() << "Failed to replace the dock click handler"; } } else if (!class_addMethod(delegateClass, shouldHandle, (IMP)dockClickHandler, "B@:")) { logger.log() << "Failed to register the dock click handler"; } } void MacOSUtils::hideDockIcon() { [NSApp setActivationPolicy:NSApplicationActivationPolicyAccessory]; } void MacOSUtils::showDockIcon() { [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; } mozilla-vpn-client-2.2.0/src/platforms/wasm/000077500000000000000000000000001404202232700210005ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/platforms/wasm/networkrequests.qrc000066400000000000000000000004101404202232700247670ustar00rootroot00000000000000 networkrequests/authentication.json networkrequests/account.json networkrequests/servers.json networkrequests/ipinfo.json mozilla-vpn-client-2.2.0/src/platforms/wasm/networkrequests/000077500000000000000000000000001404202232700242655ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/platforms/wasm/networkrequests/account.json000066400000000000000000000017271404202232700266230ustar00rootroot00000000000000{ "email":"invalid@mozilla.com", "avatar":"https://mozillausercontent.com/00000000000000000000000000000000", "display_name":"", "devices":[ { "name":"WASM", "pubkey":"%%PUBLICKEY%%", "ipv4_address":"127.0.0.1/32", "ipv6_address":"::1/128", "created_at":"2021-02-03T09:13:59.762Z" }, { "name":"User’s MacBook Pro", "pubkey":"%%PUBLICKEY%%-mac", "ipv4_address":"127.0.0.1/32", "ipv6_address":"::1/128", "created_at":"2021-01-25T18:52:10.759Z" }, { "name":"Windows 10", "pubkey":"%%PUBLICKEY%%-windows", "ipv4_address":"127.0.0.1/32", "ipv6_address":"::1/128", "created_at":"2021-02-01T14:17:58.651Z" } ], "subscriptions":{ "vpn":{ "active":true, "created_at":"2019-12-06T16:50:58.485Z", "renews_on":"2021-02-17T00:01:32.000Z" } }, "max_devices":5 } mozilla-vpn-client-2.2.0/src/platforms/wasm/networkrequests/authentication.json000066400000000000000000000021471404202232700302030ustar00rootroot00000000000000{ "user":{ "email":"invalid@mozilla.com", "avatar":"https://mozillausercontent.com/00000000000000000000000000000000", "display_name":"", "devices":[ { "name":"WASM", "pubkey":"%%PUBLICKEY%%", "ipv4_address":"127.0.0.1/32", "ipv6_address":"::1/128", "created_at":"2021-01-25T18:52:10.759Z" }, { "name":"User’s MacBook Pro", "pubkey":"%%PUBLICKEY%%-mac", "ipv4_address":"127.0.0.1/32", "ipv6_address":"::1/128", "created_at":"2021-02-01T14:17:58.651Z" }, { "name":"Windows 10", "pubkey":"%%PUBLICKEY%%-windows", "ipv4_address":"127.0.0.1/32", "ipv6_address":"::1/128", "created_at":"2021-01-25T17:35:50.749Z" } ], "subscriptions":{ "vpn":{ "active":true, "created_at":"2019-12-06T16:50:58.485Z", "renews_on":"2021-02-17T00:01:32.000Z" } }, "max_devices":5 }, "token":"WASM TOKEN" } mozilla-vpn-client-2.2.0/src/platforms/wasm/networkrequests/ipinfo.json000066400000000000000000000001051404202232700264400ustar00rootroot00000000000000{"ip":"1.2.3.4","country":"FO","subdivision":"FO34","city":"Foobar"} mozilla-vpn-client-2.2.0/src/platforms/wasm/networkrequests/servers.json000066400000000000000000003177661404202232700266750ustar00rootroot00000000000000{"countries":[{"name":"Australia","code":"au","cities":[{"name":"Melbourne","code":"mel","latitude":-37.815018,"longitude":144.946014,"servers":[{"hostname":"au3-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":3,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"au4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":3,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"Sydney","code":"syd","latitude":-33.861481,"longitude":151.205475,"servers":[{"hostname":"au10-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"au11-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"au12-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"au14-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"au1-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":3,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"au2-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":3,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"au8-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"au9-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Austria","code":"at","cities":[{"name":"Vienna","code":"vie","latitude":48.210033,"longitude":16.363449,"servers":[{"hostname":"at4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"at5-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"at6-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"at7-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"at8-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"at9-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Belgium","code":"be","cities":[{"name":"Brussels","code":"bru","latitude":50.833333,"longitude":4.333333,"servers":[{"hostname":"be1-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"be2-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"be3-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"be4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Brazil","code":"br","cities":[{"name":"Sao Paulo","code":"sao","latitude":-23.533773,"longitude":-46.62529,"servers":[{"hostname":"br1-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Bulgaria","code":"bg","cities":[{"name":"Sofia","code":"sof","latitude":42.6833333,"longitude":23.3166667,"servers":[{"hostname":"bg4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"bg5-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"bg6-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"bg7-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Canada","code":"ca","cities":[{"name":"Montreal","code":"mtr","latitude":45.5053,"longitude":-73.5525,"servers":[{"hostname":"ca10-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ca11-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ca12-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ca13-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ca14-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ca15-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ca16-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ca17-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ca18-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ca19-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ca20-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ca21-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"Toronto","code":"tor","latitude":43.666667,"longitude":-79.416667,"servers":[{"hostname":"ca22-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ca23-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ca24-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ca25-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ca26-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ca27-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"Vancouver","code":"van","latitude":49.25,"longitude":-123.133333,"servers":[{"hostname":"ca2-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ca7-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Czech Republic","code":"cz","cities":[{"name":"Prague","code":"prg","latitude":50.083333,"longitude":14.466667,"servers":[{"hostname":"cz1-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"cz2-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"cz3-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"cz4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"cz5-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Denmark","code":"dk","cities":[{"name":"Copenhagen","code":"cph","latitude":55.666667,"longitude":12.583333,"servers":[{"hostname":"dk2-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"dk3-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"dk5-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"dk6-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"dk7-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Finland","code":"fi","cities":[{"name":"Helsinki","code":"hel","latitude":60.192059,"longitude":24.945831,"servers":[{"hostname":"fi1-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"fi2-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"fi3-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"France","code":"fr","cities":[{"name":"Paris","code":"par","latitude":48.866667,"longitude":2.333333,"servers":[{"hostname":"fr10-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"fr11-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"fr12-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"fr13-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"fr14-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"fr4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"fr5-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"fr6-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"fr7-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"fr8-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Germany","code":"de","cities":[{"name":"Dusseldorf","code":"dus","latitude":51.233334,"longitude":6.783333,"servers":[{"hostname":"de20-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":500,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"de21-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":500,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"de22-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":500,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"Frankfurt","code":"fra","latitude":50.110924,"longitude":8.682127,"servers":[{"hostname":"de12-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"de13-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"de14-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"de15-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"de16-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"de17-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Hong Kong","code":"hk","cities":[{"name":"Hong Kong","code":"hkg","latitude":22.2833333,"longitude":114.15,"servers":[{"hostname":"hk1-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":500,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"hk2-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"hk3-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"hk4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Hungary","code":"hu","cities":[{"name":"Budapest","code":"bud","latitude":47.5,"longitude":19.083333,"servers":[{"hostname":"hu3-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"hu4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"hu5-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Ireland","code":"ie","cities":[{"name":"Dublin","code":"dub","latitude":53.35014,"longitude":-6.266155,"servers":[{"hostname":"ie1-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ie2-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Italy","code":"it","cities":[{"name":"Milan","code":"mil","latitude":45.466667,"longitude":9.2,"servers":[{"hostname":"it4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"it5-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"it6-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"it7-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"it8-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Japan","code":"jp","cities":[{"name":"Tokyo","code":"tyo","latitude":35.685,"longitude":139.751389,"servers":[{"hostname":"jp10-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"jp11-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"jp12-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"jp13-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"jp6-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"jp7-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"jp8-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"jp9-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Latvia","code":"lv","cities":[{"name":"Riga","code":"rix","latitude":56.946285,"longitude":24.105078,"servers":[{"hostname":"lv1-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Luxembourg","code":"lu","cities":[{"name":"Luxembourg","code":"lux","latitude":49.611622,"longitude":6.131935,"servers":[{"hostname":"lu1-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"lu2-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Moldova","code":"md","cities":[{"name":"Chisinau","code":"kiv","latitude":47.00367,"longitude":28.907089,"servers":[{"hostname":"md1-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Netherlands","code":"nl","cities":[{"name":"Amsterdam","code":"ams","latitude":52.35,"longitude":4.916667,"servers":[{"hostname":"nl1-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"nl2-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"nl3-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"nl4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"nl5-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"New Zealand","code":"nz","cities":[{"name":"Auckland","code":"akl","latitude":-36.848461,"longitude":174.763336,"servers":[{"hostname":"nz1-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":3,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"nz2-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":3,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Norway","code":"no","cities":[{"name":"Oslo","code":"osl","latitude":59.916667,"longitude":10.75,"servers":[{"hostname":"no1-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"no2-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"no3-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"no4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Poland","code":"pl","cities":[{"name":"Warsaw","code":"waw","latitude":52.25,"longitude":21,"servers":[{"hostname":"pl1-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"pl2-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"pl3-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"pl4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":50,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Portugal","code":"pt","cities":[{"name":"Lisbon","code":"lis","latitude":38.736946,"longitude":-9.142685,"servers":[{"hostname":"pt1-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"pt2-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Romania","code":"ro","cities":[{"name":"Bucharest","code":"buh","latitude":44.433333,"longitude":26.1,"servers":[{"hostname":"ro4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ro5-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ro6-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ro7-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ro8-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Serbia","code":"rs","cities":[{"name":"Belgrade","code":"beg","latitude":44.787197,"longitude":20.457273,"servers":[{"hostname":"rs3-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"rs4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Singapore","code":"sg","cities":[{"name":"Singapore","code":"sin","latitude":1.2930556,"longitude":103.8558333,"servers":[{"hostname":"sg4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"sg5-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"sg6-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"sg7-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"sg8-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Spain","code":"es","cities":[{"name":"Madrid","code":"mad","latitude":40.408566,"longitude":-3.69222,"servers":[{"hostname":"es1-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"es2-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":false,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"es4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":false,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"es5-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":false,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Sweden","code":"se","cities":[{"name":"Gothenburg","code":"got","latitude":57.70887,"longitude":11.97456,"servers":[{"hostname":"se10-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1,"include_in_country":false,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se3-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1,"include_in_country":false,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se5-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1,"include_in_country":false,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se9-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1,"include_in_country":false,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"Malmö","code":"mma","latitude":55.607075,"longitude":13.002716,"servers":[{"hostname":"se15-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se17-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se18-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se19-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se1-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":false,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se21-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se22-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se23-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"Stockholm","code":"sto","latitude":59.3289,"longitude":18.0649,"servers":[{"hostname":"se12-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se13-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se14-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se26-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se27-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se28-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se2-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":false,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se6-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se7-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"se8-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"Switzerland","code":"ch","cities":[{"name":"Zurich","code":"zrh","latitude":47.366667,"longitude":8.55,"servers":[{"hostname":"ch10-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ch11-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ch12-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ch13-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ch14-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ch15-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ch16-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ch17-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ch2-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ch5-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":900,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ch6-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":900,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ch7-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":900,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ch8-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":900,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"ch9-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":900,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"UK","code":"gb","cities":[{"name":"London","code":"lon","latitude":51.514125,"longitude":-0.093689,"servers":[{"hostname":"gb11-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb12-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb13-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb14-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb15-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":300,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb16-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb17-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb18-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb19-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb20-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb21-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":false,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb5-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":false,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"Manchester","code":"mnc","latitude":53.5,"longitude":-2.216667,"servers":[{"hostname":"gb22-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb24-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb25-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb26-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb27-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb28-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb29-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb30-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb31-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"gb32-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]},{"name":"USA","code":"us","cities":[{"name":"Atlanta, GA","code":"atl","latitude":33.753746,"longitude":-84.38633,"servers":[{"hostname":"us167-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":3,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us168-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":3,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us169-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":3,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us170-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":3,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us171-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":3,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us172-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":3,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us173-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":3,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us174-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":3,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us175-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":3,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us176-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":3,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us6-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"Chicago, IL","code":"chi","latitude":41.881832,"longitude":-87.623177,"servers":[{"hostname":"us129-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us130-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us131-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us132-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us133-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us18-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us22-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us23-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us4-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":2,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"Dallas, TX","code":"dal","latitude":32.89748,"longitude":-97.040443,"servers":[{"hostname":"us143-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us144-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us145-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us146-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us147-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us148-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us149-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us150-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us151-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us152-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us153-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us154-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us17-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":false,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us30-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us31-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us32-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us33-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us34-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us35-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us36-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us37-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us38-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us39-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"Denver, CO","code":"den","latitude":39.7392358,"longitude":-104.990251,"servers":[{"hostname":"us10-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us11-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us12-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us44-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us45-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us46-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us47-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"Los Angeles, CA","code":"lax","latitude":34.052235,"longitude":-118.243683,"servers":[{"hostname":"us48-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us49-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us50-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us51-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us52-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us53-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us54-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us55-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us56-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us57-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us58-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us59-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us60-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us61-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us62-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us63-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us64-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us65-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us66-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us67-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us68-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us69-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us70-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us71-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"Miami, FL","code":"mia","latitude":25.761681,"longitude":-80.191788,"servers":[{"hostname":"us155-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us156-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us157-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us158-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us159-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us160-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us161-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us162-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us163-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us164-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us165-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us166-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"New York, NY","code":"nyc","latitude":40.73061,"longitude":-73.935242,"servers":[{"hostname":"us101-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us102-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us103-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us104-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us105-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us106-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us107-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us108-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us109-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us110-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us111-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us112-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us113-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us114-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us115-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us116-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us117-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us118-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us119-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us120-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us121-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us122-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us123-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us124-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us125-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us126-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us127-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us72-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us73-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us74-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us75-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us76-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us77-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us78-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us79-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us80-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us81-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us82-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us83-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us84-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us85-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us86-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us87-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us88-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us89-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us90-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us91-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us92-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us94-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us95-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us96-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us97-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us98-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us99-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1000,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"Phoenix, AZ","code":"phx","latitude":33.448376,"longitude":-112.074036,"servers":[{"hostname":"us189-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us190-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us191-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us192-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us193-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":1,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"Raleigh, NC","code":"rag","latitude":35.787743,"longitude":-78.644257,"servers":[{"hostname":"us183-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us184-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us185-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us186-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us187-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"Salt Lake City, UT","code":"slc","latitude":40.758701,"longitude":-111.876183,"servers":[{"hostname":"us134-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us135-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us136-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us137-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us138-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us139-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us140-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us141-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us142-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"San Jose, CA","code":"sjc","latitude":37.3382082,"longitude":-121.8863286,"servers":[{"hostname":"us195-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us196-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us197-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us198-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us199-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us200-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":100,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"},{"name":"Seattle, WA","code":"sea","latitude":47.608013,"longitude":-122.335167,"servers":[{"hostname":"us177-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us178-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us179-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us180-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"},{"hostname":"us182-wireguard","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1","weight":5,"include_in_country":true,"public_key":"%%PUBLICKEY%%","port_ranges":[[53,53],[4000,33433],[33565,51820],[52000,60000]],"ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1"}],"public_key":"%%PUBLICKEY%%","ipv4_gateway":"127.0.0.1","ipv6_gateway":"::1","ipv4_addr_in":"127.0.0.1","ipv6_addr_in":"::1"}]}]}mozilla-vpn-client-2.2.0/src/platforms/wasm/wasmauthenticationlistener.cpp000066400000000000000000000020341404202232700271600ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "wasmauthenticationlistener.h" #include "leakdetector.h" #include "logger.h" #include namespace { Logger logger(LOG_MAIN, "WasmAuthenticationListener"); } // anonymous namespace WasmAuthenticationListener::WasmAuthenticationListener(QObject* parent) : AuthenticationListener(parent) { MVPN_COUNT_CTOR(WasmAuthenticationListener); } WasmAuthenticationListener::~WasmAuthenticationListener() { MVPN_COUNT_DTOR(WasmAuthenticationListener); } void WasmAuthenticationListener::start(MozillaVPN* vpn, QUrl& url, QUrlQuery& query) { logger.log() << "WasmAuthenticationListener initialize"; Q_UNUSED(vpn); Q_UNUSED(url); Q_UNUSED(query); QTimer* timer = new QTimer(this); connect(timer, &QTimer::timeout, [this]() { emit completed("WASM"); }); timer->start(2000); } mozilla-vpn-client-2.2.0/src/platforms/wasm/wasmauthenticationlistener.h000066400000000000000000000012311404202232700266230ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WASMAUTHENTICATIONLISTENER_H #define WASMAUTHENTICATIONLISTENER_H #include "authenticationlistener.h" class WasmAuthenticationListener final : public AuthenticationListener { Q_OBJECT Q_DISABLE_COPY_MOVE(WasmAuthenticationListener) public: explicit WasmAuthenticationListener(QObject* parent); ~WasmAuthenticationListener(); void start(MozillaVPN* vpn, QUrl& url, QUrlQuery& query) override; }; #endif // WASMAUTHENTICATIONLISTENER_H mozilla-vpn-client-2.2.0/src/platforms/wasm/wasmnetworkrequest.cpp000066400000000000000000000076171404202232700255110ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "networkrequest.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "timersingleshot.h" #include #include namespace { Logger logger(LOG_NETWORKING, "WASM NetworkRequest"); void createDummyRequest(NetworkRequest* r, const QString& resource) { TimerSingleShot::create(r, 200, [r, resource] { QByteArray data; if (!resource.isEmpty()) { QFile file(resource); if (!file.open(QFile::ReadOnly | QFile::Text)) { logger.log() << "Failed to open" << resource; return; } data = file.readAll(); file.close(); } emit r->requestCompleted(data.replace( "%%PUBLICKEY%%", MozillaVPN::instance()->keys()->publicKey().toLocal8Bit())); }); } void createDummyRequest(NetworkRequest* r) { createDummyRequest(r, ""); } } // namespace NetworkRequest::NetworkRequest(QObject* parent, int status) : QObject(parent), m_status(status) { MVPN_COUNT_CTOR(NetworkRequest); logger.log() << "Network request created"; } NetworkRequest::~NetworkRequest() { MVPN_COUNT_DTOR(NetworkRequest); } // static NetworkRequest* NetworkRequest::createForAuthenticationVerification( QObject* parent, const QString&, const QString&) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 200); createDummyRequest(r, ":/networkrequests/authentication.json"); return r; } // static NetworkRequest* NetworkRequest::createForDeviceCreation(QObject* parent, const QString&, const QString&) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 201); createDummyRequest(r); return r; } // static NetworkRequest* NetworkRequest::createForDeviceRemoval(QObject* parent, const QString&) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 204); createDummyRequest(r); return r; } NetworkRequest* NetworkRequest::createForServers(QObject* parent) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 200); createDummyRequest(r, ":/networkrequests/servers.json"); return r; } NetworkRequest* NetworkRequest::createForVersions(QObject* parent) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 200); createDummyRequest(r); return r; } NetworkRequest* NetworkRequest::createForAccount(QObject* parent) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 200); createDummyRequest(r, ":/networkrequests/account.json"); return r; } NetworkRequest* NetworkRequest::createForIpInfo(QObject* parent) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 200); createDummyRequest(r, ":/networkrequests/ipinfo.json"); return r; } NetworkRequest* NetworkRequest::createForCaptivePortalDetection( QObject* parent, const QUrl&, const QByteArray&) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 200); createDummyRequest(r); return r; } NetworkRequest* NetworkRequest::createForCaptivePortalLookup(QObject* parent) { NetworkRequest* r = new NetworkRequest(parent, 200); createDummyRequest(r); return r; } NetworkRequest* NetworkRequest::createForHeartbeat(QObject* parent) { Q_ASSERT(parent); NetworkRequest* r = new NetworkRequest(parent, 200); createDummyRequest(r); return r; } void NetworkRequest::replyFinished() {} void NetworkRequest::timeout() {} void NetworkRequest::getRequest() {} void NetworkRequest::deleteRequest() {} void NetworkRequest::postRequest(const QByteArray&) {} void NetworkRequest::handleReply(QNetworkReply*) {} int NetworkRequest::statusCode() const { return 200; } mozilla-vpn-client-2.2.0/src/platforms/wasm/wasmnetworkwatcher.cpp000066400000000000000000000013711404202232700254450ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "wasmnetworkwatcher.h" #include "leakdetector.h" #include "logger.h" namespace { Logger logger(LOG_NETWORKING, "WasmNetworkWatcher"); } WasmNetworkWatcher::WasmNetworkWatcher(QObject* parent) : NetworkWatcherImpl(parent) { MVPN_COUNT_CTOR(WasmNetworkWatcher); } WasmNetworkWatcher::~WasmNetworkWatcher() { MVPN_COUNT_DTOR(WasmNetworkWatcher); } void WasmNetworkWatcher::initialize() { logger.log() << "initialize"; } void WasmNetworkWatcher::start() { logger.log() << "actived"; emit unsecuredNetwork("WifiName", "NetworkID"); } mozilla-vpn-client-2.2.0/src/platforms/wasm/wasmnetworkwatcher.h000066400000000000000000000010311404202232700251030ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WASMNETWORKWATCHER_H #define WASMNETWORKWATCHER_H #include "networkwatcherimpl.h" class WasmNetworkWatcher final : public NetworkWatcherImpl { Q_OBJECT public: WasmNetworkWatcher(QObject* parent); ~WasmNetworkWatcher(); void initialize() override; void start() override; }; #endif // WASMNETWORKWATCHER_H mozilla-vpn-client-2.2.0/src/platforms/wasm/wasmwindowcontroller.cpp000066400000000000000000000071601404202232700260130ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "wasmwindowcontroller.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "platforms/macos/macosmenubar.h" #include "systemtrayhandler.h" #include #include #include #include #include #include #include namespace { Logger logger(LOG_MAIN, "WasmWindowController"); WasmWindowController* s_instance = nullptr; } // namespace WasmWindowController::WasmWindowController() { MVPN_COUNT_CTOR(WasmWindowController); Q_ASSERT(s_instance == nullptr); s_instance = this; QList screens = qApp->screens(); if (screens.length() < 2) { logger.log() << "Only 1 screen detected. No menu for wasm"; return; } logger.log() << "Wasm control window creation"; QWidget* centralWidget = new QWidget(&m_window); m_window.setCentralWidget(centralWidget); QVBoxLayout* layout = new QVBoxLayout(centralWidget); // System tray icon { QLabel* label = new QLabel("System tray menu:"); layout->addWidget(label); m_systemTrayMenuBar = new QMenuBar(); layout->addWidget(m_systemTrayMenuBar); StatusIcon* statusIcon = MozillaVPN::instance()->statusIcon(); connect(statusIcon, &StatusIcon::iconChanged, this, &WasmWindowController::iconChanged); iconChanged(statusIcon->iconString()); QMenu* menu = SystemTrayHandler::instance()->contextMenu(); m_systemTrayMenuBar->addMenu(menu); } // MacOS Menu bar { QLabel* label = new QLabel("MacOS menu:"); layout->addWidget(label); m_macOSMenuBar = new MacOSMenuBar(); m_macOSMenuBar->initialize(); layout->addWidget(m_macOSMenuBar->menuBar()); } // Notification title { QLabel* label = new QLabel("Last notification Title:"); layout->addWidget(label); m_notificationTitle = new QLabel(); m_notificationTitle->setStyleSheet("font-weight: bold"); layout->addWidget(m_notificationTitle); } // Notification message { QLabel* label = new QLabel("Last notification Message:"); layout->addWidget(label); m_notificationMessage = new QLabel(); m_notificationMessage->setStyleSheet("font-weight: bold"); layout->addWidget(m_notificationMessage); } // stratch layout->addWidget(new QWidget(), 1); m_window.showFullScreen(); // System tray has a different message for internal notifications (not // related to the VPN status). connect(SystemTrayHandler::instance(), &SystemTrayHandler::notificationShown, this, &WasmWindowController::notification); } WasmWindowController::~WasmWindowController() { MVPN_COUNT_DTOR(WasmWindowController); Q_ASSERT(s_instance == this); s_instance = nullptr; } // static WasmWindowController* WasmWindowController::instance() { Q_ASSERT(s_instance); return s_instance; } void WasmWindowController::iconChanged(const QString& icon) { QIcon menuIcon(icon); menuIcon.setIsMask(true); QMenu* menu = SystemTrayHandler::instance()->contextMenu(); menu->setIcon(menuIcon); } void WasmWindowController::notification(const QString& title, const QString& message) { logger.log() << "Notification received"; m_notificationTitle->setText(title); m_notificationMessage->setText(message); } void WasmWindowController::retranslate() { QMenu* menu = SystemTrayHandler::instance()->contextMenu(); m_systemTrayMenuBar->addMenu(menu); m_macOSMenuBar->retranslate(); } mozilla-vpn-client-2.2.0/src/platforms/wasm/wasmwindowcontroller.h000066400000000000000000000016671404202232700254660ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WASMWINDOWCONTROLLER_H #define WASMWINDOWCONTROLLER_H #include #include class MacOSMenuBar; class QLabel; class QMenuBar; class WasmWindowController final : public QObject { Q_DISABLE_COPY_MOVE(WasmWindowController) public: WasmWindowController(); ~WasmWindowController(); static WasmWindowController* instance(); void notification(const QString& title, const QString& message); void retranslate(); private: void iconChanged(const QString& icon); private: QMainWindow m_window; QLabel* m_notificationTitle = nullptr; QLabel* m_notificationMessage = nullptr; QMenuBar* m_systemTrayMenuBar = nullptr; MacOSMenuBar* m_macOSMenuBar = nullptr; }; #endif // WASMWINDOWCONTROLLER_H mozilla-vpn-client-2.2.0/src/platforms/windows/000077500000000000000000000000001404202232700215235ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/platforms/windows/daemon/000077500000000000000000000000001404202232700227665ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/platforms/windows/daemon/windowsdaemon.cpp000066400000000000000000000325231404202232700263550ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "windowsdaemon.h" #include "leakdetector.h" #include "logger.h" #include "platforms/windows/windowscommons.h" #include "wgquickprocess.h" #include #include #include #include #include #include #include #include #include #define TUNNEL_NAMED_PIPE \ "\\\\." \ "\\pipe\\ProtectedPrefix\\Administrators\\WireGuard\\MozillaVPN" namespace { Logger logger(LOG_WINDOWS, "WindowsDaemon"); bool waitForServiceStatus(SC_HANDLE service, DWORD expectedStatus) { int tries = 0; while (tries < 30) { SERVICE_STATUS status; if (!QueryServiceStatus(service, &status)) { WindowsCommons::windowsLog("Failed to retrieve the service status"); return false; } if (status.dwCurrentState == expectedStatus) { return true; } logger.log() << "The service is not in the right status yet."; Sleep(1000); ++tries; } return false; } bool stopAndDeleteTunnelService() { SC_HANDLE scm = nullptr; SC_HANDLE service = nullptr; auto closeService = qScopeGuard([&] { if (service) { CloseServiceHandle(service); } if (scm) { CloseServiceHandle(scm); } }); scm = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS); if (!scm) { WindowsCommons::windowsLog("Failed to open the SCM"); return false; } service = OpenService(scm, TUNNEL_SERVICE_NAME, SERVICE_ALL_ACCESS); if (!service) { WindowsCommons::windowsLog("Failed to open the service"); return false; } SERVICE_STATUS status; if (!QueryServiceStatus(service, &status)) { WindowsCommons::windowsLog("Failed to retrieve the service status"); return false; } logger.log() << "The current service is stopped:" << (status.dwCurrentState == SERVICE_STOPPED); if (status.dwCurrentState != SERVICE_STOPPED) { logger.log() << "The service is not stopped yet."; if (!ControlService(service, SERVICE_CONTROL_STOP, &status)) { WindowsCommons::windowsLog("Failed to control the service"); return false; } if (!waitForServiceStatus(service, SERVICE_STOPPED)) { logger.log() << "Unable to stop the service"; return false; } } logger.log() << "Proceeding with the deletion"; if (!DeleteService(service)) { WindowsCommons::windowsLog("Failed to delete the service"); return false; } return true; } HANDLE createPipe() { HANDLE pipe = INVALID_HANDLE_VALUE; auto guard = qScopeGuard([&] { if (pipe != INVALID_HANDLE_VALUE) { CloseHandle(pipe); } }); LPTSTR tunnelName = (LPTSTR)TEXT(TUNNEL_NAMED_PIPE); uint32_t tries = 0; while (tries < 30) { pipe = CreateFile(tunnelName, GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, nullptr); if (pipe != INVALID_HANDLE_VALUE) { break; } if (GetLastError() != ERROR_PIPE_BUSY) { WindowsCommons::windowsLog("Failed to create a named pipe"); return INVALID_HANDLE_VALUE; } logger.log() << "Pipes are busy. Let's wait"; if (!WaitNamedPipe(tunnelName, 1000)) { WindowsCommons::windowsLog("Failed to wait for named pipes"); return INVALID_HANDLE_VALUE; } ++tries; } DWORD mode = PIPE_READMODE_BYTE; if (!SetNamedPipeHandleState(pipe, &mode, nullptr, nullptr)) { WindowsCommons::windowsLog("Failed to set the read-mode on pipe"); return INVALID_HANDLE_VALUE; } guard.dismiss(); return pipe; } QString exitCodeToFailure(DWORD exitCode) { // The order of this error code is taken from wireguard. switch (exitCode) { case 0: return "No error"; case 1: return "Error when opening the ringlogger log file"; case 2: return "Error while loading the WireGuard configuration file from " "path."; case 3: return "Error while creating a WinTun device."; case 4: return "Error while listening on a named pipe."; case 5: return "Error while resolving DNS hostname endpoints."; case 6: return "Error while manipulating firewall rules."; case 7: return "Error while setting the device configuration."; case 8: return "Error while binding sockets to default routes."; case 9: return "Unable to set interface addresses, routes, dns, and/or " "interface settings."; case 10: return "Error while determining current executable path."; case 11: return "Error while opening the NUL file."; case 12: return "Error while attempting to track tunnels."; case 13: return "Error while attempting to enumerate current sessions."; case 14: return "Error while dropping privileges."; case 15: return "Windows internal error."; default: return "Unknown error"; } } } // namespace WindowsDaemon::WindowsDaemon() : Daemon(nullptr) { MVPN_COUNT_CTOR(WindowsDaemon); connect(&m_tunnelMonitor, &WindowsTunnelMonitor::backendFailure, this, &WindowsDaemon::monitorBackendFailure); } WindowsDaemon::~WindowsDaemon() { MVPN_COUNT_DTOR(WindowsDaemon); logger.log() << "Daemon released"; m_state = Inactive; stopAndDeleteTunnelService(); } QByteArray WindowsDaemon::getStatus() { logger.log() << "Status request"; QJsonObject obj; obj.insert("type", "status"); obj.insert("connected", m_connected); HANDLE pipe = createPipe(); auto guard = qScopeGuard([&] { if (pipe != INVALID_HANDLE_VALUE) { CloseHandle(pipe); } }); if (pipe == INVALID_HANDLE_VALUE || !m_connected) { return QJsonDocument(obj).toJson(QJsonDocument::Compact); } QByteArray message = "get=1\n\n"; DWORD written; if (!WriteFile(pipe, message.constData(), message.length(), &written, nullptr)) { WindowsCommons::windowsLog("Failed to write into the pipe"); return QJsonDocument(obj).toJson(QJsonDocument::Compact); } QByteArray data; while (true) { char buffer[512]; DWORD read = 0; if (!ReadFile(pipe, buffer, sizeof(buffer), &read, nullptr)) { break; } data.append(buffer, read); } uint64_t txBytes = 0; uint64_t rxBytes = 0; int steps = 0; constexpr const int TxFound = 0x01; constexpr const int RxFound = 0x02; for (const QByteArray& line : data.split('\n')) { if (!line.contains('=')) { continue; } QList parts = line.split('='); if (parts[0] == "tx_bytes") { txBytes = parts[1].toLongLong(); steps |= TxFound; } else if (parts[0] == "rx_bytes") { rxBytes = parts[1].toLongLong(); steps |= RxFound; } if (steps >= (TxFound | RxFound)) { break; } } obj.insert("status", true); obj.insert("serverIpv4Gateway", m_lastConfig.m_serverIpv4Gateway); obj.insert("date", m_connectionDate.toString()); obj.insert("txBytes", QJsonValue(double(txBytes))); obj.insert("rxBytes", QJsonValue(double(rxBytes))); return QJsonDocument(obj).toJson(QJsonDocument::Compact); } bool WindowsDaemon::registerTunnelService(const QString& configFile) { logger.log() << "Register tunnel service"; SC_HANDLE scm; SC_HANDLE service; auto guard = qScopeGuard([&] { if (service) { CloseServiceHandle(service); } if (scm) { CloseServiceHandle(scm); } }); scm = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS); if (!scm) { WindowsCommons::windowsLog("Failed to open SCManager"); return false; } // Let's see if we have to delete a previous instance. service = OpenService(scm, TUNNEL_SERVICE_NAME, SERVICE_ALL_ACCESS); if (service) { logger.log() << "An existing service has been detected. Let's close it."; CloseServiceHandle(service); service = nullptr; if (!stopAndDeleteTunnelService()) { return false; } } QString servicePath; { QTextStream out(&servicePath); out << "\"" << qApp->applicationFilePath() << "\" tunneldaemon \"" << configFile << "\""; } logger.log() << "Service name:" << servicePath; service = CreateService(scm, TUNNEL_SERVICE_NAME, L"Mozilla VPN (tunnel)", SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_DEMAND_START, SERVICE_ERROR_NORMAL, (const wchar_t*)servicePath.utf16(), nullptr, 0, TEXT("Nsi\0TcpIp\0"), nullptr, nullptr); if (!service) { WindowsCommons::windowsLog("Failed to create the tunnel service"); return false; } SERVICE_DESCRIPTION sd = { (wchar_t*)L"Manages the Mozilla VPN tunnel connection"}; if (!ChangeServiceConfig2(service, SERVICE_CONFIG_DESCRIPTION, &sd)) { WindowsCommons::windowsLog( "Failed to set the description to the tunnel service"); return false; } SERVICE_SID_INFO ssi; ssi.dwServiceSidType = SERVICE_SID_TYPE_UNRESTRICTED; if (!ChangeServiceConfig2(service, SERVICE_CONFIG_SERVICE_SID_INFO, &ssi)) { WindowsCommons::windowsLog("Failed to set the SID to the tunnel service"); return false; } if (!StartService(service, 0, nullptr)) { WindowsCommons::windowsLog("Failed to start the service"); return false; } if (waitForServiceStatus(service, SERVICE_RUNNING)) { logger.log() << "The tunnel service is up and running"; return true; } logger.log() << "Failed to run the tunnel service"; SERVICE_STATUS status; if (!QueryServiceStatus(service, &status)) { WindowsCommons::windowsLog("Failed to retrieve the service status"); return false; } logger.log() << "The tunnel service exits with status code:" << status.dwWin32ExitCode << "-" << exitCodeToFailure(status.dwWin32ExitCode); emit backendFailure(); return false; } bool WindowsDaemon::run(Daemon::Op op, const InterfaceConfig& config) { logger.log() << (op == Daemon::Up ? "Activate" : "Deactivate") << "the vpn"; if (op == Daemon::Down && m_state == Inactive) { logger.log() << "Nothing to do. The tunnel service is down"; return true; } if (op == Daemon::Down) { m_tunnelMonitor.stop(); stopAndDeleteTunnelService(); m_state = Inactive; return true; } Q_ASSERT(op == Daemon::Up); if (m_state == Active) { logger.log() << "Nothing to do. The tunnel service is up"; return true; } QString tunnelFile = WindowsCommons::tunnelConfigFile(); if (tunnelFile.isEmpty()) { logger.log() << "Failed to choose the tunnel config file"; return false; } QStringList addresses; for (const IPAddressRange& ip : config.m_allowedIPAddressRanges) { addresses.append(ip.toString()); } if (!WgQuickProcess::createConfigFile( tunnelFile, config.m_privateKey, config.m_deviceIpv4Address, config.m_deviceIpv6Address, config.m_serverIpv4Gateway, config.m_serverIpv6Gateway, config.m_serverPublicKey, config.m_serverIpv4AddrIn, config.m_serverIpv6AddrIn, addresses.join(", "), config.m_serverPort, config.m_ipv6Enabled)) { logger.log() << "Failed to create a config file"; return false; } if (!registerTunnelService(tunnelFile)) { logger.log() << "Failed to activate the tunnel service"; return false; } logger.log() << "Registration completed"; m_tunnelMonitor.start(); m_state = Active; return true; } bool WindowsDaemon::supportServerSwitching( const InterfaceConfig& config) const { return m_lastConfig.m_privateKey == config.m_privateKey && m_lastConfig.m_deviceIpv4Address == config.m_deviceIpv4Address && m_lastConfig.m_deviceIpv6Address == config.m_deviceIpv6Address && m_lastConfig.m_serverIpv4Gateway == config.m_serverIpv4Gateway && m_lastConfig.m_serverIpv6Gateway == config.m_serverIpv6Gateway; } bool WindowsDaemon::switchServer(const InterfaceConfig& config) { logger.log() << "Switching server"; Q_ASSERT(m_connected); HANDLE pipe = createPipe(); auto guard = qScopeGuard([&] { if (pipe != INVALID_HANDLE_VALUE) { CloseHandle(pipe); } }); if (pipe == INVALID_HANDLE_VALUE) { return false; } QByteArray message; { QTextStream out(&message); out << "set=1\n"; out << "replace_peers=true\n"; QByteArray publicKey = QByteArray::fromBase64(config.m_serverPublicKey.toLocal8Bit()).toHex(); out << "public_key=" << publicKey << "\n"; out << "endpoint=" << config.m_serverIpv4AddrIn << ":" << config.m_serverPort << "\n"; for (const IPAddressRange& ip : config.m_allowedIPAddressRanges) { out << "allowed_ip=" << ip.toString() << "\n"; } out << "\n"; } DWORD written; if (!WriteFile(pipe, message.constData(), message.length(), &written, nullptr)) { WindowsCommons::windowsLog("Failed to write into the pipe"); return false; } QByteArray data; while (true) { char buffer[512]; DWORD read = 0; if (!ReadFile(pipe, buffer, sizeof(buffer), &read, nullptr)) { break; } data.append(buffer, read); } logger.log() << "DATA:" << data; guard.dismiss(); return true; } void WindowsDaemon::monitorBackendFailure() { logger.log() << "Tunnel service is down"; m_tunnelMonitor.stop(); emit backendFailure(); deactivate(); m_state = Inactive; } mozilla-vpn-client-2.2.0/src/platforms/windows/daemon/windowsdaemon.h000066400000000000000000000017631404202232700260240ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WINDOWSDAEMON_H #define WINDOWSDAEMON_H #include "daemon/daemon.h" #include "windowstunnelmonitor.h" #define TUNNEL_SERVICE_NAME L"WireGuardTunnel$MozillaVPN" class WindowsDaemon final : public Daemon { Q_DISABLE_COPY_MOVE(WindowsDaemon) public: WindowsDaemon(); ~WindowsDaemon(); QByteArray getStatus() override; private: bool run(Op op, const InterfaceConfig& config) override; bool supportServerSwitching(const InterfaceConfig& config) const override; bool switchServer(const InterfaceConfig& config) override; bool registerTunnelService(const QString& configFile); private: void monitorBackendFailure(); private: enum State { Active, Inactive, }; State m_state = Inactive; WindowsTunnelMonitor m_tunnelMonitor; }; #endif // WINDOWSDAEMON_H mozilla-vpn-client-2.2.0/src/platforms/windows/daemon/windowsdaemonserver.cpp000066400000000000000000000117211404202232700276010ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "windowsdaemonserver.h" #include "commandlineparser.h" #include "daemon/daemonlocalserver.h" #include "leakdetector.h" #include "logger.h" #include "windowsdaemon.h" #include #include #include #include #include #include #define SERVICE_NAME (wchar_t*)L"Mozilla VPN" namespace { Logger logger(LOG_WINDOWS, "WindowsDaemonServer"); SERVICE_STATUS s_serviceStatus = {0}; SERVICE_STATUS_HANDLE s_statusHandle = nullptr; HANDLE s_serviceStopEvent = INVALID_HANDLE_VALUE; } // namespace class ServiceThread : public QThread { private: void run() override { SERVICE_TABLE_ENTRY serviceTable[] = { {SERVICE_NAME, (LPSERVICE_MAIN_FUNCTION)serviceMain}, {nullptr, nullptr}, }; if (StartServiceCtrlDispatcher(serviceTable) == FALSE) { logger.log() << "Failed to start the service."; } } static void WINAPI serviceMain(DWORD argc, wchar_t** argv); static void WINAPI serviceCtrlHandler(DWORD code); }; WindowsDaemonServer::WindowsDaemonServer(QObject* parent) : Command(parent, "windowsdaemon", "Activate the windows daemon") { MVPN_COUNT_CTOR(WindowsDaemonServer); } WindowsDaemonServer::~WindowsDaemonServer() { MVPN_COUNT_DTOR(WindowsDaemonServer); } int WindowsDaemonServer::run(QStringList& tokens) { Q_ASSERT(!tokens.isEmpty()); QString appName = tokens[0]; logger.log() << "Daemon is starting"; QCoreApplication app(CommandLineParser::argc(), CommandLineParser::argv()); QCoreApplication::setApplicationName("Mozilla VPN Daemon"); QCoreApplication::setApplicationVersion(APP_VERSION); if (tokens.length() > 1) { QList options; return CommandLineParser::unknownOption(this, appName, tokens[1], options, false); } ServiceThread* st = new ServiceThread(); st->start(); WindowsDaemon daemon; DaemonLocalServer server(qApp); if (!server.initialize()) { logger.log() << "Failed to initialize the server"; return 1; } QTimer stopEventTimer; connect(&stopEventTimer, &QTimer::timeout, [&]() { if (WaitForSingleObject(s_serviceStopEvent, 0) == WAIT_OBJECT_0) { logger.log() << "Stop event message received"; s_serviceStatus.dwControlsAccepted = 0; s_serviceStatus.dwCurrentState = SERVICE_STOPPED; s_serviceStatus.dwWin32ExitCode = GetLastError(); s_serviceStatus.dwCheckPoint = 1; if (SetServiceStatus(s_statusHandle, &s_serviceStatus) == FALSE) { logger.log() << "SetServiceStatus failed"; } CloseHandle(s_serviceStopEvent); qApp->exit(); } }); stopEventTimer.start(1000); return qApp->exec(); } // static void WINAPI ServiceThread::serviceMain(DWORD argc, LPTSTR* argv) { Q_UNUSED(argc); Q_UNUSED(argv); logger.log() << "Service has started"; s_statusHandle = RegisterServiceCtrlHandler(SERVICE_NAME, serviceCtrlHandler); if (!s_statusHandle) { logger.log() << "Failed to register the service handler"; return; } ZeroMemory(&s_serviceStatus, sizeof(s_serviceStatus)); s_serviceStatus.dwServiceType = SERVICE_WIN32_OWN_PROCESS; s_serviceStatus.dwCurrentState = SERVICE_START_PENDING; if (SetServiceStatus(s_statusHandle, &s_serviceStatus) == FALSE) { logger.log() << "SetServiceStatus failed"; } s_serviceStopEvent = CreateEvent(nullptr, TRUE, FALSE, nullptr); if (!s_serviceStopEvent) { logger.log() << "Failed to create the stop event"; s_serviceStatus.dwControlsAccepted = 0; s_serviceStatus.dwCurrentState = SERVICE_STOPPED; s_serviceStatus.dwWin32ExitCode = GetLastError(); s_serviceStatus.dwCheckPoint = 1; if (SetServiceStatus(s_statusHandle, &s_serviceStatus) == FALSE) { logger.log() << "SeServiceStatus failed"; } return; } s_serviceStatus.dwControlsAccepted = SERVICE_ACCEPT_STOP; s_serviceStatus.dwCurrentState = SERVICE_RUNNING; s_serviceStatus.dwWin32ExitCode = 0; s_serviceStatus.dwCheckPoint = 0; if (SetServiceStatus(s_statusHandle, &s_serviceStatus) == FALSE) { logger.log() << "SeServiceStatus failed"; return; } // Let's this thread die. } // static void WINAPI ServiceThread::serviceCtrlHandler(DWORD code) { logger.log() << "Message received"; if (code != SERVICE_CONTROL_STOP) { return; } if (s_serviceStatus.dwCurrentState != SERVICE_RUNNING) { return; } s_serviceStatus.dwControlsAccepted = 0; s_serviceStatus.dwCurrentState = SERVICE_STOP_PENDING; s_serviceStatus.dwWin32ExitCode = 0; s_serviceStatus.dwCheckPoint = 4; if (SetServiceStatus(s_statusHandle, &s_serviceStatus) == FALSE) { logger.log() << "SetServiceStatus failed"; } SetEvent(s_serviceStopEvent); } static Command::RegistrationProxy s_commandWindowsDaemon; mozilla-vpn-client-2.2.0/src/platforms/windows/daemon/windowsdaemonserver.h000066400000000000000000000010431404202232700272420ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WINDOWSDAEMONSERVER_H #define WINDOWSDAEMONSERVER_H #include "command.h" class WindowsDaemonServer final : public Command { Q_DISABLE_COPY_MOVE(WindowsDaemonServer) public: explicit WindowsDaemonServer(QObject* parent); ~WindowsDaemonServer(); int run(QStringList& tokens) override; }; #endif // WINDOWSDAEMONSERVER_H mozilla-vpn-client-2.2.0/src/platforms/windows/daemon/windowsdaemontunnel.cpp000066400000000000000000000055231404202232700276030ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "windowsdaemontunnel.h" #include "commandlineparser.h" #include "leakdetector.h" #include "logger.h" #include "platforms/windows/windowscommons.h" #include #include namespace { Logger logger(LOG_WINDOWS, "WindowsDaemonTunnel"); void tunnelLoggerFunc(int level, const char* msg) { Q_UNUSED(level); logger.log() << "tunnel.dll:" << msg; } } // namespace WindowsDaemonTunnel::WindowsDaemonTunnel(QObject* parent) : Command(parent, "tunneldaemon", "Activate the windows tunnel service") { MVPN_COUNT_CTOR(WindowsDaemonTunnel); } WindowsDaemonTunnel::~WindowsDaemonTunnel() { MVPN_COUNT_DTOR(WindowsDaemonTunnel); } int WindowsDaemonTunnel::run(QStringList& tokens) { Q_ASSERT(!tokens.isEmpty()); logger.log() << "Tunnel daemon service is starting"; QCoreApplication app(CommandLineParser::argc(), CommandLineParser::argv()); QCoreApplication::setApplicationName("Mozilla VPN Tunnel"); QCoreApplication::setApplicationVersion(APP_VERSION); if (tokens.length() != 2) { logger.log() << "Expected 1 parameter only: the config file."; return 1; } // This process will be used by the wireguard tunnel. No need to call // FreeLibrary. HMODULE tunnelLib = LoadLibrary(TEXT("tunnel.dll")); if (!tunnelLib) { WindowsCommons::windowsLog("Failed to load tunnel.dll"); return 1; } typedef struct { const char* p; size_t n; } gostring_t; typedef void (*logFunc)(int level, const char* msg); typedef bool WireGuardTunnelService(gostring_t settings); typedef void WireGuardTunnelLogger(logFunc func); WireGuardTunnelLogger* tunnelLogger = (WireGuardTunnelLogger*)GetProcAddress( tunnelLib, "WireGuardTunnelLogger"); if (!tunnelLogger) { WindowsCommons::windowsLog("Failed to get WireGuardTunnelLogger function"); return 1; } WireGuardTunnelService* tunnelProc = (WireGuardTunnelService*)GetProcAddress( tunnelLib, "WireGuardTunnelService"); if (!tunnelProc) { WindowsCommons::windowsLog("Failed to get WireGuardTunnelService function"); return 1; } tunnelLogger(tunnelLoggerFunc); QString configFile = WindowsCommons::tunnelConfigFile(); if (configFile.isEmpty()) { logger.log() << "Failed to retrieve the config file"; return 1; } QByteArray configFileData = configFile.toLocal8Bit(); gostring_t goConfigFile; goConfigFile.p = configFileData.constData(); goConfigFile.n = configFileData.length(); if (!tunnelProc(goConfigFile)) { logger.log() << "Failed to activate the tunnel service"; return 1; } return 0; } static Command::RegistrationProxy s_commandWindowsDaemonTunnel; mozilla-vpn-client-2.2.0/src/platforms/windows/daemon/windowsdaemontunnel.h000066400000000000000000000007671404202232700272550ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WINDOWSDAEMONTUNNEL_H #define WINDOWSDAEMONTUNNEL_H #include "command.h" class WindowsDaemonTunnel final : public Command { public: explicit WindowsDaemonTunnel(QObject* parent); ~WindowsDaemonTunnel(); int run(QStringList& tokens) override; }; #endif // WINDOWSDAEMONTUNNEL_H mozilla-vpn-client-2.2.0/src/platforms/windows/daemon/windowstunnelmonitor.cpp000066400000000000000000000040401404202232700300200ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "windowstunnelmonitor.h" #include "leakdetector.h" #include "logger.h" #include "platforms/windows/windowscommons.h" #include "windowsdaemon.h" #include #include constexpr uint32_t WINDOWS_TUNNEL_MONITOR_TIMEOUT_MSEC = 2000; namespace { Logger logger(LOG_WINDOWS, "WindowsTunnelMonitor"); } WindowsTunnelMonitor::WindowsTunnelMonitor() { MVPN_COUNT_CTOR(WindowsTunnelMonitor); connect(&m_timer, &QTimer::timeout, this, &WindowsTunnelMonitor::timeout); } WindowsTunnelMonitor::~WindowsTunnelMonitor() { MVPN_COUNT_CTOR(WindowsTunnelMonitor); } void WindowsTunnelMonitor::start() { logger.log() << "Starting monitoring the tunnel service"; m_timer.start(WINDOWS_TUNNEL_MONITOR_TIMEOUT_MSEC); } void WindowsTunnelMonitor::stop() { logger.log() << "Stopping monitoring the tunnel service"; m_timer.stop(); } void WindowsTunnelMonitor::timeout() { SC_HANDLE scm; SC_HANDLE service; auto guard = qScopeGuard([&] { if (service) { CloseServiceHandle(service); } if (scm) { CloseServiceHandle(scm); } }); scm = OpenSCManager(nullptr, nullptr, SC_MANAGER_ALL_ACCESS); if (!scm) { WindowsCommons::windowsLog("Failed to open SCManager"); emit backendFailure(); return; } // Let's see if we have to delete a previous instance. service = OpenService(scm, TUNNEL_SERVICE_NAME, SERVICE_ALL_ACCESS); if (!service) { logger.log() << "The service doesn't exist"; emit backendFailure(); return; } SERVICE_STATUS status; if (!QueryServiceStatus(service, &status)) { WindowsCommons::windowsLog("Failed to retrieve the service status"); emit backendFailure(); return; } if (status.dwCurrentState == SERVICE_RUNNING) { // The service is active return; } logger.log() << "The service is not active"; emit backendFailure(); } mozilla-vpn-client-2.2.0/src/platforms/windows/daemon/windowstunnelmonitor.h000066400000000000000000000012011404202232700274610ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WINDOWSTUNNELMONITOR_H #define WINDOWSTUNNELMONITOR_H #include #include class WindowsTunnelMonitor final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(WindowsTunnelMonitor) public: WindowsTunnelMonitor(); ~WindowsTunnelMonitor(); void start(); void stop(); signals: void backendFailure(); private: void timeout(); private: QTimer m_timer; }; #endif // WINDOWSTUNNELMONITOR_H mozilla-vpn-client-2.2.0/src/platforms/windows/windowscaptiveportaldetection.cpp000066400000000000000000000036101404202232700304160ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "windowscaptiveportaldetection.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "windowscaptiveportaldetectionthread.h" #include namespace { Logger logger(LOG_WINDOWS, "WindowsCaptivePortalDetection"); } WindowsCaptivePortalDetection::WindowsCaptivePortalDetection() { MVPN_COUNT_CTOR(WindowsCaptivePortalDetection); m_thread.start(); } WindowsCaptivePortalDetection::~WindowsCaptivePortalDetection() { MVPN_COUNT_DTOR(WindowsCaptivePortalDetection); m_thread.quit(); m_thread.wait(); } void WindowsCaptivePortalDetection::start() { logger.log() << "Captive portal detection started"; auto guard = qScopeGuard([&] { emit detectionCompleted(false); }); QStringList ipv4Addresses = MozillaVPN::instance()->captivePortal()->ipv4Addresses(); if (ipv4Addresses.isEmpty()) { logger.log() << "No ipv4 addresses for the captive portal endpoint"; return; } WindowsCaptivePortalDetectionThread* thread = new WindowsCaptivePortalDetectionThread(this); thread->moveToThread(&m_thread); connect(&m_thread, &QThread::finished, thread, &QObject::deleteLater); connect(this, &QObject::destroyed, thread, &QObject::deleteLater); connect(thread, &WindowsCaptivePortalDetectionThread::detectionCompleted, this, &WindowsCaptivePortalDetection::detectionCompleted); connect(this, &WindowsCaptivePortalDetection::startWorker, thread, &WindowsCaptivePortalDetectionThread::startWorker); QString ipv4 = ipv4Addresses[0]; QString url = QString(CAPTIVEPORTAL_URL_IPV4).arg(ipv4); emit startWorker(ipv4, CAPTIVEPORTAL_HOST, url, CAPTIVEPORTAL_REQUEST_CONTENT); guard.dismiss(); } mozilla-vpn-client-2.2.0/src/platforms/windows/windowscaptiveportaldetection.h000066400000000000000000000014131404202232700300620ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WINDOWSCAPTIVEPORTALDETECTION_H #define WINDOWSCAPTIVEPORTALDETECTION_H #include "captiveportal/captiveportaldetectionimpl.h" #include class WindowsCaptivePortalDetection final : public CaptivePortalDetectionImpl { Q_OBJECT public: WindowsCaptivePortalDetection(); ~WindowsCaptivePortalDetection(); void start() override; signals: void startWorker(const QString& ip, const QString& host, const QString& url, const QString& expectedResult); private: QThread m_thread; }; #endif // WINDOWSCAPTIVEPORTALDETECTION_H mozilla-vpn-client-2.2.0/src/platforms/windows/windowscaptiveportaldetectionthread.cpp000066400000000000000000000074531404202232700316170ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "windowscaptiveportaldetectionthread.h" #include "leakdetector.h" #include "logger.h" #include "windowscommons.h" #include #include namespace { Logger logger(LOG_WINDOWS, "WindowsCaptivePortalDetectionThread"); HMODULE s_tunnelLib; typedef struct { const char* p; size_t n; } gostring_t; typedef int32_t WireguardTestOutsideConnectivity(gostring_t ip, gostring_t host, gostring_t url, gostring_t expectedTestResult); WireguardTestOutsideConnectivity* s_captivePortalDetection = nullptr; } // namespace static void tunnelLoggerFunc(int level, const char* msg) { Q_UNUSED(level); logger.log() << "tunnel.dll:" << msg; } static bool loadLibrary() { Q_ASSERT(!s_tunnelLib); // This process will be used by the wireguard tunnel the whole time. No need // to call FreeLibrary. s_tunnelLib = LoadLibrary(TEXT("tunnel.dll")); if (!s_tunnelLib) { WindowsCommons::windowsLog("Failed to load tunnel.dll"); return false; } typedef void (*logFunc)(int level, const char* msg); typedef void WireGuardTunnelLogger(logFunc func); WireGuardTunnelLogger* tunnelLogger = (WireGuardTunnelLogger*)GetProcAddress( s_tunnelLib, "WireGuardTunnelLogger"); if (!tunnelLogger) { WindowsCommons::windowsLog("Failed to get WireGuardTunnelLogger function"); return false; } tunnelLogger(tunnelLoggerFunc); s_captivePortalDetection = (WireguardTestOutsideConnectivity*)GetProcAddress( s_tunnelLib, "WireguardTestOutsideConnectivity"); if (!s_captivePortalDetection) { WindowsCommons::windowsLog( "Failed to get WireguardTestOutsideConnectivity function"); return false; } return true; } WindowsCaptivePortalDetectionThread::WindowsCaptivePortalDetectionThread( QObject* parent) : QObject(parent){MVPN_COUNT_CTOR(WindowsCaptivePortalDetectionThread)} WindowsCaptivePortalDetectionThread:: ~WindowsCaptivePortalDetectionThread() { MVPN_COUNT_DTOR(WindowsCaptivePortalDetectionThread) } void WindowsCaptivePortalDetectionThread::startWorker( const QString& ip, const QString& host, const QString& url, const QString& expectedResult) { logger.log() << "starting the captive portal detection"; auto guard = qScopeGuard([&] { emit detectionCompleted(false); }); if (!s_tunnelLib && !loadLibrary()) { logger.log() << "Failed to load the library"; return; } Q_ASSERT(s_tunnelLib); if (!s_captivePortalDetection) { logger.log() << "No captive portal function found"; return; } QByteArray ipArray(ip.toLocal8Bit()); gostring_t ipGo{ipArray.constData(), (size_t)ipArray.length()}; QByteArray hostArray(host.toLocal8Bit()); gostring_t hostGo{hostArray.constData(), (size_t)hostArray.length()}; QByteArray urlArray(url.toLocal8Bit()); gostring_t urlGo{urlArray.constData(), (size_t)hostArray.length()}; QByteArray expectedResutArray(expectedResult.toLocal8Bit()); gostring_t expectedResultGo{expectedResutArray.constData(), (size_t)expectedResutArray.length()}; int32_t result = s_captivePortalDetection(ipGo, hostGo, urlGo, expectedResultGo); switch (result) { case -1: logger.log() << "Failed to detect the captive portal"; return; case 0: logger.log() << "Captive portal detected!"; guard.dismiss(); emit detectionCompleted(true); return; case 1: logger.log() << "No captive portal detected"; return; default: logger.log() << "Invalid return value:" << result; return; } } mozilla-vpn-client-2.2.0/src/platforms/windows/windowscaptiveportaldetectionthread.h000066400000000000000000000013571404202232700312610ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WINDOWSCAPTIVEPORTALDETECTIONTHREAD_H #define WINDOWSCAPTIVEPORTALDETECTIONTHREAD_H #include class WindowsCaptivePortalDetectionThread final : public QObject { Q_OBJECT public: WindowsCaptivePortalDetectionThread(QObject* parent); ~WindowsCaptivePortalDetectionThread(); signals: void detectionCompleted(bool detected); public: void startWorker(const QString& ip, const QString& host, const QString& url, const QString& expectedResult); }; #endif // WINDOWSCAPTIVEPORTALDETECTIONTHREAD_H mozilla-vpn-client-2.2.0/src/platforms/windows/windowscommons.cpp000066400000000000000000000037771404202232700253330ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "windowscommons.h" #include "logger.h" #include #include #include #define TUNNEL_SERVICE_NAME L"WireGuardTunnel$mozvpn" constexpr const char* VPN_NAME = "MozillaVPN"; namespace { Logger logger(LOG_MAIN, "WindowsCommons"); } // A simple function to log windows error messages. void WindowsCommons::windowsLog(const QString& msg) { DWORD errorId = GetLastError(); LPSTR messageBuffer = nullptr; size_t size = FormatMessageA( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr, errorId, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&messageBuffer, 0, nullptr); std::string message(messageBuffer, size); logger.log() << msg << "-" << QString(message.c_str()); LocalFree(messageBuffer); } QString WindowsCommons::tunnelConfigFile() { QStringList paths = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); for (const QString& path : paths) { QDir dir(path); if (!dir.exists()) { continue; } QDir vpnDir(dir.filePath(VPN_NAME)); if (!vpnDir.exists()) { continue; } QString wireguardFile(vpnDir.filePath(QString("%1.conf").arg(VPN_NAME))); if (!QFileInfo::exists(wireguardFile)) { continue; } logger.log() << "Found the current wireguard configuration:" << wireguardFile; return wireguardFile; } for (const QString& path : paths) { QDir dir(path); QDir vpnDir(dir.filePath(VPN_NAME)); if (!vpnDir.exists() && !dir.mkdir(VPN_NAME)) { logger.log() << "Failed to create path Mozilla under" << path; continue; } return vpnDir.filePath(QString("%1.conf").arg(VPN_NAME)); } logger.log() << "Failed to create the right paths"; return QString(); } mozilla-vpn-client-2.2.0/src/platforms/windows/windowscommons.h000066400000000000000000000006571404202232700247720ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WINDOWSCOMMONS_H #define WINDOWSCOMMONS_H #include class WindowsCommons final { public: static void windowsLog(const QString& msg); static QString tunnelConfigFile(); }; #endif // WINDOWSCOMMONS_H mozilla-vpn-client-2.2.0/src/platforms/windows/windowscryptosettings.cpp000066400000000000000000000046521404202232700267520ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "cryptosettings.h" #include "logger.h" #include #include #include #include #define CRED_KEY L"Mozilla VPN" namespace { Logger logger(LOG_WINDOWS, "CryptoSettings"); bool s_initialized = false; QByteArray s_key; } // namespace void CryptoSettings::resetKey() { logger.log() << "Reset the key in the keychain"; if (s_initialized) { CredDeleteW(CRED_KEY, CRED_TYPE_GENERIC, 0); s_initialized = false; } } bool CryptoSettings::getKey(uint8_t key[CRYPTO_SETTINGS_KEY_SIZE]) { if (!s_initialized) { logger.log() << "Get key"; s_initialized = true; { PCREDENTIALW cred; if (CredReadW(CRED_KEY, CRED_TYPE_GENERIC, 0, &cred)) { s_key = QByteArray((char*)cred->CredentialBlob, cred->CredentialBlobSize); logger.log() << "Key found with length:" << s_key.length(); if (s_key.length() == CRYPTO_SETTINGS_KEY_SIZE) { memcpy(key, s_key.data(), CRYPTO_SETTINGS_KEY_SIZE); return true; } } else if (GetLastError() != ERROR_NOT_FOUND) { logger.log() << "Failed to retrieve the key"; return false; } } logger.log() << "Key not found. Let's create it."; s_key = QByteArray(CRYPTO_SETTINGS_KEY_SIZE, 0x00); QRandomGenerator* rg = QRandomGenerator::system(); for (int i = 0; i < CRYPTO_SETTINGS_KEY_SIZE; ++i) { s_key[i] = rg->generate() & 0xFF; } { CREDENTIALW cred; memset(&cred, 0, sizeof(cred)); cred.Comment = const_cast(CRED_KEY); cred.Type = CRED_TYPE_GENERIC; cred.TargetName = const_cast(CRED_KEY); cred.CredentialBlobSize = s_key.length(); cred.CredentialBlob = (LPBYTE)s_key.constData(); cred.Persist = CRED_PERSIST_ENTERPRISE; if (!CredWriteW(&cred, 0)) { logger.log() << "Failed to write the key"; return false; } } } if (s_key.length() == CRYPTO_SETTINGS_KEY_SIZE) { memcpy(key, s_key.data(), CRYPTO_SETTINGS_KEY_SIZE); return true; } logger.log() << "Invalid key"; return false; } // static CryptoSettings::Version CryptoSettings::getSupportedVersion() { return CryptoSettings::EncryptionChachaPolyV1; } mozilla-vpn-client-2.2.0/src/platforms/windows/windowsdatamigration.cpp000066400000000000000000000075321404202232700264740ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "windowsdatamigration.h" #include "logger.h" #include "mozillavpn.h" #include "settingsholder.h" #include #include #include #include namespace { Logger logger(LOG_WINDOWS, "WindowsDataMigration"); void migrateConfigFile(const QString& fileName) { SettingsHolder* settingsHolder = SettingsHolder::instance(); QSettings settings(fileName, QSettings::IniFormat); QString token = settings.value("FxA/Token").toString(); if (!token.isEmpty()) { MozillaVPN::instance()->setToken(token); } QString language = settings.value("Language/PreferredLanguage").toString(); if (!language.isEmpty()) { settingsHolder->setLanguageCode(language); } bool ipv6Enabled = settings.value("Network/EnableIPv6").toBool(); settingsHolder->setIpv6Enabled(ipv6Enabled); bool captivePortalAlert = settings.value("Network/CaptivePortalAlert").toBool(); settingsHolder->setCaptivePortalAlert(captivePortalAlert); // The 1.x setting file contains "unsecure" instead of "unsecured". bool unsecuredNetworkAlert = settings.value("Network/UnsecureNetworkAlert").toBool(); settingsHolder->setUnsecuredNetworkAlert(unsecuredNetworkAlert); bool localNetworkAccess = settings.value("Network/AllowLocalDeviceAccess").toBool(); settingsHolder->setLocalNetworkAccess(localNetworkAccess); } void migrateServersFile(const QString& fileName) { QFile file(fileName); if (!file.open(QFile::ReadOnly)) { return; } bool ok = MozillaVPN::instance()->setServerList(file.readAll()); Q_UNUSED(ok); } void migrateFxaFile(const QString& fileName) { QFile file(fileName); if (!file.open(QFile::ReadOnly)) { return; } MozillaVPN::instance()->accountChecked(file.readAll()); } void migrateWireguardFile(const QString& fileName) { MozillaVPN* vpn = MozillaVPN::instance(); QSettings settings(fileName, QSettings::IniFormat); const Device* device = vpn->deviceModel()->currentDevice(vpn->keys()); if (device) { QString privateKey = settings.value("Interface/PrivateKey").toString(); if (!privateKey.isEmpty()) { vpn->deviceAdded(Device::currentDeviceName(), device->publicKey(), privateKey); } } QString endpoint = settings.value("Peer/Endpoint").toString().split(":").at(0); if (!endpoint.isEmpty()) { ServerData serverData; if (vpn->serverCountryModel()->pickByIPv4Address(endpoint, serverData)) { serverData.writeSettings(); } } } } // namespace // static void WindowsDataMigration::migrate() { logger.log() << "Windows Data Migration"; QStringList paths = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); for (const QString& path : paths) { QDir dir(path); if (!dir.exists()) { continue; } QDir mozillaDir(dir.filePath("Mozilla")); if (!mozillaDir.exists()) { continue; } QDir fpnDir(mozillaDir.filePath("FirefoxPrivateNetworkVPN")); if (!fpnDir.exists()) { continue; } QString confFile(fpnDir.filePath("settings.conf")); if (!QFileInfo::exists(confFile)) { continue; } migrateConfigFile(confFile); QString serversFile(fpnDir.filePath("servers.json")); if (!QFileInfo::exists(serversFile)) { continue; } migrateServersFile(serversFile); QString fxaFile(fpnDir.filePath("FirefoxFxAUser.json")); if (!QFileInfo::exists(fxaFile)) { continue; } migrateFxaFile(fxaFile); QString wireguardFile(fpnDir.filePath("FirefoxPrivateNetworkVPN.conf")); if (!QFileInfo::exists(wireguardFile)) { continue; } migrateWireguardFile(wireguardFile); break; } } mozilla-vpn-client-2.2.0/src/platforms/windows/windowsdatamigration.h000066400000000000000000000005701404202232700261340ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WINDOWSDATAMIGRATION_H #define WINDOWSDATAMIGRATION_H class WindowsDataMigration final { public: static void migrate(); }; #endif // WINDOWSDATAMIGRATION_H mozilla-vpn-client-2.2.0/src/platforms/windows/windowsnetworkwatcher.cpp000066400000000000000000000076421404202232700267220ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "windowsnetworkwatcher.h" #include "leakdetector.h" #include "logger.h" #include "windowscommons.h" #include #pragma comment(lib, "Wlanapi.lib") namespace { Logger logger(LOG_WINDOWS, "WindowsNetworkWatcher"); } WindowsNetworkWatcher::WindowsNetworkWatcher(QObject* parent) : NetworkWatcherImpl(parent) { MVPN_COUNT_CTOR(WindowsNetworkWatcher); } WindowsNetworkWatcher::~WindowsNetworkWatcher() { MVPN_COUNT_DTOR(WindowsNetworkWatcher); if (m_wlanHandle) { WlanCloseHandle(m_wlanHandle, nullptr); } } void WindowsNetworkWatcher::initialize() { logger.log() << "initialize"; DWORD negotiatedVersion; if (WlanOpenHandle(2, nullptr, &negotiatedVersion, &m_wlanHandle) != ERROR_SUCCESS) { WindowsCommons::windowsLog("Failed to open the WLAN handle"); return; } DWORD prefNotifSource; if (WlanRegisterNotification(m_wlanHandle, WLAN_NOTIFICATION_SOURCE_MSM, true /* ignore duplicates */, wlanCallback, this, nullptr, &prefNotifSource) != ERROR_SUCCESS) { WindowsCommons::windowsLog("Failed to register a wlan callback"); return; } logger.log() << "callback registered"; } // static void WindowsNetworkWatcher::wlanCallback(PWLAN_NOTIFICATION_DATA data, PVOID context) { logger.log() << "Callback"; WindowsNetworkWatcher* that = static_cast(context); Q_ASSERT(that); that->processWlan(data); } void WindowsNetworkWatcher::processWlan(PWLAN_NOTIFICATION_DATA data) { logger.log() << "Processing wlan data"; if (!isActive()) { logger.log() << "The watcher is off"; return; } if (data->NotificationSource != WLAN_NOTIFICATION_SOURCE_MSM) { logger.log() << "The wlan source is not MSM"; return; } if (data->NotificationCode != wlan_notification_msm_connected) { logger.log() << "The wlan code is not MSM connected"; return; } PWLAN_CONNECTION_ATTRIBUTES connectionInfo = nullptr; DWORD connectionInfoSize = sizeof(WLAN_CONNECTION_ATTRIBUTES); WLAN_OPCODE_VALUE_TYPE opCode = wlan_opcode_value_type_invalid; DWORD result = WlanQueryInterface( m_wlanHandle, &data->InterfaceGuid, wlan_intf_opcode_current_connection, nullptr, &connectionInfoSize, (PVOID*)&connectionInfo, &opCode); if (result != ERROR_SUCCESS) { WindowsCommons::windowsLog("Failed to query the interface"); return; } auto guard = qScopeGuard([&] { WlanFreeMemory(connectionInfo); }); if (connectionInfo->wlanSecurityAttributes.dot11AuthAlgorithm != DOT11_AUTH_ALGO_80211_OPEN && connectionInfo->wlanSecurityAttributes.dot11CipherAlgorithm != DOT11_CIPHER_ALGO_WEP && connectionInfo->wlanSecurityAttributes.dot11CipherAlgorithm != DOT11_CIPHER_ALGO_WEP40 && connectionInfo->wlanSecurityAttributes.dot11CipherAlgorithm != DOT11_CIPHER_ALGO_WEP104) { logger.log() << "The network is secure enought"; return; } QString ssid; for (size_t i = 0; i < connectionInfo->wlanAssociationAttributes.dot11Ssid.uSSIDLength; ++i) { ssid.append(QString::asprintf( "%c", (char)connectionInfo->wlanAssociationAttributes.dot11Ssid.ucSSID[i])); } QString bssid; for (size_t i = 0; i < sizeof(connectionInfo->wlanAssociationAttributes.dot11Bssid); ++i) { if (i == 5) { bssid.append(QString::asprintf( "%.2X\n", connectionInfo->wlanAssociationAttributes.dot11Bssid[i])); } else { bssid.append(QString::asprintf( "%.2X-", connectionInfo->wlanAssociationAttributes.dot11Bssid[i])); } } logger.log() << "Unsecure network:" << ssid << "id:" << bssid; emit unsecuredNetwork(ssid, bssid); } mozilla-vpn-client-2.2.0/src/platforms/windows/windowsnetworkwatcher.h000066400000000000000000000015271404202232700263630ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WINDOWSNETWORKWATCHER_H #define WINDOWSNETWORKWATCHER_H #include "networkwatcherimpl.h" #include #include class WindowsNetworkWatcher final : public NetworkWatcherImpl { public: WindowsNetworkWatcher(QObject* parent); ~WindowsNetworkWatcher(); void initialize() override; private: static void wlanCallback(PWLAN_NOTIFICATION_DATA data, PVOID context); void processWlan(PWLAN_NOTIFICATION_DATA data); private: // The handle is set during the initialization. Windows calls processWlan() // to inform about network changes. HANDLE m_wlanHandle = nullptr; }; #endif // WINDOWSNETWORKWATCHER_H mozilla-vpn-client-2.2.0/src/platforms/windows/windowspingsendworker.cpp000066400000000000000000000030621404202232700267040ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "windowspingsendworker.h" #include "logger.h" #include "leakdetector.h" #include #include #include #include #pragma comment(lib, "Iphlpapi.lib") #pragma comment(lib, "Ws2_32.lib") namespace { Logger logger({LOG_WINDOWS, LOG_NETWORKING}, "WindowsPingSendWorker"); } WindowsPingSendWorker::WindowsPingSendWorker() { MVPN_COUNT_CTOR(WindowsPingSendWorker); } WindowsPingSendWorker::~WindowsPingSendWorker() { MVPN_COUNT_DTOR(WindowsPingSendWorker); } void WindowsPingSendWorker::sendPing(const QString& destination) { logger.log() << "WindowsPingSendWorker - start" << destination; IN_ADDR ip{}; if (InetPtonA(AF_INET, destination.toLocal8Bit(), &ip) != 1) { emit pingFailed(); return; } HANDLE icmpHandle = IcmpCreateFile(); if (icmpHandle == INVALID_HANDLE_VALUE) { emit pingFailed(); return; } constexpr WORD payloadSize = 1; unsigned char payload[payloadSize]{42}; constexpr DWORD replyBufferSize = sizeof(ICMP_ECHO_REPLY) + payloadSize + 8; unsigned char replyBuffer[replyBufferSize]{}; DWORD replyCount = IcmpSendEcho(icmpHandle, ip.S_un.S_addr, payload, payloadSize, nullptr, replyBuffer, replyBufferSize, 10000); IcmpCloseHandle(icmpHandle); if (replyCount == 0) { emit pingFailed(); return; } emit pingSucceeded(); } mozilla-vpn-client-2.2.0/src/platforms/windows/windowspingsendworker.h000066400000000000000000000011161404202232700263470ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WINDOWSPINGSENDWORKER_H #define WINDOWSPINGSENDWORKER_H #include "pingsendworker.h" class WindowsPingSendWorker final : public PingSendWorker { Q_OBJECT Q_DISABLE_COPY_MOVE(WindowsPingSendWorker) public: WindowsPingSendWorker(); ~WindowsPingSendWorker(); public slots: void sendPing(const QString& destination) override; }; #endif // WINDOWSPINGSENDWORKER_H mozilla-vpn-client-2.2.0/src/platforms/windows/windowsstartatbootwatcher.cpp000066400000000000000000000022711404202232700275700ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "windowsstartatbootwatcher.h" #include "leakdetector.h" #include "logger.h" #include #include #include namespace { Logger logger(LOG_WINDOWS, "WindowsStartAtBootWatcher"); } WindowsStartAtBootWatcher::WindowsStartAtBootWatcher(bool startAtBoot) { MVPN_COUNT_CTOR(WindowsStartAtBootWatcher); logger.log() << "StartAtBoot watcher"; startAtBootChanged(startAtBoot); } WindowsStartAtBootWatcher::~WindowsStartAtBootWatcher() { MVPN_COUNT_DTOR(WindowsStartAtBootWatcher); } void WindowsStartAtBootWatcher::startAtBootChanged(bool startAtBoot) { logger.log() << "StartAtBoot changed:" << startAtBoot; QSettings settings( "HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat); if (startAtBoot) { settings.setValue( "Mozilla_VPN", QDir::toNativeSeparators(QCoreApplication::applicationFilePath())); } else { settings.remove("Mozilla_VPN"); } settings.sync(); } mozilla-vpn-client-2.2.0/src/platforms/windows/windowsstartatbootwatcher.h000066400000000000000000000011441404202232700272330ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WINDOWSSTARTATBOOTWATCHER_H #define WINDOWSSTARTATBOOTWATCHER_H #include class WindowsStartAtBootWatcher final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(WindowsStartAtBootWatcher) public: explicit WindowsStartAtBootWatcher(bool startAtBoot); ~WindowsStartAtBootWatcher(); public slots: void startAtBootChanged(bool value); }; #endif // WINDOWSSTARTATBOOTWATCHER_H mozilla-vpn-client-2.2.0/src/qml.qrc000066400000000000000000000472341404202232700173340ustar00rootroot00000000000000 ui/main.qml ui/components/VPNButton.qml ui/components/VPNMenu.qml ui/components/VPNSettingsItem.qml ui/components/VPNToggle.qml ui/components/VPNServerListToggle.qml ui/components/VPNRadioButtonLabel.qml ui/components/VPNRadioDelegate.qml ui/components/VPNBoldLabel.qml ui/components/VPNControllerView.qml ui/components/VPNLightLabel.qml ui/components/VPNIcon.qml ui/components/VPNChevron.qml ui/components/VPNPopupButton.qml ui/components/VPNPanel.qml ui/components/VPNFooterLink.qml ui/components/VPNHeaderLink.qml ui/components/VPNHeadline.qml ui/components/VPNAlert.qml ui/components/VPNConnectionStability.qml ui/components/VPNInterLabel.qml ui/components/VPNIconAndLabel.qml ui/components/VPNClickableRow.qml ui/components/VPNTextBlock.qml ui/components/VPNExternalLinkListItem.qml ui/components/VPNSubtitle.qml ui/components/VPNIconButton.qml ui/components/VPNDevicesListHeader.qml ui/components/VPNGetHelp.qml ui/components/VPNAnimatedRings.qml ui/components/VPNConnectionInfo.qml ui/components/VPNCheckBoxRow.qml ui/components/VPNMainImage.qml ui/components/VPNCheckBox.qml ui/components/VPNCheckBoxAlert.qml ui/components/VPNRadioSublabel.qml ui/components/VPNLinkButton.qml ui/components/VPNGraphLegendMarker.qml ui/components/VPNDropShadow.qml ui/components/VPNButtonBase.qml ui/components/VPNSystemAlert.qml ui/components/VPNSignOut.qml ui/components/VPNUIStates.qml ui/components/VPNList.qml ui/components/VPNServerCountry.qml ui/themes/themes.js ui/settings/ViewLanguage.qml ui/settings/ViewNetworkSettings.qml ui/settings/ViewAppPermissions.qml ui/settings/ViewNotifications.qml ui/settings/ViewSettingsMenu.qml ui/states/StateSubscriptionNeeded.qml ui/states/StateSubscriptionValidation.qml ui/states/StateSubscriptionBlocked.qml ui/states/StateMain.qml ui/states/StateInitialize.qml ui/states/StateAuthenticating.qml ui/states/StatePostAuthentication.qml ui/states/StateUpdateRequired.qml ui/states/StateDeviceLimit.qml ui/states/StateBackendFailure.qml ui/views/ViewMain.qml ui/views/ViewDevices.qml ui/views/ViewServers.qml ui/views/ViewSettings.qml ui/views/ViewInitialize.qml ui/views/ViewOnboarding.qml ui/views/ViewUpdate.qml ui/resources/flags/AC.png ui/resources/flags/AD.png ui/resources/flags/AE.png ui/resources/flags/AF.png ui/resources/flags/AG.png ui/resources/flags/AI.png ui/resources/flags/AL.png ui/resources/flags/AM.png ui/resources/flags/AO.png ui/resources/flags/AQ.png ui/resources/flags/AR.png ui/resources/flags/AS.png ui/resources/flags/AT.png ui/resources/flags/AU.png ui/resources/flags/AW.png ui/resources/flags/AX.png ui/resources/flags/AZ.png ui/resources/flags/BA.png ui/resources/flags/BB.png ui/resources/flags/BD.png ui/resources/flags/BE.png ui/resources/flags/BF.png ui/resources/flags/BG.png ui/resources/flags/BH.png ui/resources/flags/BI.png ui/resources/flags/BJ.png ui/resources/flags/BL.png ui/resources/flags/BM.png ui/resources/flags/BN.png ui/resources/flags/BO.png ui/resources/flags/BQ.png ui/resources/flags/BR.png ui/resources/flags/BS.png ui/resources/flags/BT.png ui/resources/flags/BV.png ui/resources/flags/BW.png ui/resources/flags/BY.png ui/resources/flags/BZ.png ui/resources/flags/CA.png ui/resources/flags/CC.png ui/resources/flags/CD.png ui/resources/flags/CF.png ui/resources/flags/CG.png ui/resources/flags/CH.png ui/resources/flags/CI.png ui/resources/flags/CK.png ui/resources/flags/CL.png ui/resources/flags/CM.png ui/resources/flags/CN.png ui/resources/flags/CO.png ui/resources/flags/CP.png ui/resources/flags/CR.png ui/resources/flags/CU.png ui/resources/flags/CV.png ui/resources/flags/CW.png ui/resources/flags/CX.png ui/resources/flags/CY.png ui/resources/flags/CZ.png ui/resources/flags/DE.png ui/resources/flags/DG.png ui/resources/flags/DJ.png ui/resources/flags/DK.png ui/resources/flags/DM.png ui/resources/flags/DO.png ui/resources/flags/DZ.png ui/resources/flags/EA.png ui/resources/flags/EC.png ui/resources/flags/EE.png ui/resources/flags/EG.png ui/resources/flags/EH.png ui/resources/flags/ER.png ui/resources/flags/ES.png ui/resources/flags/ET.png ui/resources/flags/EU.png ui/resources/flags/FI.png ui/resources/flags/FJ.png ui/resources/flags/FK.png ui/resources/flags/FM.png ui/resources/flags/FO.png ui/resources/flags/FR.png ui/resources/flags/GA.png ui/resources/flags/GB.png ui/resources/flags/GD.png ui/resources/flags/GE.png ui/resources/flags/GF.png ui/resources/flags/GG.png ui/resources/flags/GH.png ui/resources/flags/GI.png ui/resources/flags/GL.png ui/resources/flags/GM.png ui/resources/flags/GN.png ui/resources/flags/GP.png ui/resources/flags/GQ.png ui/resources/flags/GR.png ui/resources/flags/GS.png ui/resources/flags/GT.png ui/resources/flags/GU.png ui/resources/flags/GW.png ui/resources/flags/GY.png ui/resources/flags/HK.png ui/resources/flags/HM.png ui/resources/flags/HN.png ui/resources/flags/HR.png ui/resources/flags/HT.png ui/resources/flags/HU.png ui/resources/flags/IC.png ui/resources/flags/ID.png ui/resources/flags/IE.png ui/resources/flags/IL.png ui/resources/flags/IM.png ui/resources/flags/IN.png ui/resources/flags/IO.png ui/resources/flags/IQ.png ui/resources/flags/IR.png ui/resources/flags/IS.png ui/resources/flags/IT.png ui/resources/flags/JE.png ui/resources/flags/JM.png ui/resources/flags/JO.png ui/resources/flags/JP.png ui/resources/flags/KE.png ui/resources/flags/KG.png ui/resources/flags/KH.png ui/resources/flags/KI.png ui/resources/flags/KM.png ui/resources/flags/KN.png ui/resources/flags/KP.png ui/resources/flags/KR.png ui/resources/flags/KW.png ui/resources/flags/KY.png ui/resources/flags/KZ.png ui/resources/flags/LA.png ui/resources/flags/LB.png ui/resources/flags/LC.png ui/resources/flags/LI.png ui/resources/flags/LK.png ui/resources/flags/LR.png ui/resources/flags/LS.png ui/resources/flags/LT.png ui/resources/flags/LU.png ui/resources/flags/LV.png ui/resources/flags/LY.png ui/resources/flags/MA.png ui/resources/flags/MC.png ui/resources/flags/MD.png ui/resources/flags/ME.png ui/resources/flags/MF.png ui/resources/flags/MG.png ui/resources/flags/MH.png ui/resources/flags/MK.png ui/resources/flags/ML.png ui/resources/flags/MM.png ui/resources/flags/MN.png ui/resources/flags/MO.png ui/resources/flags/MP.png ui/resources/flags/MQ.png ui/resources/flags/MR.png ui/resources/flags/MS.png ui/resources/flags/MT.png ui/resources/flags/MU.png ui/resources/flags/MV.png ui/resources/flags/MW.png ui/resources/flags/MX.png ui/resources/flags/MY.png ui/resources/flags/MZ.png ui/resources/flags/NA.png ui/resources/flags/NC.png ui/resources/flags/NE.png ui/resources/flags/NF.png ui/resources/flags/NG.png ui/resources/flags/NI.png ui/resources/flags/NL.png ui/resources/flags/NO.png ui/resources/flags/NP.png ui/resources/flags/NR.png ui/resources/flags/NU.png ui/resources/flags/NZ.png ui/resources/flags/OM.png ui/resources/flags/PA.png ui/resources/flags/PE.png ui/resources/flags/PF.png ui/resources/flags/PG.png ui/resources/flags/PH.png ui/resources/flags/PK.png ui/resources/flags/PL.png ui/resources/flags/PM.png ui/resources/flags/PN.png ui/resources/flags/PR.png ui/resources/flags/PS.png ui/resources/flags/PT.png ui/resources/flags/PW.png ui/resources/flags/PY.png ui/resources/flags/QA.png ui/resources/flags/RE.png ui/resources/flags/RO.png ui/resources/flags/RS.png ui/resources/flags/RU.png ui/resources/flags/RW.png ui/resources/flags/SA.png ui/resources/flags/SB.png ui/resources/flags/SC.png ui/resources/flags/SD.png ui/resources/flags/SE.png ui/resources/flags/SG.png ui/resources/flags/SH.png ui/resources/flags/SI.png ui/resources/flags/SJ.png ui/resources/flags/SK.png ui/resources/flags/SL.png ui/resources/flags/SM.png ui/resources/flags/SN.png ui/resources/flags/SO.png ui/resources/flags/SR.png ui/resources/flags/SS.png ui/resources/flags/ST.png ui/resources/flags/SV.png ui/resources/flags/SX.png ui/resources/flags/SY.png ui/resources/flags/SZ.png ui/resources/flags/TA.png ui/resources/flags/TC.png ui/resources/flags/TD.png ui/resources/flags/TF.png ui/resources/flags/TG.png ui/resources/flags/TH.png ui/resources/flags/TJ.png ui/resources/flags/TK.png ui/resources/flags/TL.png ui/resources/flags/TM.png ui/resources/flags/TN.png ui/resources/flags/TO.png ui/resources/flags/TR.png ui/resources/flags/TT.png ui/resources/flags/TV.png ui/resources/flags/TW.png ui/resources/flags/TZ.png ui/resources/flags/UA.png ui/resources/flags/UG.png ui/resources/flags/UM.png ui/resources/flags/UN.png ui/resources/flags/US.png ui/resources/flags/UY.png ui/resources/flags/UZ.png ui/resources/flags/VA.png ui/resources/flags/VC.png ui/resources/flags/VE.png ui/resources/flags/VG.png ui/resources/flags/VI.png ui/resources/flags/VN.png ui/resources/flags/VU.png ui/resources/flags/WF.png ui/resources/flags/WS.png ui/resources/flags/XK.png ui/resources/flags/YE.png ui/resources/flags/YT.png ui/resources/flags/ZA.png ui/resources/flags/ZM.png ui/resources/flags/ZW.png ui/resources/delete.svg ui/resources/devices.svg ui/resources/back.svg ui/resources/settings.svg ui/resources/logo.svg ui/resources/chevron.svg ui/resources/close-white.svg ui/resources/externalLink.svg ui/resources/arrow-toggle.svg ui/resources/connection.svg ui/resources/onboarding/onboarding1.svg ui/resources/onboarding/onboarding4.svg ui/resources/onboarding/onboarding3.svg ui/resources/onboarding/onboarding2.svg ui/resources/fonts/InterUI.otf ui/resources/fonts/Metropolis.otf ui/resources/fonts/Metropolis-SemiBold.otf ui/resources/spinner.svg ui/resources/warning.svg ui/resources/warning-orange.svg ui/resources/settings/getHelp.svg ui/resources/settings/feedback.svg ui/resources/settings/language.svg ui/resources/settings/apps.svg ui/resources/settings/aboutUs.svg ui/resources/settings/notifications.svg ui/resources/settings/networkSettings.svg ui/resources/devicesLimit.svg ui/resources/removeDevice.png ui/resources/settings-white.svg ui/resources/shield-off.svg ui/resources/shield-on.svg ui/resources/switching.svg ui/resources/connection-info.svg ui/resources/checkmark.svg ui/resources/update-lock.svg ui/resources/warning-white.svg ui/resources/connection-info-dark.svg ui/resources/down.svg ui/resources/close-dark.svg ui/resources/logo-on.svg ui/resources/logo-on.png ui/resources/logo-animated1.svg ui/resources/logo-animated1.png ui/resources/logo-animated2.svg ui/resources/logo-animated2.png ui/resources/logo-animated3.svg ui/resources/logo-animated3.png ui/resources/logo-animated4.svg ui/resources/logo-animated4.png ui/resources/logo-generic.svg ui/resources/logo-generic.png ui/components/VPNToolTip.qml ui/views/ViewSubscriptionNeeded.qml ui/components/VPNCallout.qml ui/components/VPNFlickable.qml ui/resources/updateRecommended.svg ui/resources/updateRequired.svg ui/components/VPNAboutUs.qml ui/components/VPNRemoveDevicePopup.qml ui/views/ViewLogs.qml ui/components/VPNStackView.qml ui/components/VPNFocusOutline.qml ui/components/VPNMouseArea.qml ui/components/VPNFocusBorder.qml ui/resources/buttonLoader.svg ui/components/VPNButtonLoader.qml ui/resources/quick-access.svg platforms/android/androidauthenticationview.qml ui/components/VPNLogsButton.qml ui/resources/copy.svg ui/components/VPNLoader.qml ui/components/VPNGreyLink.qml ui/components/VPNExpandableAppList.qml ui/components/VPNVerticalSpacer.qml ui/components/VPNWasmAlerts.qml ui/components/VPNWasmMenu.qml ui/components/VPNWasmMenuButton.qml ui/components/VPNWasmHeader.qml ui/views/ViewErrorFullScreen.qml ui/components/VPNControllerNav.qml ui/components/VPNSettingsToggle.qml ui/components/VPNDeviceListItem.qml mozilla-vpn-client-2.2.0/src/qmlengineholder.cpp000066400000000000000000000024551404202232700217110ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "qmlengineholder.h" #include "leakdetector.h" #include "logger.h" #include namespace { Logger logger(LOG_MAIN, "QmlEngineHolder"); QmlEngineHolder* s_instance = nullptr; } // namespace QmlEngineHolder::QmlEngineHolder() { MVPN_COUNT_CTOR(QmlEngineHolder); Q_ASSERT(!s_instance); s_instance = this; } QmlEngineHolder::~QmlEngineHolder() { MVPN_COUNT_DTOR(QmlEngineHolder); Q_ASSERT(s_instance == this); s_instance = nullptr; } // static QmlEngineHolder* QmlEngineHolder::instance() { Q_ASSERT(s_instance); return s_instance; } // static bool QmlEngineHolder::exists() { return !!s_instance; } QNetworkAccessManager* QmlEngineHolder::networkAccessManager() { return m_engine.networkAccessManager(); } QWindow* QmlEngineHolder::window() const { QObject* rootObject = m_engine.rootObjects().first(); return qobject_cast(rootObject); } void QmlEngineHolder::showWindow() { QWindow* w = window(); Q_ASSERT(w); w->show(); w->raise(); w->requestActivate(); } void QmlEngineHolder::hideWindow() { QWindow* w = window(); Q_ASSERT(w); w->hide(); } mozilla-vpn-client-2.2.0/src/qmlengineholder.h000066400000000000000000000014541404202232700213540ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef QMLENGINEHOLDER_H #define QMLENGINEHOLDER_H #include "networkmanager.h" #include class QWindow; class QmlEngineHolder final : public NetworkManager { Q_DISABLE_COPY_MOVE(QmlEngineHolder) public: QmlEngineHolder(); ~QmlEngineHolder(); static QmlEngineHolder* instance(); static bool exists(); QQmlApplicationEngine* engine() { return &m_engine; } QNetworkAccessManager* networkAccessManager() override; QWindow* window() const; void showWindow(); void hideWindow(); private: QQmlApplicationEngine m_engine; }; #endif // QMLENGINEHOLDER_H mozilla-vpn-client-2.2.0/src/releasemonitor.cpp000066400000000000000000000046701404202232700215650ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "releasemonitor.h" #include "constants.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "timersingleshot.h" #include "update/updater.h" namespace { Logger logger(LOG_MAIN, "ReleaseMonitor"); } ReleaseMonitor::ReleaseMonitor() { MVPN_COUNT_CTOR(ReleaseMonitor); m_timer.setSingleShot(true); connect(&m_timer, &QTimer::timeout, this, &ReleaseMonitor::runInternal); } ReleaseMonitor::~ReleaseMonitor() { MVPN_COUNT_DTOR(ReleaseMonitor); } void ReleaseMonitor::runSoon() { logger.log() << "ReleaseManager - Scheduling a quick timer"; TimerSingleShot::create(this, 0, [this] { runInternal(); }); } void ReleaseMonitor::runInternal() { logger.log() << "ReleaseMonitor started"; Updater* updater = Updater::create(this, false); Q_ASSERT(updater); updater->start(); connect(updater, &Updater::updateRequired, this, &ReleaseMonitor::updateRequired); connect(updater, &Updater::updateRecommended, this, &ReleaseMonitor::updateRecommended); connect(updater, &QObject::destroyed, this, &ReleaseMonitor::releaseChecked); connect(updater, &QObject::destroyed, this, &ReleaseMonitor::schedule); } void ReleaseMonitor::schedule() { logger.log() << "ReleaseMonitor scheduling"; m_timer.start(Constants::RELEASE_MONITOR_MSEC); } void ReleaseMonitor::updateRequired() { logger.log() << "update required"; MozillaVPN::instance()->setUpdateRecommended(false); MozillaVPN::instance()->controller()->updateRequired(); } void ReleaseMonitor::updateRecommended() { logger.log() << "Update recommended"; MozillaVPN::instance()->setUpdateRecommended(true); } void ReleaseMonitor::update() { logger.log() << "Update"; Updater* updater = Updater::create(this, true); if (!updater) { logger.log() << "No updater supported for this platform. Fallback"; MozillaVPN* vpn = MozillaVPN::instance(); Q_ASSERT(vpn); vpn->openLink(MozillaVPN::LinkUpdate); vpn->setUpdating(false); return; } // The updater, in download mode, is not destroyed. So, if this happens, // probably something went wrong. connect(updater, &QObject::destroyed, [] { MozillaVPN* vpn = MozillaVPN::instance(); Q_ASSERT(vpn); vpn->setUpdating(false); }); updater->start(); } mozilla-vpn-client-2.2.0/src/releasemonitor.h000066400000000000000000000012721404202232700212250ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef RELEASEMONITOR_H #define RELEASEMONITOR_H #include #include class ReleaseMonitor final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(ReleaseMonitor) public: ReleaseMonitor(); ~ReleaseMonitor(); void runSoon(); void update(); signals: // for testing void releaseChecked(); private: void schedule(); void runInternal(); void updateRequired(); void updateRecommended(); private: QTimer m_timer; }; #endif // RELEASEMONITOR_H mozilla-vpn-client-2.2.0/src/rfc1918.cpp000066400000000000000000000010131404202232700176160ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "rfc1918.h" // static QList RFC1918::ipv4() { QList list; // From RFC1918: https://tools.ietf.org/html/rfc1918 list.append(IPAddress::create("10.0.0.0/8")); list.append(IPAddress::create("172.16.0.0/12")); list.append(IPAddress::create("192.168.0.0/16")); return list; } mozilla-vpn-client-2.2.0/src/rfc1918.h000066400000000000000000000005671404202232700173000ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef RFC1918_H #define RFC1918_H #include "ipaddress.h" #include class RFC1918 final { public: static QList ipv4(); }; #endif // RFC1918_H mozilla-vpn-client-2.2.0/src/rfc4193.cpp000066400000000000000000000026321404202232700176240ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "rfc4193.h" // static QList RFC4193::ipv6() { // Allow all IP's except fc00::/7 QList list; list.append(IPAddressRange("0:0:0:0:0:0:0:0", 1, IPAddressRange::IPv6)); list.append(IPAddressRange("8000::", 2, IPAddressRange::IPv6)); list.append(IPAddressRange("c000::", 3, IPAddressRange::IPv6)); list.append(IPAddressRange("e000::", 4, IPAddressRange::IPv6)); list.append(IPAddressRange("f000::", 5, IPAddressRange::IPv6)); list.append(IPAddressRange("f800::", 6, IPAddressRange::IPv6)); list.append(IPAddressRange("fc01::", 16, IPAddressRange::IPv6)); list.append(IPAddressRange("fc02::", 15, IPAddressRange::IPv6)); list.append(IPAddressRange("fc04::", 14, IPAddressRange::IPv6)); list.append(IPAddressRange("fc08::", 13, IPAddressRange::IPv6)); list.append(IPAddressRange("fc10::", 12, IPAddressRange::IPv6)); list.append(IPAddressRange("fc20::", 11, IPAddressRange::IPv6)); list.append(IPAddressRange("fc40::", 10, IPAddressRange::IPv6)); list.append(IPAddressRange("fc80::", 9, IPAddressRange::IPv6)); list.append(IPAddressRange("fd00::", 8, IPAddressRange::IPv6)); list.append(IPAddressRange("fe00::", 7, IPAddressRange::IPv6)); return list; } mozilla-vpn-client-2.2.0/src/rfc4193.h000066400000000000000000000007501404202232700172700ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef RFC4193_H #define RFC4193_H #include "ipaddressrange.h" #include class RFC4193 final { public: // Note: this returns the "opposite" of the RFC4193: what does not be treated // as local network. static QList ipv6(); }; #endif // RFC4193_H mozilla-vpn-client-2.2.0/src/serveri18n.cpp000066400000000000000000000066511404202232700205440ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "serveri18n.h" #include "logger.h" #include "settingsholder.h" #include #include #include #include #include namespace { Logger logger(LOG_MAIN, "ServerI18N"); bool s_initialized = false; QHash s_items; QString itemKey(const QString& languageCode, const QString& countryCode, const QString& city = QString()) { return QString("%1^%2^%3").arg(languageCode).arg(countryCode).arg(city); } void addCity(const QString& countryCode, const QJsonValue& value) { if (!value.isObject()) { return; } QJsonObject obj = value.toObject(); QString cityName = obj["city"].toString(); if (cityName.isEmpty()) { logger.log() << "Empty city string"; return; } QJsonValue languages = obj["languages"]; if (!languages.isObject()) { logger.log() << "Empty language list"; return; } QJsonObject languageObj = languages.toObject(); for (const QString& languageCode : languageObj.keys()) { s_items.insert(itemKey(languageCode, countryCode, cityName), languageObj[languageCode].toString()); } } void addCountry(const QJsonValue& value) { if (!value.isObject()) { return; } QJsonObject obj = value.toObject(); QString countryCode = obj["countryCode"].toString(); if (countryCode.isEmpty()) { logger.log() << "Empty countryCode string"; return; } QJsonValue languages = obj["languages"]; if (!languages.isObject()) { logger.log() << "Empty language list"; return; } QJsonObject languageObj = languages.toObject(); for (const QString& languageCode : languageObj.keys()) { s_items.insert(itemKey(languageCode, countryCode), languageObj[languageCode].toString()); } QJsonValue cities = obj["cities"]; if (!cities.isArray()) { logger.log() << "Empty city list"; return; } QJsonArray cityArray = cities.toArray(); for (const QJsonValue city : cityArray) { addCity(countryCode, city); } } void maybeInitialize() { if (s_initialized) { return; } s_initialized = true; QFile file(":/i18n/servers.json"); if (!file.open(QFile::ReadOnly | QFile::Text)) { logger.log() << "Failed to open the servers.json"; return; } QJsonDocument json = QJsonDocument::fromJson(file.readAll()); if (!json.isArray()) { logger.log() << "Invalid format (expected array)"; return; } QJsonArray array = json.array(); for (const QJsonValue country : array) { addCountry(country); } } QString translateItem(const QString& countryCode, const QString& cityName, const QString& fallback) { maybeInitialize(); return s_items.value(itemKey(SettingsHolder::instance()->languageCode(), countryCode, cityName), fallback); } } // namespace // static QString ServerI18N::translateCountryName(const QString& countryCode, const QString& countryName) { return translateItem(countryCode, "", countryName); } // static QString ServerI18N::translateCityName(const QString& countryCode, const QString& cityName) { return translateItem(countryCode, cityName, cityName); } mozilla-vpn-client-2.2.0/src/serveri18n.h000066400000000000000000000011151404202232700201770ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef SERVERI18N_H #define SERVERI18N_H #include class ServerI18N final { public: static QString translateCountryName(const QString& countryCode, const QString& countryName); static QString translateCityName(const QString& countryCode, const QString& cityName); }; #endif // SERVERI18N_H mozilla-vpn-client-2.2.0/src/settingsholder.cpp000066400000000000000000000302731404202232700215710ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "settingsholder.h" #include "cryptosettings.h" #include "featurelist.h" #include "leakdetector.h" #include "logger.h" #include constexpr bool SETTINGS_IPV6ENABLED_DEFAULT = true; constexpr bool SETTINGS_LOCALNETWORKACCESS_DEFAULT = false; constexpr bool SETTINGS_UNSECUREDNETWORKALERT_DEFAULT = true; constexpr bool SETTINGS_CAPTIVEPORTALALERT_DEFAULT = true; constexpr bool SETTINGS_STARTATBOOT_DEFAULT = false; constexpr bool SETTINGS_PROTECTSELECTEDAPPS_DEFAULT = false; const QStringList SETTINGS_VPNDISABLEDAPPS_DEFAULT = QStringList(); constexpr const char* SETTINGS_IPV6ENABLED = "ipv6Enabled"; constexpr const char* SETTINGS_LOCALNETWORKACCESS = "localNetworkAccess"; constexpr const char* SETTINGS_UNSECUREDNETWORKALERT = "unsecuredNetworkAlert"; constexpr const char* SETTINGS_CAPTIVEPORTALALERT = "captivePortalAlert"; constexpr const char* SETTINGS_STARTATBOOT = "startAtBoot"; constexpr const char* SETTINGS_LANGUAGECODE = "languageCode"; constexpr const char* SETTINGS_PREVIOUSLANGUAGECODE = "previousLanguageCode"; constexpr const char* SETTINGS_SYSTEMLANGUAGECODEMIGRATED = "systemLanguageCodeMigrated"; constexpr const char* SETTINGS_TOKEN = "token"; constexpr const char* SETTINGS_SERVERS = "servers"; constexpr const char* SETTINGS_PRIVATEKEY = "privateKey"; constexpr const char* SETTINGS_PUBLICKEY = "publicKey"; constexpr const char* SETTINGS_USER_AVATAR = "user/avatar"; constexpr const char* SETTINGS_USER_DISPLAYNAME = "user/displayName"; constexpr const char* SETTINGS_USER_EMAIL = "user/email"; constexpr const char* SETTINGS_USER_MAXDEVICES = "user/maxDevices"; constexpr const char* SETTINGS_USER_SUBSCRIPTIONNEEDED = "user/subscriptionNeeded"; constexpr const char* SETTINGS_CURRENTSERVER_COUNTRYCODE = "currentServer/countryCode"; constexpr const char* SETTINGS_CURRENTSERVER_COUNTRY = "currentServer/country"; constexpr const char* SETTINGS_CURRENTSERVER_CITY = "currentServer/city"; constexpr const char* SETTINGS_DEVICES = "devices"; constexpr const char* SETTINGS_IAPPRODUCTS = "iapProducts"; constexpr const char* SETTINGS_CAPTIVEPORTALIPV4ADDRESSES = "captivePortal/ipv4Addresses"; constexpr const char* SETTINGS_CAPTIVEPORTALIPV6ADDRESSES = "captivePortal/ipv6Addresses"; constexpr const char* SETTINGS_POSTAUTHENTICATIONSHOWN = "postAuthenticationShown"; constexpr const char* SETTINGS_PROTECTSELECTEDAPPS = "protectSelectedApps"; constexpr const char* SETTINGS_VPNDISABLEDAPPS = "vpnDisabledApps"; #ifdef MVPN_IOS constexpr const char* SETTINGS_NATIVEIOSDATAMIGRATED = "nativeIOSDataMigrated"; constexpr const char* SETTINGS_SUBSCRIPTIONTRANSACTIONS = "subscriptionTransactions"; #endif #ifdef MVPN_ANDROID constexpr const char* SETTINGS_NATIVEANDROIDSDATAMIGRATED = "nativeAndroidDataMigrated"; #endif #ifdef MVPN_WINDOWS constexpr const char* SETTINGS_NATIVEWINDOWSDATAMIGRATED = "nativeWindowsDataMigrated"; #endif namespace { Logger logger(LOG_MAIN, "SettingsHolder"); SettingsHolder* s_instance = nullptr; } // namespace // static SettingsHolder* SettingsHolder::instance() { Q_ASSERT(s_instance); return s_instance; } #ifndef UNIT_TEST const QSettings::Format MozFormat = QSettings::registerFormat( "moz", CryptoSettings::readFile, CryptoSettings::writeFile); #endif SettingsHolder::SettingsHolder() : #ifndef UNIT_TEST m_settings(MozFormat, QSettings::UserScope, "mozilla", "vpn") #else m_settings("mozilla_testing", "vpn") #endif { MVPN_COUNT_CTOR(SettingsHolder); logger.log() << "Creating SettingsHolder instance"; Q_ASSERT(!s_instance); s_instance = this; } SettingsHolder::~SettingsHolder() { MVPN_COUNT_DTOR(SettingsHolder); Q_ASSERT(s_instance == this); s_instance = nullptr; #ifdef UNIT_TEST m_settings.clear(); #endif } void SettingsHolder::clear() { logger.log() << "Clean up the settings"; m_settings.remove(SETTINGS_TOKEN); m_settings.remove(SETTINGS_SERVERS); m_settings.remove(SETTINGS_PRIVATEKEY); m_settings.remove(SETTINGS_USER_AVATAR); m_settings.remove(SETTINGS_USER_DISPLAYNAME); m_settings.remove(SETTINGS_USER_EMAIL); m_settings.remove(SETTINGS_USER_MAXDEVICES); m_settings.remove(SETTINGS_USER_SUBSCRIPTIONNEEDED); m_settings.remove(SETTINGS_CURRENTSERVER_COUNTRYCODE); m_settings.remove(SETTINGS_CURRENTSERVER_COUNTRY); m_settings.remove(SETTINGS_CURRENTSERVER_CITY); m_settings.remove(SETTINGS_DEVICES); m_settings.remove(SETTINGS_IAPPRODUCTS); m_settings.remove(SETTINGS_POSTAUTHENTICATIONSHOWN); // We do not remove language, ipv6 and localnetwork settings. } #define GETSETDEFAULT(def, type, toType, key, has, get, set, signal) \ bool SettingsHolder::has() const { return m_settings.contains(key); } \ type SettingsHolder::get() const { \ if (!has()) { \ return def; \ } \ return m_settings.value(key).toType(); \ } \ void SettingsHolder::set(const type& value) { \ logger.log() << "Setting" << key << "to" << value; \ m_settings.setValue(key, value); \ emit signal(value); \ } GETSETDEFAULT(SETTINGS_IPV6ENABLED_DEFAULT, bool, toBool, SETTINGS_IPV6ENABLED, hasIpv6Enabled, ipv6Enabled, setIpv6Enabled, ipv6EnabledChanged) GETSETDEFAULT(FeatureList::instance()->localNetworkAccessSupported() && SETTINGS_LOCALNETWORKACCESS_DEFAULT, bool, toBool, SETTINGS_LOCALNETWORKACCESS, hasLocalNetworkAccess, localNetworkAccess, setLocalNetworkAccess, localNetworkAccessChanged) GETSETDEFAULT( FeatureList::instance()->unsecuredNetworkNotificationSupported() && SETTINGS_UNSECUREDNETWORKALERT_DEFAULT, bool, toBool, SETTINGS_UNSECUREDNETWORKALERT, hasUnsecuredNetworkAlert, unsecuredNetworkAlert, setUnsecuredNetworkAlert, unsecuredNetworkAlertChanged) GETSETDEFAULT(FeatureList::instance()->captivePortalNotificationSupported() && SETTINGS_CAPTIVEPORTALALERT_DEFAULT, bool, toBool, SETTINGS_CAPTIVEPORTALALERT, hasCaptivePortalAlert, captivePortalAlert, setCaptivePortalAlert, captivePortalAlertChanged) GETSETDEFAULT(FeatureList::instance()->startOnBootSupported() && SETTINGS_STARTATBOOT_DEFAULT, bool, toBool, SETTINGS_STARTATBOOT, hasStartAtBoot, startAtBoot, setStartAtBoot, startAtBootChanged) GETSETDEFAULT(FeatureList::instance()->protectSelectedAppsSupported() && SETTINGS_PROTECTSELECTEDAPPS_DEFAULT, bool, toBool, SETTINGS_PROTECTSELECTEDAPPS, hasProtectSelectedApps, protectSelectedApps, setProtectSelectedApps, protectSelectedAppsChanged) GETSETDEFAULT(SETTINGS_VPNDISABLEDAPPS_DEFAULT, QStringList, toStringList, SETTINGS_VPNDISABLEDAPPS, hasVpnDisabledApps, vpnDisabledApps, setVpnDisabledApps, vpnDisabledAppsChanged) #undef GETSETDEFAULT #define GETSET(type, toType, key, has, get, set) \ bool SettingsHolder::has() const { return m_settings.contains(key); } \ type SettingsHolder::get() const { \ Q_ASSERT(has()); \ return m_settings.value(key).toType(); \ } \ void SettingsHolder::set(const type& value) { \ logger.log() << "Setting" << key; \ m_settings.setValue(key, value); \ } GETSET(QString, toString, SETTINGS_TOKEN, hasToken, token, setToken) GETSET(QString, toString, SETTINGS_PRIVATEKEY, hasPrivateKey, privateKey, setPrivateKey) GETSET(QString, toString, SETTINGS_PUBLICKEY, hasPublicKey, publicKey, setPublicKey) GETSET(QByteArray, toByteArray, SETTINGS_SERVERS, hasServers, servers, setServers) GETSET(QString, toString, SETTINGS_USER_AVATAR, hasUserAvatar, userAvatar, setUserAvatar) GETSET(QString, toString, SETTINGS_USER_DISPLAYNAME, hasUserDisplayName, userDisplayName, setUserDisplayName) GETSET(QString, toString, SETTINGS_USER_EMAIL, hasUserEmail, userEmail, setUserEmail) GETSET(int, toInt, SETTINGS_USER_MAXDEVICES, hasUserMaxDevices, userMaxDevices, setUserMaxDevices) GETSET(bool, toBool, SETTINGS_USER_SUBSCRIPTIONNEEDED, hasUserSubscriptionNeeded, userSubscriptionNeeded, setUserSubscriptionNeeded) GETSET(QString, toString, SETTINGS_CURRENTSERVER_COUNTRYCODE, hasCurrentServerCountryCode, currentServerCountryCode, setCurrentServerCountryCode) GETSET(QString, toString, SETTINGS_CURRENTSERVER_COUNTRY, hasCurrentServerCountry, currentServerCountry, setCurrentServerCountry) GETSET(QString, toString, SETTINGS_CURRENTSERVER_CITY, hasCurrentServerCity, currentServerCity, setCurrentServerCity) GETSET(QByteArray, toByteArray, SETTINGS_DEVICES, hasDevices, devices, setDevices) GETSET(QStringList, toStringList, SETTINGS_IAPPRODUCTS, hasIapProducts, iapProducts, setIapProducts) GETSET(QStringList, toStringList, SETTINGS_CAPTIVEPORTALIPV4ADDRESSES, hasCaptivePortalIpv4Addresses, captivePortalIpv4Addresses, setCaptivePortalIpv4Addresses) GETSET(QStringList, toStringList, SETTINGS_CAPTIVEPORTALIPV6ADDRESSES, hasCaptivePortalIpv6Addresses, captivePortalIpv6Addresses, setCaptivePortalIpv6Addresses) GETSET(bool, toBool, SETTINGS_POSTAUTHENTICATIONSHOWN, hasPostAuthenticationShown, postAuthenticationShown, setPostAuthenticationShown); GETSET(QString, toString, SETTINGS_LANGUAGECODE, hasLanguageCode, languageCode, setLanguageCode); GETSET(QString, toString, SETTINGS_PREVIOUSLANGUAGECODE, hasPreviousLanguageCode, previousLanguageCode, setPreviousLanguageCode); GETSET(bool, toBool, SETTINGS_SYSTEMLANGUAGECODEMIGRATED, hasSystemLanguageCodeMigrated, systemLanguageCodeMigrated, setSystemLanguageCodeMigrated); #ifdef MVPN_ANDROID GETSET(bool, toBool, SETTINGS_NATIVEANDROIDSDATAMIGRATED, hasNativeAndroidDataMigrated, nativeAndroidDataMigrated, setNativeAndroidDataMigrated); #endif #ifdef MVPN_IOS GETSET(bool, toBool, SETTINGS_NATIVEIOSDATAMIGRATED, hasNativeIOSDataMigrated, nativeIOSDataMigrated, setNativeIOSDataMigrated) GETSET(QStringList, toStringList, SETTINGS_SUBSCRIPTIONTRANSACTIONS, hasSubscriptionTransactions, subscriptionTransactions, setSubscriptionTransactions) bool SettingsHolder::hasSubscriptionTransaction( const QString& transactionId) const { return hasSubscriptionTransactions() && subscriptionTransactions().contains(transactionId); } void SettingsHolder::addSubscriptionTransactions( const QStringList& transactionIds) { QStringList transactions; if (hasSubscriptionTransactions()) { transactions = subscriptionTransactions(); } transactions.append(transactionIds); setSubscriptionTransactions(transactions); } #endif #ifdef MVPN_WINDOWS GETSET(bool, toBool, SETTINGS_NATIVEWINDOWSDATAMIGRATED, hasNativeWindowsDataMigrated, nativeWindowsDataMigrated, setNativeWindowsDataMigrated) #endif #undef GETSET bool SettingsHolder::hasVpnDisabledApp(const QString& appID) { QStringList applist; if (hasVpnDisabledApps()) { applist = vpnDisabledApps(); } return applist.contains(appID); } void SettingsHolder::removeVpnDisabledApp(const QString& appID) { QStringList applist; if (hasVpnDisabledApps()) { applist = vpnDisabledApps(); } applist.removeAll(appID); setVpnDisabledApps(applist); } void SettingsHolder::addVpnDisabledApp(const QString& appID) { QStringList applist; if (hasVpnDisabledApps()) { applist = vpnDisabledApps(); } applist.append(appID); setVpnDisabledApps(applist); } mozilla-vpn-client-2.2.0/src/settingsholder.h000066400000000000000000000114371404202232700212370ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef SETTINGSHOLDER_H #define SETTINGSHOLDER_H #include #include class SettingsHolder final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(SettingsHolder) Q_PROPERTY(bool ipv6Enabled READ ipv6Enabled WRITE setIpv6Enabled NOTIFY ipv6EnabledChanged) Q_PROPERTY(bool localNetworkAccess READ localNetworkAccess WRITE setLocalNetworkAccess NOTIFY localNetworkAccessChanged) Q_PROPERTY(bool unsecuredNetworkAlert READ unsecuredNetworkAlert WRITE setUnsecuredNetworkAlert NOTIFY unsecuredNetworkAlertChanged) Q_PROPERTY(bool captivePortalAlert READ captivePortalAlert WRITE setCaptivePortalAlert NOTIFY captivePortalAlertChanged) Q_PROPERTY(bool startAtBoot READ startAtBoot WRITE setStartAtBoot NOTIFY startAtBootChanged) Q_PROPERTY(bool protectSelectedApps READ protectSelectedApps WRITE setProtectSelectedApps NOTIFY protectSelectedAppsChanged) public: SettingsHolder(); ~SettingsHolder(); static SettingsHolder* instance(); void clear(); #define GETSET(type, has, get, set) \ bool has() const; \ type get() const; \ void set(const type& value); GETSET(bool, hasIpv6Enabled, ipv6Enabled, setIpv6Enabled) GETSET(bool, hasLocalNetworkAccess, localNetworkAccess, setLocalNetworkAccess) GETSET(bool, hasUnsecuredNetworkAlert, unsecuredNetworkAlert, setUnsecuredNetworkAlert) GETSET(bool, hasCaptivePortalAlert, captivePortalAlert, setCaptivePortalAlert) GETSET(bool, hasStartAtBoot, startAtBoot, setStartAtBoot) GETSET(QString, hasLanguageCode, languageCode, setLanguageCode) GETSET(QString, hasPreviousLanguageCode, previousLanguageCode, setPreviousLanguageCode) GETSET(bool, hasSystemLanguageCodeMigrated, systemLanguageCodeMigrated, setSystemLanguageCodeMigrated) GETSET(QString, hasToken, token, setToken) GETSET(QString, hasPrivateKey, privateKey, setPrivateKey) GETSET(QString, hasPublicKey, publicKey, setPublicKey) GETSET(QByteArray, hasServers, servers, setServers) GETSET(QString, hasUserAvatar, userAvatar, setUserAvatar) GETSET(QString, hasUserDisplayName, userDisplayName, setUserDisplayName) GETSET(QString, hasUserEmail, userEmail, setUserEmail) GETSET(int, hasUserMaxDevices, userMaxDevices, setUserMaxDevices) GETSET(bool, hasUserSubscriptionNeeded, userSubscriptionNeeded, setUserSubscriptionNeeded) GETSET(QString, hasCurrentServerCountryCode, currentServerCountryCode, setCurrentServerCountryCode) GETSET(QString, hasCurrentServerCountry, currentServerCountry, setCurrentServerCountry) GETSET(QString, hasCurrentServerCity, currentServerCity, setCurrentServerCity) GETSET(QByteArray, hasDevices, devices, setDevices) GETSET(QStringList, hasIapProducts, iapProducts, setIapProducts) GETSET(QStringList, hasCaptivePortalIpv4Addresses, captivePortalIpv4Addresses, setCaptivePortalIpv4Addresses) GETSET(QStringList, hasCaptivePortalIpv6Addresses, captivePortalIpv6Addresses, setCaptivePortalIpv6Addresses) GETSET(bool, hasPostAuthenticationShown, postAuthenticationShown, setPostAuthenticationShown); GETSET(bool, hasProtectSelectedApps, protectSelectedApps, setProtectSelectedApps) GETSET(QStringList, hasVpnDisabledApps, vpnDisabledApps, setVpnDisabledApps) bool hasVpnDisabledApp(const QString& appID); void removeVpnDisabledApp(const QString& appID); void addVpnDisabledApp(const QString& appID); #ifdef MVPN_IOS GETSET(bool, hasNativeIOSDataMigrated, nativeIOSDataMigrated, setNativeIOSDataMigrated) GETSET(QStringList, hasSubscriptionTransactions, subscriptionTransactions, setSubscriptionTransactions); bool hasSubscriptionTransaction(const QString& transactionId) const; void addSubscriptionTransactions(const QStringList& transactionIds); #endif #ifdef MVPN_WINDOWS GETSET(bool, hasNativeWindowsDataMigrated, nativeWindowsDataMigrated, setNativeWindowsDataMigrated) #endif #ifdef MVPN_ANDROID GETSET(bool, hasNativeAndroidDataMigrated, nativeAndroidDataMigrated, setNativeAndroidDataMigrated) #endif #undef GETSET signals: void ipv6EnabledChanged(bool value); void localNetworkAccessChanged(bool value); void unsecuredNetworkAlertChanged(bool value); void captivePortalAlertChanged(bool value); void startAtBootChanged(bool value); void protectSelectedAppsChanged(bool value); void vpnDisabledAppsChanged(const QStringList& apps); private: explicit SettingsHolder(QObject* parent); private: QSettings m_settings; }; #endif // SETTINGSHOLDER_H mozilla-vpn-client-2.2.0/src/signalhandler.cpp000066400000000000000000000016341404202232700213450ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "signalhandler.h" #include "logger.h" #include "signal.h" namespace { Logger logger(LOG_MAIN, "SignalHandler"); SignalHandler* self = nullptr; } // namespace SignalHandler::SignalHandler() { Q_ASSERT(!self); self = this; int quitSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP}; sigset_t mask; sigemptyset(&mask); for (auto sig : quitSignals) { sigaddset(&mask, sig); } struct sigaction sa; sa.sa_handler = SignalHandler::saHandler; sa.sa_mask = mask; sa.sa_flags = 0; for (auto sig : quitSignals) { sigaction(sig, &sa, nullptr); } } void SignalHandler::saHandler(int signal) { logger.log() << "Signal" << signal; Q_ASSERT(self); emit self->quitRequested(); } mozilla-vpn-client-2.2.0/src/signalhandler.h000066400000000000000000000007321404202232700210100ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef SIGNALHANDLER_H #define SIGNALHANDLER_H #include class SignalHandler final : public QObject { Q_OBJECT public: SignalHandler(); private: static void saHandler(int signal); signals: void quitRequested(); }; #endif // SIGNALHANDLER_H mozilla-vpn-client-2.2.0/src/simplenetworkmanager.cpp000066400000000000000000000012371404202232700227670ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "simplenetworkmanager.h" #include "leakdetector.h" #include SimpleNetworkManager::SimpleNetworkManager() { MVPN_COUNT_CTOR(SimpleNetworkManager); } SimpleNetworkManager::~SimpleNetworkManager() { MVPN_COUNT_DTOR(SimpleNetworkManager); } QNetworkAccessManager* SimpleNetworkManager::networkAccessManager() { if (!m_networkManager) { m_networkManager = new QNetworkAccessManager(this); } return m_networkManager; } mozilla-vpn-client-2.2.0/src/simplenetworkmanager.h000066400000000000000000000012051404202232700224270ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef SIMPLENETWORKMANAGER_H #define SIMPLENETWORKMANAGER_H #include "networkmanager.h" #include class SimpleNetworkManager final : public NetworkManager { Q_DISABLE_COPY_MOVE(SimpleNetworkManager) public: SimpleNetworkManager(); ~SimpleNetworkManager(); QNetworkAccessManager* networkAccessManager() override; private: QNetworkAccessManager* m_networkManager = nullptr; }; #endif // SIMPLENETWORKMANAGER_H mozilla-vpn-client-2.2.0/src/src.pro000066400000000000000000000601471404202232700173430ustar00rootroot00000000000000# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. include($$PWD/../version.pri) DEFINES += APP_VERSION=\\\"$$VERSION\\\" DEFINES += BUILD_ID=\\\"$$BUILD_ID\\\" QT += network QT += quick QT += widgets QT += charts CONFIG += c++1z TEMPLATE = app DEFINES += QT_DEPRECATED_WARNINGS INCLUDEPATH += \ hacl-star \ hacl-star/kremlin \ hacl-star/kremlin/minimal DEPENDPATH += $${INCLUDEPATH} OBJECTS_DIR = .obj MOC_DIR = .moc RCC_DIR = .rcc UI_DIR = .ui SOURCES += \ apppermission.cpp \ authenticationlistener.cpp \ captiveportal/captiveportal.cpp \ captiveportal/captiveportaldetection.cpp \ captiveportal/captiveportaldetectionimpl.cpp \ captiveportal/captiveportalmonitor.cpp \ captiveportal/captiveportalnotifier.cpp \ captiveportal/captiveportalrequest.cpp \ closeeventhandler.cpp \ command.cpp \ commandlineparser.cpp \ commands/commandactivate.cpp \ commands/commanddeactivate.cpp \ commands/commanddevice.cpp \ commands/commandlogin.cpp \ commands/commandlogout.cpp \ commands/commandselect.cpp \ commands/commandservers.cpp \ commands/commandstatus.cpp \ commands/commandui.cpp \ connectioncheck.cpp \ connectiondataholder.cpp \ connectionhealth.cpp \ controller.cpp \ cryptosettings.cpp \ curve25519.cpp \ errorhandler.cpp \ featurelist.cpp \ fontloader.cpp \ hacl-star/Hacl_Chacha20.c \ hacl-star/Hacl_Chacha20Poly1305_32.c \ hacl-star/Hacl_Curve25519_51.c \ hacl-star/Hacl_Poly1305_32.c \ ipaddress.cpp \ ipaddressrange.cpp \ leakdetector.cpp \ localizer.cpp \ logger.cpp \ loghandler.cpp \ logoutobserver.cpp \ main.cpp \ models/helpmodel.cpp \ models/user.cpp \ models/device.cpp \ models/devicemodel.cpp \ models/keys.cpp \ models/server.cpp \ models/servercity.cpp \ models/servercountry.cpp \ models/servercountrymodel.cpp \ models/serverdata.cpp \ mozillavpn.cpp \ networkmanager.cpp \ networkrequest.cpp \ networkwatcher.cpp \ notificationhandler.cpp \ pinghelper.cpp \ pingsender.cpp \ platforms/dummy/dummyapplistprovider.cpp \ platforms/dummy/dummynetworkwatcher.cpp \ qmlengineholder.cpp \ releasemonitor.cpp \ rfc1918.cpp \ rfc4193.cpp \ serveri18n.cpp \ settingsholder.cpp \ simplenetworkmanager.cpp \ statusicon.cpp \ systemtrayhandler.cpp \ tasks/accountandservers/taskaccountandservers.cpp \ tasks/adddevice/taskadddevice.cpp \ tasks/authenticate/taskauthenticate.cpp \ tasks/captiveportallookup/taskcaptiveportallookup.cpp \ tasks/controlleraction/taskcontrolleraction.cpp \ tasks/function/taskfunction.cpp \ tasks/heartbeat/taskheartbeat.cpp \ tasks/removedevice/taskremovedevice.cpp \ timercontroller.cpp \ timersingleshot.cpp \ update/updater.cpp \ update/versionapi.cpp \ urlopener.cpp HEADERS += \ apppermission.h \ applistprovider.h \ authenticationlistener.h \ captiveportal/captiveportal.h \ captiveportal/captiveportaldetection.h \ captiveportal/captiveportaldetectionimpl.h \ captiveportal/captiveportalmonitor.h \ captiveportal/captiveportalnotifier.h \ captiveportal/captiveportalrequest.h \ closeeventhandler.h \ command.h \ commandlineparser.h \ commands/commandactivate.h \ commands/commanddeactivate.h \ commands/commanddevice.h \ commands/commandlogin.h \ commands/commandlogout.h \ commands/commandselect.h \ commands/commandservers.h \ commands/commandstatus.h \ commands/commandui.h \ connectioncheck.h \ connectiondataholder.h \ connectionhealth.h \ constants.h \ controller.h \ controllerimpl.h \ cryptosettings.h \ curve25519.h \ errorhandler.h \ featurelist.h \ fontloader.h \ ipaddress.h \ ipaddressrange.h \ leakdetector.h \ localizer.h \ logger.h \ loghandler.h \ logoutobserver.h \ models/device.h \ models/devicemodel.h \ models/helpmodel.h \ models/keys.h \ models/server.h \ models/servercity.h \ models/servercountry.h \ models/servercountrymodel.h \ models/serverdata.h \ models/user.h \ mozillavpn.h \ networkmanager.h \ networkrequest.h \ networkwatcher.h \ networkwatcherimpl.h \ notificationhandler.h \ pinghelper.h \ pingsender.h \ pingsendworker.h \ platforms/dummy/dummyapplistprovider.h \ platforms/dummy/dummynetworkwatcher.h \ qmlengineholder.h \ releasemonitor.h \ rfc1918.h \ rfc4193.h \ serveri18n.h \ settingsholder.h \ simplenetworkmanager.h \ statusicon.h \ systemtrayhandler.h \ task.h \ tasks/accountandservers/taskaccountandservers.h \ tasks/adddevice/taskadddevice.h \ tasks/authenticate/taskauthenticate.h \ tasks/captiveportallookup/taskcaptiveportallookup.h \ tasks/controlleraction/taskcontrolleraction.h \ tasks/function/taskfunction.h \ tasks/heartbeat/taskheartbeat.h \ tasks/removedevice/taskremovedevice.h \ timercontroller.h \ timersingleshot.h \ update/updater.h \ update/versionapi.h \ urlopener.h inspector { message(Enabling the inspector) QT+= websockets QT+= testlib QT.testlib.CONFIG -= console CONFIG += no_testcase_installs RESOURCES += inspector/inspector.qrc DEFINES += MVPN_INSPECTOR SOURCES += \ inspector/inspectorhttpconnection.cpp \ inspector/inspectorhttpserver.cpp \ inspector/inspectorwebsocketconnection.cpp \ inspector/inspectorwebsocketserver.cpp HEADERS += \ inspector/inspectorhttpconnection.h \ inspector/inspectorhttpserver.h \ inspector/inspectorwebsocketconnection.h \ inspector/inspectorwebsocketserver.h } # Signal handling for unix platforms unix { SOURCES += signalhandler.cpp HEADERS += signalhandler.h } RESOURCES += qml.qrc QML_IMPORT_PATH = QML_DESIGNER_IMPORT_PATH = production { message(Production build) DEFINES += MVPN_PRODUCTION_MODE RESOURCES += logo_prod.qrc } else { message(Staging build) RESOURCES += logo_beta.qrc } balrog { message(Balrog enabled) DEFINES += MVPN_BALROG SOURCES += update/balrog.cpp HEADERS += update/balrog.h } DUMMY { message(Dummy build) win* { CONFIG += embed_manifest_exe QT += svg } else { QMAKE_CXXFLAGS *= -Werror } macos { TARGET = MozillaVPN } else { TARGET = mozillavpn } QT += networkauth DEFINES += MVPN_DUMMY SOURCES += \ platforms/dummy/dummycontroller.cpp \ platforms/dummy/dummycryptosettings.cpp \ platforms/dummy/dummypingsendworker.cpp \ systemtraynotificationhandler.cpp \ tasks/authenticate/desktopauthenticationlistener.cpp HEADERS += \ platforms/dummy/dummycontroller.h \ platforms/dummy/dummypingsendworker.h \ systemtraynotificationhandler.h \ tasks/authenticate/desktopauthenticationlistener.h } # Platform-specific: Linux else:linux:!android { message(Linux build) QMAKE_CXXFLAGS *= -Werror TARGET = mozillavpn QT += networkauth QT += dbus DEFINES += MVPN_LINUX DEFINES += PROTOCOL_VERSION=\\\"$$DBUS_PROTOCOL_VERSION\\\" SOURCES += \ eventlistener.cpp \ platforms/linux/backendlogsobserver.cpp \ platforms/linux/dbusclient.cpp \ platforms/linux/linuxcontroller.cpp \ platforms/linux/linuxcryptosettings.cpp \ platforms/linux/linuxdependencies.cpp \ platforms/linux/linuxnetworkwatcher.cpp \ platforms/linux/linuxpingsendworker.cpp \ platforms/linux/linuxsystemtrayhandler.cpp \ systemtraynotificationhandler.cpp \ tasks/authenticate/desktopauthenticationlistener.cpp HEADERS += \ eventlistener.h \ platforms/linux/backendlogsobserver.h \ platforms/linux/dbusclient.h \ platforms/linux/linuxcontroller.h \ platforms/linux/linuxdependencies.h \ platforms/linux/linuxnetworkwatcher.h \ platforms/linux/linuxpingsendworker.h \ platforms/linux/linuxsystemtrayhandler.h \ systemtraynotificationhandler.h \ tasks/authenticate/desktopauthenticationlistener.h # The daemon source code: SOURCES += \ ../3rdparty/wireguard-tools/contrib/embeddable-wg-library/wireguard.c \ daemon/daemon.cpp \ platforms/linux/daemon/dbusservice.cpp \ platforms/linux/daemon/linuxdaemon.cpp \ platforms/linux/daemon/polkithelper.cpp \ platforms/linux/daemon/wireguardutilslinux.cpp \ wgquickprocess.cpp HEADERS += \ ../3rdparty/wireguard-tools/contrib/embeddable-wg-library/wireguard.h \ daemon/interfaceconfig.h \ daemon/daemon.h \ daemon/wireguardutils.h \ platforms/linux/daemon/dbusservice.h \ platforms/linux/daemon/polkithelper.h \ platforms/linux/daemon/wireguardutilslinux.h \ wgquickprocess.h isEmpty(USRPATH) { USRPATH=/usr } isEmpty(ETCPATH) { ETCPATH=/etc } DBUS_ADAPTORS += platforms/linux/daemon/org.mozilla.vpn.dbus.xml DBUS_INTERFACES = platforms/linux/daemon/org.mozilla.vpn.dbus.xml target.path = $${USRPATH}/bin INSTALLS += target desktopFile.path = $${USRPATH}/share/applications desktopFile.files = ../linux/extra/MozillaVPN.desktop INSTALLS += desktopFile autostartFile.path = $${ETCPATH}/xdg/autostart autostartFile.files = ../linux/extra/MozillaVPN-startup.desktop INSTALLS += autostartFile icon16x16.path = $${USRPATH}/share/icons/hicolor/16x16/apps icon16x16.files = ../linux/extra/icons/16x16/mozillavpn.png INSTALLS += icon16x16 icon32x32.path = $${USRPATH}/share/icons/hicolor/32x32/apps icon32x32.files = ../linux/extra/icons/32x32/mozillavpn.png INSTALLS += icon32x32 icon48x48.path = $${USRPATH}/share/icons/hicolor/48x48/apps icon48x48.files = ../linux/extra/icons/48x48/mozillavpn.png INSTALLS += icon48x48 DEFINES += MVPN_ICON_PATH=\\\"$${USRPATH}/share/icons/hicolor/64x64/apps/mozillavpn.png\\\" icon64x64.path = $${USRPATH}/share/icons/hicolor/64x64/apps icon64x64.files = ../linux/extra/icons/64x64/mozillavpn.png INSTALLS += icon64x64 icon128x128.path = $${USRPATH}/share/icons/hicolor/128x128/apps icon128x128.files = ../linux/extra/icons/128x128/mozillavpn.png INSTALLS += icon128x128 polkit_actions.files = platforms/linux/daemon/org.mozilla.vpn.policy polkit_actions.path = $${USRPATH}/share/polkit-1/actions INSTALLS += polkit_actions dbus_conf.files = platforms/linux/daemon/org.mozilla.vpn.conf dbus_conf.path = $${USRPATH}/share/dbus-1/system.d/ INSTALLS += dbus_conf dbus_service.files = platforms/linux/daemon/org.mozilla.vpn.dbus.service dbus_service.path = $${USRPATH}/share/dbus-1/system-services INSTALLS += dbus_service DEFINES += MVPN_DATA_PATH=\\\"$${USRPATH}/share/mozillavpn\\\" helper.path = $${USRPATH}/share/mozillavpn helper.files = ../linux/daemon/helper.sh INSTALLS += helper CONFIG += link_pkgconfig PKGCONFIG += polkit-gobject-1 } # Platform-specific: android else:android { message(Android build) QMAKE_CXXFLAGS *= -Werror # Android Deploy-to-Qt strips the info anyway # but we want to create an extra bundle with the info :) CONFIG += force_debug_info TARGET = mozillavpn QT += networkauth QT += svg QT += androidextras QT += qml QT += xml LIBS += \-ljnigraphics\ DEFINES += MVPN_ANDROID ANDROID_ABIS = x86 x86_64 armeabi-v7a arm64-v8a INCLUDEPATH += platforms/android SOURCES += platforms/android/androidauthenticationlistener.cpp \ platforms/android/androidcontroller.cpp \ platforms/android/androidnotificationhandler.cpp \ platforms/android/androidutils.cpp \ platforms/android/androidwebview.cpp \ platforms/android/androidstartatbootwatcher.cpp \ platforms/android/androiddatamigration.cpp \ platforms/android/androidappimageprovider.cpp \ platforms/android/androidapplistprovider.cpp \ platforms/android/androidsharedprefs.cpp \ tasks/authenticate/desktopauthenticationlistener.cpp HEADERS += platforms/android/androidauthenticationlistener.h \ platforms/android/androidcontroller.h \ platforms/android/androidnotificationhandler.h \ platforms/android/androidutils.h \ platforms/android/androidwebview.h \ platforms/android/androidstartatbootwatcher.h\ platforms/android/androiddatamigration.h\ platforms/android/androidappimageprovider.h \ platforms/android/androidapplistprovider.h \ platforms/android/androidsharedprefs.h \ tasks/authenticate/desktopauthenticationlistener.h # Usable Linux Imports SOURCES += platforms/linux/linuxpingsendworker.cpp \ platforms/linux/linuxcryptosettings.cpp HEADERS += platforms/linux/linuxpingsendworker.h # We need to compile our own openssl :/ exists(../3rdparty/openSSL/openssl.pri) { include(../3rdparty/openSSL/openssl.pri) } else{ message(Have you imported the 3rd-party git submodules? Read the README.md) error(Did not found openSSL in 3rdparty/openSSL - Exiting Android Build ) } # For the android build we need to unset those # Otherwise the packaging will fail 🙅 OBJECTS_DIR = MOC_DIR = RCC_DIR = UI_DIR = DISTFILES += \ ../android/AndroidManifest.xml \ ../android/build.gradle \ ../android/gradle/wrapper/gradle-wrapper.jar \ ../android/gradle/wrapper/gradle-wrapper.properties \ ../android/gradlew \ ../android/gradlew.bat \ ../android/res/values/libs.xml ANDROID_PACKAGE_SOURCE_DIR = $$PWD/../android } # Platform-specific: MacOS else:macos { message(MacOSX build) QMAKE_CXXFLAGS *= -Werror TARGET = MozillaVPN QMAKE_TARGET_BUNDLE_PREFIX = org.mozilla.macos QT += networkauth # For the loginitem LIBS += -framework ServiceManagement LIBS += -framework Security LIBS += -framework CoreWLAN DEFINES += MVPN_MACOS SOURCES += \ platforms/macos/macosmenubar.cpp \ platforms/macos/macospingsendworker.cpp \ platforms/macos/macosstartatbootwatcher.cpp \ systemtraynotificationhandler.cpp \ tasks/authenticate/desktopauthenticationlistener.cpp OBJECTIVE_SOURCES += \ platforms/macos/macoscryptosettings.mm \ platforms/macos/macosnetworkwatcher.mm \ platforms/macos/macosutils.mm HEADERS += \ platforms/macos/macosmenubar.h \ platforms/macos/macospingsendworker.h \ platforms/macos/macosstartatbootwatcher.h \ systemtraynotificationhandler.h \ tasks/authenticate/desktopauthenticationlistener.h OBJECTIVE_HEADERS += \ platforms/macos/macosnetworkwatcher.h \ platforms/macos/macosutils.h isEmpty(MVPN_MACOS) { message(No integration required for this build - let\'s use the dummy controller) SOURCES += platforms/dummy/dummycontroller.cpp HEADERS += platforms/dummy/dummycontroller.h } else:networkextension { message(Network extension mode) DEFINES += MVPN_MACOS_NETWORKEXTENSION INCLUDEPATH += \ ../3rdparty/Wireguard-apple/WireGuard/WireGuard/Crypto \ ../3rdparty/wireguard-apple/WireGuard/Shared/Model \ OBJECTIVE_SOURCES += \ platforms/ios/ioscontroller.mm \ platforms/ios/iosglue.mm OBJECTIVE_HEADERS += \ platforms/ios/iosscontroller.h } else { message(Daemon mode) DEFINES += MVPN_MACOS_DAEMON SOURCES += \ daemon/daemon.cpp \ daemon/daemonlocalserver.cpp \ daemon/daemonlocalserverconnection.cpp \ localsocketcontroller.cpp \ wgquickprocess.cpp \ platforms/macos/daemon/macosdaemon.cpp \ platforms/macos/daemon/macosdaemonserver.cpp HEADERS += \ daemon/interfaceconfig.h \ daemon/daemon.h \ daemon/daemonlocalserver.h \ daemon/daemonlocalserverconnection.h \ daemon/wireguardutils.h \ localsocketcontroller.h \ wgquickprocess.h \ platforms/macos/daemon/macosdaemon.h \ platforms/macos/daemon/macosdaemonserver.h } QMAKE_MACOSX_DEPLOYMENT_TARGET = 10.14 QMAKE_INFO_PLIST=../macos/app/Info.plist QMAKE_ASSET_CATALOGS_APP_ICON = "AppIcon" production { QMAKE_ASSET_CATALOGS = $$PWD/../macos/app/Images.xcassets } else { QMAKE_ASSET_CATALOGS = $$PWD/../macos/app/Images-beta.xcassets } } # Platform-specific: IOS else:ios { message(IOS build) TARGET = MozillaVPN QMAKE_TARGET_BUNDLE_PREFIX = org.mozilla.ios QT += svg QT += gui-private # For the authentication LIBS += -framework AuthenticationServices # For notifications LIBS += -framework UIKit LIBS += -framework Foundation LIBS += -framework StoreKit LIBS += -framework UserNotifications DEFINES += MVPN_IOS SOURCES += \ platforms/ios/taskiosproducts.cpp \ platforms/macos/macospingsendworker.cpp OBJECTIVE_SOURCES += \ platforms/ios/iaphandler.mm \ platforms/ios/iosauthenticationlistener.mm \ platforms/ios/ioscontroller.mm \ platforms/ios/iosdatamigration.mm \ platforms/ios/iosglue.mm \ platforms/ios/iosnotificationhandler.mm \ platforms/ios/iosutils.mm \ platforms/macos/macoscryptosettings.mm HEADERS += \ platforms/ios/taskiosproducts.h \ platforms/macos/macospingsendworker.h OBJECTIVE_HEADERS += \ platforms/ios/iaphandler.h \ platforms/ios/iosauthenticationlistener.h \ platforms/ios/ioscontroller.h \ platforms/ios/iosdatamigration.h \ platforms/ios/iosnotificationhandler.h \ platforms/ios/iosutils.h QMAKE_INFO_PLIST= $$PWD/../ios/app/Info.plist QMAKE_ASSET_CATALOGS_APP_ICON = "AppIcon" production { QMAKE_ASSET_CATALOGS = $$PWD/../ios/app/Images.xcassets } else { QMAKE_ASSET_CATALOGS = $$PWD/../ios/app/Images-beta.xcassets } app_launch_screen.files = $$files($$PWD/../ios/app/MozillaVPNLaunchScreen.storyboard) QMAKE_BUNDLE_DATA += app_launch_screen ios_launch_screen_images.files = $$files($$PWD/../ios/app/launch.png) QMAKE_BUNDLE_DATA += ios_launch_screen_images } else:win* { message(Windows build) TARGET = MozillaVPN QT += networkauth QT += svg CONFIG += embed_manifest_exe DEFINES += MVPN_WINDOWS production { RC_ICONS = ui/resources/logo.ico } else { RC_ICONS = ui/resources/logo-beta.ico } SOURCES += \ daemon/daemon.cpp \ daemon/daemonlocalserver.cpp \ daemon/daemonlocalserverconnection.cpp \ eventlistener.cpp \ localsocketcontroller.cpp \ platforms/windows/daemon/windowsdaemon.cpp \ platforms/windows/daemon/windowsdaemonserver.cpp \ platforms/windows/daemon/windowsdaemontunnel.cpp \ platforms/windows/daemon/windowstunnelmonitor.cpp \ platforms/windows/windowscaptiveportaldetection.cpp \ platforms/windows/windowscaptiveportaldetectionthread.cpp \ platforms/windows/windowscommons.cpp \ platforms/windows/windowscryptosettings.cpp \ platforms/windows/windowsdatamigration.cpp \ platforms/windows/windowsnetworkwatcher.cpp \ platforms/windows/windowspingsendworker.cpp \ platforms/windows/windowsstartatbootwatcher.cpp \ tasks/authenticate/desktopauthenticationlistener.cpp \ systemtraynotificationhandler.cpp \ wgquickprocess.cpp HEADERS += \ daemon/interfaceconfig.h \ daemon/daemon.h \ daemon/daemonlocalserver.h \ daemon/daemonlocalserverconnection.h \ daemon/wireguardutils.h \ eventlistener.h \ localsocketcontroller.h \ platforms/windows/daemon/windowsdaemon.h \ platforms/windows/daemon/windowsdaemonserver.h \ platforms/windows/daemon/windowsdaemontunnel.h \ platforms/windows/daemon/windowstunnelmonitor.h \ platforms/windows/windowscaptiveportaldetection.h \ platforms/windows/windowscaptiveportaldetectionthread.h \ platforms/windows/windowscommons.h \ platforms/windows/windowsdatamigration.h \ platforms/windows/windowsnetworkwatcher.h \ platforms/windows/windowspingsendworker.h \ tasks/authenticate/desktopauthenticationlistener.h \ platforms/windows/windowsstartatbootwatcher.h \ systemtraynotificationhandler.h \ wgquickprocess.h } else:wasm { message(WASM \\o/) DEFINES += MVPN_DUMMY DEFINES += MVPN_WASM QMAKE_CXXFLAGS *= -Werror TARGET = mozillavpn QT += svg SOURCES += \ platforms/dummy/dummycontroller.cpp \ platforms/dummy/dummycryptosettings.cpp \ platforms/dummy/dummypingsendworker.cpp \ platforms/macos/macosmenubar.cpp \ platforms/wasm/wasmauthenticationlistener.cpp \ platforms/wasm/wasmnetworkrequest.cpp \ platforms/wasm/wasmnetworkwatcher.cpp \ platforms/wasm/wasmwindowcontroller.cpp \ systemtraynotificationhandler.cpp HEADERS += \ platforms/dummy/dummycontroller.h \ platforms/dummy/dummypingsendworker.h \ platforms/macos/macosmenubar.h \ platforms/wasm/wasmauthenticationlistener.h \ platforms/wasm/wasmnetworkwatcher.h \ platforms/wasm/wasmwindowcontroller.h \ systemtraynotificationhandler.h SOURCES -= networkrequest.cpp RESOURCES += platforms/wasm/networkrequests.qrc } # Anything else else { error(Unsupported platform) } RESOURCES += $$PWD/../translations/servers.qrc exists($$PWD/../translations/translations.pri) { include($$PWD/../translations/translations.pri) } else{ message(Languages were not imported - using fallback english) TRANSLATIONS += \ ../translations/mozillavpn_en.ts ts.commands += lupdate $$PWD -no-obsolete -ts $$PWD/../translations/mozillavpn_en.ts ts.CONFIG += no_check_exist ts.output = $$PWD/../translations/mozillavpn_en.ts ts.input = . QMAKE_EXTRA_TARGETS += ts PRE_TARGETDEPS += ts } QMAKE_LRELEASE_FLAGS += -idbased CONFIG += lrelease CONFIG += embed_translations equals(QMAKE_CXX, clang++):debug { message(Coverage enabled) QMAKE_CXXFLAGS += -fprofile-instr-generate -fcoverage-mapping QMAKE_LFLAGS += -fprofile-instr-generate -fcoverage-mapping } mozilla-vpn-client-2.2.0/src/statusicon.cpp000066400000000000000000000054101404202232700207220ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "statusicon.h" #include "constants.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include namespace { Logger logger(LOG_MAIN, "StatusIcon"); #if defined(MVPN_LINUX) || defined(MVPN_WINDOWS) constexpr const std::array ANIMATED_ICON_STEPS = { ":/ui/resources/logo-animated1.png", ":/ui/resources/logo-animated2.png", ":/ui/resources/logo-animated3.png", ":/ui/resources/logo-animated4.png"}; constexpr const char* ICON_ON = ":/ui/resources/logo-on.png"; constexpr const char* ICON_GENERIC = ":/ui/resources/logo-generic.png"; #else constexpr const std::array ANIMATED_ICON_STEPS = { ":/ui/resources/logo-animated1.svg", ":/ui/resources/logo-animated2.svg", ":/ui/resources/logo-animated3.svg", ":/ui/resources/logo-animated4.svg"}; constexpr const char* ICON_ON = ":/ui/resources/logo-on.svg"; constexpr const char* ICON_GENERIC = ":/ui/resources/logo-generic.svg"; #endif } // namespace StatusIcon::StatusIcon() : m_icon(ICON_GENERIC) { MVPN_COUNT_CTOR(StatusIcon); connect(&m_animatedIconTimer, &QTimer::timeout, this, &StatusIcon::animateIcon); } StatusIcon::~StatusIcon() { MVPN_COUNT_DTOR(StatusIcon); } void StatusIcon::activateAnimation() { m_animatedIconIndex = 0; m_animatedIconTimer.start(Constants::STATUSICON_ANIMATION_MSEC); animateIcon(); } void StatusIcon::animateIcon() { Q_ASSERT(m_animatedIconIndex < ANIMATED_ICON_STEPS.size()); setIcon(ANIMATED_ICON_STEPS[m_animatedIconIndex++]); if (m_animatedIconIndex == ANIMATED_ICON_STEPS.size()) { m_animatedIconIndex = 0; } } void StatusIcon::stateChanged() { logger.log() << "Show notification"; m_animatedIconTimer.stop(); MozillaVPN* vpn = MozillaVPN::instance(); // If we are in a non-main state, we don't need to show special icons. if (vpn->state() != MozillaVPN::StateMain) { setIcon(ICON_GENERIC); return; } switch (vpn->controller()->state()) { case Controller::StateOn: setIcon(ICON_ON); break; case Controller::StateOff: setIcon(ICON_GENERIC); break; case Controller::StateSwitching: [[fallthrough]]; case Controller::StateConnecting: [[fallthrough]]; case Controller::StateConfirming: [[fallthrough]]; case Controller::StateDisconnecting: activateAnimation(); break; default: setIcon(ICON_GENERIC); break; } } void StatusIcon::setIcon(const QString& icon) { m_icon = icon; emit iconChanged(icon); } QUrl StatusIcon::iconUrl() const { return QUrl(QString("qrc%1").arg(m_icon)); } mozilla-vpn-client-2.2.0/src/statusicon.h000066400000000000000000000016551404202232700203760ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef STATUSICON_H #define STATUSICON_H #include #include #include #include class StatusIcon final : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(StatusIcon) Q_PROPERTY(QUrl iconUrl READ iconUrl NOTIFY iconChanged) public: StatusIcon(); ~StatusIcon(); QUrl iconUrl() const; const QString& iconString() const { return m_icon; } signals: void iconChanged(const QString& icon); public slots: void stateChanged(); private slots: void animateIcon(); private: void activateAnimation(); void setIcon(const QString& icon); private: QString m_icon; // Animated icon. QTimer m_animatedIconTimer; uint8_t m_animatedIconIndex = 0; }; #endif // STATUSICON_H mozilla-vpn-client-2.2.0/src/systemtrayhandler.cpp000066400000000000000000000224561404202232700223210ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "systemtrayhandler.h" #include "constants.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "qmlengineholder.h" #include "statusicon.h" #ifdef MVPN_LINUX # include "platforms/linux/linuxsystemtrayhandler.h" #endif #ifdef MVPN_MACOS # include "platforms/macos/macosutils.h" #endif #include #include #include #include namespace { Logger logger(LOG_MAIN, "SystemTrayHandler"); SystemTrayHandler* s_instance = nullptr; } // namespace // static SystemTrayHandler* SystemTrayHandler::create(QObject* parent) { #if defined(MVPN_LINUX) if (LinuxSystemTrayHandler::requiredCustomImpl()) { return new LinuxSystemTrayHandler(parent); } #endif return new SystemTrayHandler(parent); } // static SystemTrayHandler* SystemTrayHandler::instance() { Q_ASSERT(s_instance); return s_instance; } SystemTrayHandler::SystemTrayHandler(QObject* parent) : QSystemTrayIcon(parent) { MVPN_COUNT_CTOR(SystemTrayHandler); Q_ASSERT(!s_instance); s_instance = this; MozillaVPN* vpn = MozillaVPN::instance(); setToolTip(qtTrId("vpn.main.productName")); // Status label m_statusLabel = m_menu.addAction(""); m_statusLabel->setEnabled(false); m_lastLocationLabel = m_menu.addAction("", vpn->controller(), &Controller::activate); m_lastLocationLabel->setEnabled(false); m_disconnectAction = m_menu.addAction("", vpn->controller(), &Controller::deactivate); m_separator = m_menu.addSeparator(); m_showHideLabel = m_menu.addAction("", this, &SystemTrayHandler::showHideWindow); m_menu.addSeparator(); m_helpMenu = m_menu.addMenu(""); m_preferencesAction = m_menu.addAction("", vpn, &MozillaVPN::requestSettings); m_menu.addSeparator(); m_quitAction = m_menu.addAction("", vpn->controller(), &Controller::quit); setContextMenu(&m_menu); updateIcon(MozillaVPN::instance()->statusIcon()->iconString()); connect(QmlEngineHolder::instance()->window(), &QWindow::visibleChanged, this, &SystemTrayHandler::updateContextMenu); connect(this, &QSystemTrayIcon::activated, this, &SystemTrayHandler::maybeActivated); connect(this, &QSystemTrayIcon::messageClicked, this, &SystemTrayHandler::messageClickHandle); retranslate(); } SystemTrayHandler::~SystemTrayHandler() { MVPN_COUNT_DTOR(SystemTrayHandler); Q_ASSERT(s_instance == this); s_instance = nullptr; } void SystemTrayHandler::updateContextMenu() { logger.log() << "Update context menu"; // If the QML Engine Holder has been released, we are shutting down. if (!QmlEngineHolder::exists()) { return; } MozillaVPN* vpn = MozillaVPN::instance(); bool isStateMain = vpn->state() == MozillaVPN::StateMain; m_preferencesAction->setVisible(isStateMain); m_disconnectAction->setVisible(isStateMain && vpn->controller()->state() == Controller::StateOn); m_statusLabel->setVisible(isStateMain); m_lastLocationLabel->setVisible(isStateMain); m_separator->setVisible(isStateMain); if (QmlEngineHolder::instance()->window()->isVisible()) { //% "Hide Mozilla VPN" m_showHideLabel->setText(qtTrId("systray.hide")); } else { //% "Show Mozilla VPN" m_showHideLabel->setText(qtTrId("systray.show")); } // If we are in a non-main state, we don't need to show notifications. if (!isStateMain) { return; } QString statusLabel; switch (vpn->controller()->state()) { case Controller::StateOn: //% "Connected to:" statusLabel = qtTrId("vpn.systray.status.connectedTo"); break; case Controller::StateOff: //% "Connect to the last location:" statusLabel = qtTrId("vpn.systray.status.connectTo"); break; case Controller::StateSwitching: [[fallthrough]]; case Controller::StateConnecting: [[fallthrough]]; case Controller::StateConfirming: //% "Connecting to:" statusLabel = qtTrId("vpn.systray.status.connectingTo"); break; case Controller::StateDisconnecting: //% "Disconnecting from:" statusLabel = qtTrId("vpn.systray.status.disconnectingFrom"); break; default: m_statusLabel->setVisible(false); m_lastLocationLabel->setVisible(false); m_separator->setVisible(false); return; } Q_ASSERT(!statusLabel.isEmpty()); m_statusLabel->setVisible(true); m_statusLabel->setText(statusLabel); m_lastLocationLabel->setVisible(true); QIcon flagIcon(QString(":/ui/resources/flags/%1.png") .arg(vpn->currentServer()->countryCode().toUpper())); m_lastLocationLabel->setIcon(flagIcon); m_lastLocationLabel->setText( //% "%1, %2" //: Location in the systray. %1 is the country, %2 is the city. qtTrId("vpn.systray.location") .arg(vpn->currentServer()->country()) .arg(vpn->currentServer()->city())); m_lastLocationLabel->setEnabled(vpn->controller()->state() == Controller::StateOff); } void SystemTrayHandler::unsecuredNetworkNotification( const QString& networkName) { logger.log() << "Unsecured network notification shown"; //% "Unsecured Wi-Fi network detected" QString title = qtTrId("vpn.systray.unsecuredNetwork.title"); //% "%1 is not secure. Click here to turn on VPN and secure your device." //: %1 is the Wi-Fi network name QString message = qtTrId("vpn.systray.unsecuredNetwork2.message").arg(networkName); showNotificationInternal(UnsecuredNetwork, title, message, Constants::UNSECURED_NETWORK_ALERT_MSEC); } void SystemTrayHandler::captivePortalBlockNotificationRequired() { logger.log() << "Captive portal block notification shown"; //% "Guest Wi-Fi portal blocked" QString title = qtTrId("vpn.systray.captivePortalBlock.title"); //% "The guest Wi-Fi network you’re connected to requires action. Click here" //% " to turn off VPN to see the portal." QString message = qtTrId("vpn.systray.captivePortalBlock2.message"); showNotificationInternal(CaptivePortalBlock, title, message, Constants::CAPTIVE_PORTAL_ALERT_MSEC); } void SystemTrayHandler::captivePortalUnblockNotificationRequired() { logger.log() << "Captive portal unblock notification shown"; //% "Guest Wi-Fi portal detected" QString title = qtTrId("vpn.systray.captivePortalUnblock.title"); //% "The guest Wi-Fi network you’re connected to may not be secure. Click" //% " here to turn on VPN to secure your device." QString message = qtTrId("vpn.systray.captivePortalUnblock2.message"); showNotificationInternal(CaptivePortalUnblock, title, message, Constants::CAPTIVE_PORTAL_ALERT_MSEC); } void SystemTrayHandler::updateIcon(const QString& icon) { QIcon trayIconMask(icon); trayIconMask.setIsMask(true); setIcon(trayIconMask); } void SystemTrayHandler::showHideWindow() { QmlEngineHolder* engine = QmlEngineHolder::instance(); if (engine->window()->isVisible()) { engine->hideWindow(); #ifdef MVPN_MACOS MacOSUtils::hideDockIcon(); #endif } else { engine->showWindow(); #ifdef MVPN_MACOS MacOSUtils::showDockIcon(); #endif } } void SystemTrayHandler::retranslate() { logger.log() << "Retranslate"; //% "Disconnect" m_disconnectAction->setText(qtTrId("systray.disconnect")); //% "Help" m_helpMenu->setTitle(qtTrId("systray.help")); for (QAction* action : m_helpMenu->actions()) { m_helpMenu->removeAction(action); } MozillaVPN* vpn = MozillaVPN::instance(); vpn->helpModel()->forEach([&](const char* nameId, int id) { m_helpMenu->addAction(qtTrId(nameId), [help = vpn->helpModel(), id]() { help->open(id); }); }); //% "Preferences…" m_preferencesAction->setText(qtTrId("systray.preferences")); //% "Quit Mozilla VPN" m_quitAction->setText(qtTrId("systray.quit")); updateContextMenu(); } void SystemTrayHandler::maybeActivated( QSystemTrayIcon::ActivationReason reason) { logger.log() << "Activated"; #if defined(MVPN_WINDOWS) || defined(MVPN_LINUX) if (reason == QSystemTrayIcon::DoubleClick || reason == QSystemTrayIcon::Trigger) { QmlEngineHolder* engine = QmlEngineHolder::instance(); engine->showWindow(); } #else Q_UNUSED(reason); #endif } void SystemTrayHandler::messageClickHandle() { logger.log() << "Message clicked"; if (m_lastMessage == None) { logger.log() << "Random message clicked received"; return; } emit notificationClicked(m_lastMessage); m_lastMessage = None; } void SystemTrayHandler::showNotification(const QString& title, const QString& message, int timerMsec) { showNotificationInternal(None, title, message, timerMsec); } void SystemTrayHandler::showNotificationInternal(Message type, const QString& title, const QString& message, int timerMsec) { m_lastMessage = type; emit notificationShown(title, message); QIcon icon(Constants::LOGO_URL); showMessage(title, message, icon, timerMsec); } mozilla-vpn-client-2.2.0/src/systemtrayhandler.h000066400000000000000000000036701404202232700217630ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef SYSTEMTRAYHANDLER_H #define SYSTEMTRAYHANDLER_H #include #include #include class MozillaVPN; class QAction; class SystemTrayHandler : public QSystemTrayIcon { Q_OBJECT Q_DISABLE_COPY_MOVE(SystemTrayHandler) public: enum Message { None, UnsecuredNetwork, CaptivePortalBlock, CaptivePortalUnblock, }; static SystemTrayHandler* create(QObject* parent); static SystemTrayHandler* instance(); virtual ~SystemTrayHandler(); void captivePortalBlockNotificationRequired(); void captivePortalUnblockNotificationRequired(); void unsecuredNetworkNotification(const QString& networkName); void showNotification(const QString& title, const QString& message, int timerMsec); void retranslate(); signals: void notificationShown(const QString& title, const QString& message); void notificationClicked(Message message); public slots: void updateIcon(const QString& icon); void updateContextMenu(); void messageClickHandle(); protected: explicit SystemTrayHandler(QObject* parent); virtual void showNotificationInternal(Message type, const QString& title, const QString& message, int timerMsec); protected: Message m_lastMessage = None; private: void showHideWindow(); void maybeActivated(QSystemTrayIcon::ActivationReason reason); private: QMenu m_menu; QAction* m_statusLabel = nullptr; QAction* m_lastLocationLabel = nullptr; QAction* m_disconnectAction = nullptr; QAction* m_separator = nullptr; QAction* m_preferencesAction = nullptr; QAction* m_showHideLabel = nullptr; QAction* m_quitAction = nullptr; QMenu* m_helpMenu = nullptr; }; #endif // SYSTEMTRAYHANDLER_H mozilla-vpn-client-2.2.0/src/systemtraynotificationhandler.cpp000066400000000000000000000016301404202232700247170ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "systemtraynotificationhandler.h" #include "leakdetector.h" #include "systemtrayhandler.h" SystemTrayNotificationHandler::SystemTrayNotificationHandler(QObject* parent) : NotificationHandler(parent) { MVPN_COUNT_CTOR(SystemTrayNotificationHandler); } SystemTrayNotificationHandler::~SystemTrayNotificationHandler() { MVPN_COUNT_DTOR(SystemTrayNotificationHandler); } void SystemTrayNotificationHandler::notify(const QString& title, const QString& message, int timerSec) { SystemTrayHandler::instance()->showNotification(title, message, timerSec * 1000); } mozilla-vpn-client-2.2.0/src/systemtraynotificationhandler.h000066400000000000000000000012071404202232700243640ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef SYSTEMTRAYNOTIFICATIONHANDLER_H #define SYSTEMTRAYNOTIFICATIONHANDLER_H #include "notificationhandler.h" class SystemTrayNotificationHandler final : public NotificationHandler { public: SystemTrayNotificationHandler(QObject* parent); ~SystemTrayNotificationHandler(); protected: void notify(const QString& title, const QString& message, int timerSec) override; }; #endif // SYSTEMTRAYNOTIFICATIONHANDLER_H mozilla-vpn-client-2.2.0/src/task.h000066400000000000000000000011111404202232700171270ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef TASK_H #define TASK_H #include class MozillaVPN; class Task : public QObject { Q_OBJECT public: explicit Task(const QString& name) : m_name(name) {} virtual ~Task() = default; const QString& name() const { return m_name; } virtual void run(MozillaVPN* vpn) = 0; signals: void completed(); private: QString m_name; }; #endif // TASK_H mozilla-vpn-client-2.2.0/src/tasks/000077500000000000000000000000001404202232700171475ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/tasks/accountandservers/000077500000000000000000000000001404202232700227005ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/tasks/accountandservers/taskaccountandservers.cpp000066400000000000000000000045521404202232700300260ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "taskaccountandservers.h" #include "errorhandler.h" #include "leakdetector.h" #include "logger.h" #include "models/servercountrymodel.h" #include "mozillavpn.h" #include "networkrequest.h" namespace { Logger logger(LOG_MAIN, "TaskAccountAndServers"); } TaskAccountAndServers::TaskAccountAndServers() : Task("TaskAccountAndServers") { MVPN_COUNT_CTOR(TaskAccountAndServers); } TaskAccountAndServers::~TaskAccountAndServers() { MVPN_COUNT_DTOR(TaskAccountAndServers); } void TaskAccountAndServers::run(MozillaVPN* vpn) { // Account fetch and servers fetch run in parallel. // Account fetch { NetworkRequest* request = NetworkRequest::createForAccount(this); connect(request, &NetworkRequest::requestFailed, [this, vpn](QNetworkReply::NetworkError error, const QByteArray&) { logger.log() << "Account request failed" << error; vpn->errorHandle(ErrorHandler::toErrorType(error)); m_accountCompleted = true; maybeCompleted(); }); connect(request, &NetworkRequest::requestCompleted, [this, vpn](const QByteArray& data) { logger.log() << "Account request completed"; vpn->accountChecked(data); m_accountCompleted = true; maybeCompleted(); }); } // Server list fetch { NetworkRequest* request = NetworkRequest::createForServers(this); connect(request, &NetworkRequest::requestFailed, [this, vpn](QNetworkReply::NetworkError error, const QByteArray&) { logger.log() << "Failed to retrieve servers"; vpn->errorHandle(ErrorHandler::toErrorType(error)); m_serversCompleted = true; maybeCompleted(); }); connect(request, &NetworkRequest::requestCompleted, [this, vpn](const QByteArray& data) { logger.log() << "Servers obtained"; vpn->serversFetched(data); m_serversCompleted = true; maybeCompleted(); }); } } void TaskAccountAndServers::maybeCompleted() { if (m_accountCompleted && m_serversCompleted) { emit completed(); } } mozilla-vpn-client-2.2.0/src/tasks/accountandservers/taskaccountandservers.h000066400000000000000000000012551404202232700274700ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef TASKACCOUNTANDSERVERS_H #define TASKACCOUNTANDSERVERS_H #include "task.h" #include #include class TaskAccountAndServers final : public Task { Q_DISABLE_COPY_MOVE(TaskAccountAndServers) public: TaskAccountAndServers(); ~TaskAccountAndServers(); void run(MozillaVPN* vpn) override; private: void maybeCompleted(); private: bool m_accountCompleted = false; bool m_serversCompleted = false; }; #endif // TASKACCOUNTANDSERVERS_H mozilla-vpn-client-2.2.0/src/tasks/adddevice/000077500000000000000000000000001404202232700210575ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/tasks/adddevice/taskadddevice.cpp000066400000000000000000000037501404202232700243630ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "taskadddevice.h" #include "curve25519.h" #include "errorhandler.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "networkrequest.h" #include namespace { Logger logger(LOG_MAIN, "TaskAddDevice"); QByteArray generatePrivateKey() { QByteArray key; QRandomGenerator* generator = QRandomGenerator::system(); Q_ASSERT(generator); for (uint8_t i = 0; i < CURVE25519_KEY_SIZE; ++i) { quint32 v = generator->generate(); key.append(v & 0xFF); } return key.toBase64(); } } // anonymous namespace TaskAddDevice::TaskAddDevice(const QString& deviceName) : Task("TaskAddDevice"), m_deviceName(deviceName) { MVPN_COUNT_CTOR(TaskAddDevice); } TaskAddDevice::~TaskAddDevice() { MVPN_COUNT_DTOR(TaskAddDevice); } void TaskAddDevice::run(MozillaVPN* vpn) { logger.log() << "Adding the device" << m_deviceName; QByteArray privateKey = generatePrivateKey(); QByteArray publicKey = Curve25519::generatePublicKey(privateKey); #ifdef QT_DEBUG logger.log() << "Private key: " << privateKey; logger.log() << "Public key: " << publicKey; #endif NetworkRequest* request = NetworkRequest::createForDeviceCreation(this, m_deviceName, publicKey); connect(request, &NetworkRequest::requestFailed, [this, vpn](QNetworkReply::NetworkError error, const QByteArray&) { logger.log() << "Failed to add the device" << error; vpn->errorHandle(ErrorHandler::toErrorType(error)); emit completed(); }); connect(request, &NetworkRequest::requestCompleted, [this, vpn, publicKey, privateKey](const QByteArray&) { logger.log() << "Device added"; vpn->deviceAdded(m_deviceName, publicKey, privateKey); emit completed(); }); } mozilla-vpn-client-2.2.0/src/tasks/adddevice/taskadddevice.h000066400000000000000000000010611404202232700240210ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef TASKADDDEVICE_H #define TASKADDDEVICE_H #include "task.h" #include class TaskAddDevice final : public Task { Q_DISABLE_COPY_MOVE(TaskAddDevice) public: explicit TaskAddDevice(const QString& deviceName); ~TaskAddDevice(); void run(MozillaVPN* vpn) override; private: QString m_deviceName; }; #endif // TASKADDDEVICE_H mozilla-vpn-client-2.2.0/src/tasks/authenticate/000077500000000000000000000000001404202232700216255ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/tasks/authenticate/desktopauthenticationlistener.cpp000066400000000000000000000050511404202232700305110ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "desktopauthenticationlistener.h" #include "leakdetector.h" #include "logger.h" #include "urlopener.h" #ifdef MVPN_INSPECTOR # include "inspector/inspectorwebsocketconnection.h" #endif #include #include #include #include namespace { Logger logger(LOG_MAIN, "DesktopAuthenticationListener"); int choosePort(QVector triedPorts) { logger.log() << "Choosing port"; while (true) { quint32 v = QRandomGenerator::global()->generate(); quint16 port = 1024 + (v % (std::numeric_limits::max() - 1024)); logger.log() << "Random port:" << port; if (!triedPorts.contains(port)) { triedPorts.append(port); return port; } logger.log() << "Already tried!"; } } } // anonymous namespace DesktopAuthenticationListener::DesktopAuthenticationListener(QObject* parent) : AuthenticationListener(parent) { MVPN_COUNT_CTOR(DesktopAuthenticationListener); m_server = new QOAuthHttpServerReplyHandler(QHostAddress::LocalHost, this); connect(m_server, &QAbstractOAuthReplyHandler::callbackReceived, [this](const QVariantMap& values) { logger.log() << "DesktopAuthenticationListener data received"; // Unknown connection. if (!values.contains("code")) { return; } QString code = values["code"].toString(); emit completed(code); }); } DesktopAuthenticationListener::~DesktopAuthenticationListener() { MVPN_COUNT_DTOR(DesktopAuthenticationListener); } void DesktopAuthenticationListener::start(MozillaVPN* vpn, QUrl& url, QUrlQuery& query) { logger.log() << "DesktopAuthenticationListener initialize"; Q_UNUSED(vpn); if (!m_server->isListening()) { QVector triedPorts; for (int i = 0; i < 50; ++i) { int port = choosePort(triedPorts); if (m_server->listen(QHostAddress::LocalHost, port)) { break; } } } if (!m_server->isListening()) { logger.log() << "Unable to listen for the authentication server."; emit failed(ErrorHandler::UnrecoverableError); return; } logger.log() << "Port:" << m_server->port(); query.addQueryItem("port", QString::number(m_server->port())); url.setQuery(query); UrlOpener::open(url.toString()); } mozilla-vpn-client-2.2.0/src/tasks/authenticate/desktopauthenticationlistener.h000066400000000000000000000014221404202232700301540ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef DESKTOPAUTHENTICATIONLISTENER_H #define DESKTOPAUTHENTICATIONLISTENER_H #include "authenticationlistener.h" class QOAuthHttpServerReplyHandler; class DesktopAuthenticationListener final : public AuthenticationListener { Q_OBJECT Q_DISABLE_COPY_MOVE(DesktopAuthenticationListener) public: explicit DesktopAuthenticationListener(QObject* parent); ~DesktopAuthenticationListener(); void start(MozillaVPN* vpn, QUrl& url, QUrlQuery& query) override; private: QOAuthHttpServerReplyHandler* m_server = nullptr; }; #endif // DESKTOPAUTHENTICATIONLISTENER_H mozilla-vpn-client-2.2.0/src/tasks/authenticate/taskauthenticate.cpp000066400000000000000000000113431404202232700256740ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "taskauthenticate.h" #include "authenticationlistener.h" #include "constants.h" #include "errorhandler.h" #include "leakdetector.h" #include "logger.h" #include "models/user.h" #include "mozillavpn.h" #include "networkrequest.h" #include #include #include #include #include #include #include namespace { Logger logger(LOG_MAIN, "TaskAuthenticate"); QByteArray generatePkceCodeVerifier() { QRandomGenerator* generator = QRandomGenerator::system(); Q_ASSERT(generator); QByteArray pkceCodeVerifier; static QByteArray range( "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-._~"); for (uint16_t i = 0; i < 128; ++i) { pkceCodeVerifier.append(range.at(generator->generate() % range.length())); } return pkceCodeVerifier; } } // anonymous namespace TaskAuthenticate::TaskAuthenticate() : Task("TaskAuthenticate") { MVPN_COUNT_CTOR(TaskAuthenticate); } TaskAuthenticate::~TaskAuthenticate() { MVPN_COUNT_DTOR(TaskAuthenticate); } void TaskAuthenticate::run(MozillaVPN* vpn) { Q_ASSERT(vpn); logger.log() << "TaskAuthenticate::Run"; Q_ASSERT(!m_authenticationListener); QByteArray pkceCodeVerifier = generatePkceCodeVerifier(); QByteArray pkceCodeChallenge = QCryptographicHash::hash(pkceCodeVerifier, QCryptographicHash::Sha256) .toBase64(); Q_ASSERT(pkceCodeChallenge.length() == 44); m_authenticationListener = AuthenticationListener::create(this); connect( m_authenticationListener, &AuthenticationListener::completed, [this, vpn, pkceCodeVerifier](const QString& pkceCodeSucces) { logger.log() << "Authentication completed with code:" << pkceCodeSucces; NetworkRequest* request = NetworkRequest::createForAuthenticationVerification( this, pkceCodeSucces, pkceCodeVerifier); connect(request, &NetworkRequest::requestFailed, [vpn](QNetworkReply::NetworkError error, const QByteArray&) { logger.log() << "Failed to complete the authentication" << error; vpn->errorHandle(ErrorHandler::toErrorType(error)); }); connect(request, &NetworkRequest::requestCompleted, [this, vpn](const QByteArray& data) { logger.log() << "Authentication completed"; authenticationCompleted(vpn, data); }); }); connect(m_authenticationListener, &AuthenticationListener::failed, [this, vpn](const ErrorHandler::ErrorType error) { vpn->errorHandle(error); emit completed(); }); connect(m_authenticationListener, &AuthenticationListener::abortedByUser, [this, vpn]() { vpn->abortAuthentication(); emit completed(); }); QString path("/api/v2/vpn/login/"); #if defined(MVPN_IOS) path.append("ios"); #elif defined(MVPN_LINUX) path.append("linux"); #elif defined(MVPN_ANDROID) path.append("android"); #elif defined(MVPN_MACOS) path.append("macos"); #elif defined(MVPN_WINDOWS) path.append("windows"); #elif defined(MVPN_DUMMY) // Let's use linux here. path.append("linux"); #else # error Not supported #endif QUrl url(Constants::API_URL); url.setPath(path); QUrlQuery query; query.addQueryItem("code_challenge", QUrl::toPercentEncoding(pkceCodeChallenge)); query.addQueryItem("code_challenge_method", "S256"); m_authenticationListener->start(vpn, url, query); } void TaskAuthenticate::authenticationCompleted(MozillaVPN* vpn, const QByteArray& data) { logger.log() << "Authentication completed"; QJsonDocument json = QJsonDocument::fromJson(data); if (json.isNull()) { vpn->errorHandle(ErrorHandler::RemoteServiceError); return; } QJsonObject obj = json.object(); QJsonValue userObj = obj.value("user"); if (!userObj.isObject()) { vpn->errorHandle(ErrorHandler::RemoteServiceError); return; } #ifdef QT_DEBUG logger.log() << "User data:" << QJsonDocument(userObj.toObject()).toJson(QJsonDocument::Compact); #endif QJsonValue tokenValue = obj.value("token"); if (!tokenValue.isString()) { vpn->errorHandle(ErrorHandler::RemoteServiceError); return; } QJsonDocument userDoc; userDoc.setObject(userObj.toObject()); vpn->authenticationCompleted(userDoc.toJson(QJsonDocument::Compact), tokenValue.toString()); emit completed(); } mozilla-vpn-client-2.2.0/src/tasks/authenticate/taskauthenticate.h000066400000000000000000000013061404202232700253370ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef TASKAUTHENTICATE_H #define TASKAUTHENTICATE_H #include "task.h" class QByteArray; class AuthenticationListener; class TaskAuthenticate final : public Task { Q_OBJECT Q_DISABLE_COPY_MOVE(TaskAuthenticate) public: TaskAuthenticate(); ~TaskAuthenticate(); void run(MozillaVPN* vpn) override; private: void authenticationCompleted(MozillaVPN* vpn, const QByteArray& data); private: AuthenticationListener* m_authenticationListener = nullptr; }; #endif // TASKAUTHENTICATE_H mozilla-vpn-client-2.2.0/src/tasks/captiveportallookup/000077500000000000000000000000001404202232700232565ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/tasks/captiveportallookup/taskcaptiveportallookup.cpp000066400000000000000000000027651404202232700307660ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "taskcaptiveportallookup.h" #include "captiveportal/captiveportal.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "networkrequest.h" namespace { Logger logger(LOG_NETWORKING, "TaskCaptivePortalLookup"); } TaskCaptivePortalLookup::TaskCaptivePortalLookup() : Task("TaskCaptivePortalLookup") { MVPN_COUNT_CTOR(TaskCaptivePortalLookup); } TaskCaptivePortalLookup::~TaskCaptivePortalLookup() { MVPN_COUNT_DTOR(TaskCaptivePortalLookup); } void TaskCaptivePortalLookup::run(MozillaVPN* vpn) { logger.log() << "Resolving the captive portal detector URL"; NetworkRequest* request = NetworkRequest::createForCaptivePortalLookup(this); connect(request, &NetworkRequest::requestFailed, [this, vpn](QNetworkReply::NetworkError error, const QByteArray&) { logger.log() << "Failed to obtain captive poral IPs" << error; vpn->errorHandle(ErrorHandler::toErrorType(error)); emit completed(); }); connect(request, &NetworkRequest::requestCompleted, [this, vpn](const QByteArray& data) { logger.log() << "Lookup completed"; if (vpn->captivePortal()->fromJson(data)) { vpn->captivePortal()->writeSettings(); } emit completed(); }); } mozilla-vpn-client-2.2.0/src/tasks/captiveportallookup/taskcaptiveportallookup.h000066400000000000000000000010621404202232700304200ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef TASKCAPTIVEPORTALLOOKUP_H #define TASKCAPTIVEPORTALLOOKUP_H #include "task.h" #include class TaskCaptivePortalLookup final : public Task { Q_DISABLE_COPY_MOVE(TaskCaptivePortalLookup) public: TaskCaptivePortalLookup(); ~TaskCaptivePortalLookup(); void run(MozillaVPN* vpn) override; }; #endif // TASKCAPTIVEPORTALLOOKUP_H mozilla-vpn-client-2.2.0/src/tasks/controlleraction/000077500000000000000000000000001404202232700225305ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/tasks/controlleraction/taskcontrolleraction.cpp000066400000000000000000000042351404202232700275040ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "taskcontrolleraction.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" constexpr uint32_t TASKCONTROLLER_TIMER_MSEC = 3000; namespace { Logger logger(QStringList{LOG_MAIN, LOG_CONTROLLER}, "TaskControllerAction"); } TaskControllerAction::TaskControllerAction( TaskControllerAction::TaskAction action) : Task("TaskControllerAction"), m_action(action) { MVPN_COUNT_CTOR(TaskControllerAction); logger.log() << "TaskControllerAction created for" << (action == eActivate ? "activation" : "deactivation"); connect(&m_timer, &QTimer::timeout, this, &TaskControllerAction::completed); } TaskControllerAction::~TaskControllerAction() { MVPN_COUNT_DTOR(TaskControllerAction); } void TaskControllerAction::run(MozillaVPN* vpn) { logger.log() << "TaskControllerAction run"; Controller* controller = vpn->controller(); Q_ASSERT(controller); connect(controller, &Controller::stateChanged, this, &TaskControllerAction::stateChanged); bool expectSignal = false; switch (m_action) { case eActivate: expectSignal = controller->activate(); break; case eDeactivate: expectSignal = controller->deactivate(); break; } // No signal expected. Probably, the VPN is already in the right state. Let's // use the timer to wait 1 cycle only. if (!expectSignal) { m_timer.start(0); return; } // Fallback 3 seconds. m_timer.start(TASKCONTROLLER_TIMER_MSEC); } void TaskControllerAction::stateChanged() { if (!m_timer.isActive()) { logger.log() << "stateChanged received by to be ignored"; return; } Controller* controller = MozillaVPN::instance()->controller(); Q_ASSERT(controller); Controller::State state = controller->state(); if ((m_action == eActivate && state == Controller::StateOn) || (m_action == eDeactivate && state == Controller::StateOff)) { logger.log() << "Operation completed"; m_timer.stop(); emit completed(); } } mozilla-vpn-client-2.2.0/src/tasks/controlleraction/taskcontrolleraction.h000066400000000000000000000016341404202232700271510ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef TASKCONTROLLERACTION_H #define TASKCONTROLLERACTION_H #include "task.h" #include #include // The purpose of this task is to block any other task when // activating/deactivating the VPN. It doesn't relay on the Controller state, // but just wait a bit: 1 second is enough. class TaskControllerAction final : public Task { Q_DISABLE_COPY_MOVE(TaskControllerAction) public: enum TaskAction { eActivate, eDeactivate, }; explicit TaskControllerAction(TaskAction action); ~TaskControllerAction(); void run(MozillaVPN* vpn) override; private slots: void stateChanged(); private: const TaskAction m_action; QTimer m_timer; }; #endif // TASKCONTROLLERACTION_H mozilla-vpn-client-2.2.0/src/tasks/function/000077500000000000000000000000001404202232700207745ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/tasks/function/taskfunction.cpp000066400000000000000000000011021404202232700242020ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "taskfunction.h" #include "leakdetector.h" TaskFunction::TaskFunction(std::function&& callback) : Task("TaskFunction"), m_callback(std::move(callback)) { MVPN_COUNT_CTOR(TaskFunction); } TaskFunction::~TaskFunction() { MVPN_COUNT_DTOR(TaskFunction); } void TaskFunction::run(MozillaVPN* vpn) { m_callback(vpn); emit completed(); } mozilla-vpn-client-2.2.0/src/tasks/function/taskfunction.h000066400000000000000000000011401404202232700236510ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef TASKFUNCTION_H #define TASKFUNCTION_H #include "task.h" #include #include class TaskFunction final : public Task { Q_DISABLE_COPY_MOVE(TaskFunction) public: TaskFunction(std::function&& callback); ~TaskFunction(); void run(MozillaVPN* vpn) override; private: std::function m_callback; }; #endif // TASKFUNCTION_H mozilla-vpn-client-2.2.0/src/tasks/heartbeat/000077500000000000000000000000001404202232700211065ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/tasks/heartbeat/taskheartbeat.cpp000066400000000000000000000042461404202232700244420ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "taskheartbeat.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "networkrequest.h" #include #include namespace { Logger logger(LOG_MAIN, "TaskHeartbeat"); } TaskHeartbeat::TaskHeartbeat() : Task("TaskHeartbeat") { MVPN_COUNT_CTOR(TaskHeartbeat); } TaskHeartbeat::~TaskHeartbeat() { MVPN_COUNT_DTOR(TaskHeartbeat); } void TaskHeartbeat::run(MozillaVPN* vpn) { NetworkRequest* request = NetworkRequest::createForHeartbeat(this); connect(request, &NetworkRequest::requestFailed, [this, request, vpn](QNetworkReply::NetworkError, const QByteArray&) { logger.log() << "Failed to talk with the server"; int statusCode = request->statusCode(); // Internal server errors. if (statusCode >= 500 && statusCode <= 509) { vpn->heartbeatCompleted(false); return; } // Request failure ((?!?) if (statusCode >= 400 && statusCode <= 409) { vpn->heartbeatCompleted(false); return; } // We don't know if this happeneded because of a global network // failure or a local network issue. In general, let's ignore this // error. vpn->heartbeatCompleted(true); emit completed(); }); connect(request, &NetworkRequest::requestCompleted, [this, vpn](const QByteArray& data) { logger.log() << "Heartbeat content received:" << data; QJsonObject json = QJsonDocument::fromJson(data).object(); QJsonValue mullvad = json.value("mullvadOK"); QJsonValue db = json.value("dbOK"); if ((mullvad.isBool() && db.isBool()) && (!mullvad.toBool() || !db.toBool())) { vpn->heartbeatCompleted(false); return; } vpn->heartbeatCompleted(true); emit completed(); }); } mozilla-vpn-client-2.2.0/src/tasks/heartbeat/taskheartbeat.h000066400000000000000000000007541404202232700241070ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef TASKHEARTBEAT_H #define TASKHEARTBEAT_H #include "task.h" #include class TaskHeartbeat final : public Task { Q_DISABLE_COPY_MOVE(TaskHeartbeat) public: TaskHeartbeat(); ~TaskHeartbeat(); void run(MozillaVPN* vpn) override; }; #endif // TASKHEARTBEAT_H mozilla-vpn-client-2.2.0/src/tasks/removedevice/000077500000000000000000000000001404202232700216245ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/tasks/removedevice/taskremovedevice.cpp000066400000000000000000000026331404202232700256740ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "taskremovedevice.h" #include "errorhandler.h" #include "leakdetector.h" #include "logger.h" #include "models/user.h" #include "mozillavpn.h" #include "networkrequest.h" namespace { Logger logger(LOG_MAIN, "TaskRemoveDevice"); } TaskRemoveDevice::TaskRemoveDevice(const QString& publicKey) : Task("TaskRemoveDevice"), m_publicKey(publicKey) { MVPN_COUNT_CTOR(TaskRemoveDevice); } TaskRemoveDevice::~TaskRemoveDevice() { MVPN_COUNT_DTOR(TaskRemoveDevice); } void TaskRemoveDevice::run(MozillaVPN* vpn) { logger.log() << "Removing the device with public key" << m_publicKey; NetworkRequest* request = NetworkRequest::createForDeviceRemoval(this, m_publicKey); connect(request, &NetworkRequest::requestFailed, [this, vpn](QNetworkReply::NetworkError error, const QByteArray&) { logger.log() << "Failed to remove the device" << error; vpn->errorHandle(ErrorHandler::toErrorType(error)); emit completed(); }); connect(request, &NetworkRequest::requestCompleted, [this, vpn](const QByteArray&) { logger.log() << "Device removed"; vpn->deviceRemoved(m_publicKey); emit completed(); }); } mozilla-vpn-client-2.2.0/src/tasks/removedevice/taskremovedevice.h000066400000000000000000000011051404202232700253320ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef TASKREMOVEDEVICE_H #define TASKREMOVEDEVICE_H #include "task.h" #include class TaskRemoveDevice final : public Task { Q_DISABLE_COPY_MOVE(TaskRemoveDevice) public: explicit TaskRemoveDevice(const QString& publickKey); ~TaskRemoveDevice(); void run(MozillaVPN* vpn) override; private: QString m_publicKey; }; #endif // TASKREMOVEDEVICE_H mozilla-vpn-client-2.2.0/src/timercontroller.cpp000066400000000000000000000072671404202232700217660ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "timercontroller.h" #include "leakdetector.h" #include "logger.h" namespace { Logger logger(LOG_CONTROLLER, "TimerController"); } TimerController::TimerController(ControllerImpl* impl) : m_impl(impl) { MVPN_COUNT_CTOR(TimerController); Q_ASSERT(m_impl); m_impl->setParent(this); connect(m_impl, &ControllerImpl::initialized, this, &ControllerImpl::initialized); connect(m_impl, &ControllerImpl::connected, [this] { TimerController::maybeDone(true); }); connect(m_impl, &ControllerImpl::disconnected, [this] { TimerController::maybeDone(false); }); connect(m_impl, &ControllerImpl::statusUpdated, this, &ControllerImpl::statusUpdated); m_timer.setSingleShot(true); connect(&m_timer, &QTimer::timeout, this, &TimerController::timeout); } TimerController::~TimerController() { MVPN_COUNT_DTOR(TimerController); } void TimerController::initialize(const Device* device, const Keys* keys) { m_impl->initialize(device, keys); } void TimerController::activate( const Server& server, const Device* device, const Keys* keys, const QList& allowedIPAddressRanges, const QList& vpnDisabledApps, Reason reason) { if (m_state != None) { return; } m_state = Connecting; if (reason != ReasonSwitching) { m_timer.stop(); m_timer.start(TIME_ACTIVATION); } m_impl->activate(server, device, keys, allowedIPAddressRanges, vpnDisabledApps, reason); } void TimerController::deactivate(Reason reason) { if (m_state != None) { return; } m_state = Disconnecting; m_timer.stop(); switch (reason) { case ReasonSwitching: m_timer.start(TIME_SWITCHING); break; case ReasonConfirming: m_timer.start(0); break; default: Q_ASSERT(reason == ReasonNone); m_timer.start(TIME_DEACTIVATION); } m_impl->deactivate(reason); } void TimerController::timeout() { logger.log() << "TimerController - Timeout:" << m_state; Q_ASSERT(m_state != None); if (m_state == Connected) { m_state = None; emit connected(); return; } if (m_state == Disconnected) { m_state = None; emit disconnected(); return; } // Any other state can be ignored. } void TimerController::maybeDone(bool isConnected) { logger.log() << "TimerController - Operation completed:" << m_state << isConnected; if (m_state == Connecting) { if (m_timer.isActive()) { // The connection was faster. m_state = isConnected ? Connected : Disconnected; return; } // The timer was faster. m_state = None; if (isConnected) { emit connected(); } else { emit disconnected(); } return; } if (m_state == Disconnecting) { if (m_timer.isActive()) { // The disconnection was faster. m_state = Disconnected; return; } // The timer was faster. m_state = None; emit disconnected(); return; } // External events could trigger the following codes. Q_ASSERT(m_state == None); Q_ASSERT(!m_timer.isActive()); if (isConnected) { emit connected(); return; } emit disconnected(); } void TimerController::checkStatus() { m_impl->checkStatus(); } void TimerController::getBackendLogs( std::function&& a_callback) { std::function callback = std::move(a_callback); m_impl->getBackendLogs(std::move(callback)); } void TimerController::cleanupBackendLogs() { m_impl->cleanupBackendLogs(); } mozilla-vpn-client-2.2.0/src/timercontroller.h000066400000000000000000000026651404202232700214300ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef TIMERCONTROLLER_H #define TIMERCONTROLLER_H #include "controllerimpl.h" #include #include constexpr uint32_t TIME_ACTIVATION = 1000; constexpr uint32_t TIME_DEACTIVATION = 1500; constexpr uint32_t TIME_SWITCHING = 2000; constexpr uint32_t TIME_CONFIRMING = 0; class TimerController final : public ControllerImpl { Q_OBJECT Q_DISABLE_COPY_MOVE(TimerController) public: TimerController(ControllerImpl* impl); ~TimerController(); void initialize(const Device* device, const Keys* keys) override; void activate(const Server& server, const Device* device, const Keys* keys, const QList& allowedIPAddressRanges, const QList& vpnDisabledApps, Reason reason) override; void deactivate(Reason reason) override; void checkStatus() override; void getBackendLogs(std::function&& callback) override; void cleanupBackendLogs() override; private slots: void timeout(); private: void maybeDone(bool isConnected); private: ControllerImpl* m_impl; QTimer m_timer; enum State { None, Connecting, Connected, Disconnecting, Disconnected, }; State m_state = None; }; #endif // TIMERCONTROLLER_H mozilla-vpn-client-2.2.0/src/timersingleshot.cpp000066400000000000000000000012241404202232700217450ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "timersingleshot.h" #include // static void TimerSingleShot::create(QObject* parent, uint32_t timer, std::function&& a_callback) { std::function callback = std::move(a_callback); QTimer* t = new QTimer(parent); QObject::connect(t, &QTimer::timeout, [t, callback = std::move(callback)]() { t->deleteLater(); callback(); }); t->setSingleShot(true); t->start(timer); } mozilla-vpn-client-2.2.0/src/timersingleshot.h000066400000000000000000000010231404202232700214070ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef TIMERSINGLESHOT_H #define TIMERSINGLESHOT_H #include #include // Like QTimer::singleShot but with a parent object. class TimerSingleShot { public: static void create(QObject* parent, uint32_t timer, std::function&& callback); }; #endif // TIMERSINGLESHOT_H mozilla-vpn-client-2.2.0/src/ui/000077500000000000000000000000001404202232700164375ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/components/000077500000000000000000000000001404202232700206245ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/components/VPNAboutUs.qml000066400000000000000000000070561404202232700233150ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme Item { id: viewAboutUs property alias isSettingsView: menu.isSettingsView ListModel { id: aboutUsListModel ListElement { linkId: "tos" //% "Terms of Service" linkTitle: qsTrId("vpn.aboutUs.tos") openUrl: VPN.LinkTermsOfService } ListElement { linkId: "privacy" //% "Privacy Notice" linkTitle: qsTrId("vpn.aboutUs.privacyNotice") openUrl: VPN.LinkPrivacyNotice } ListElement { linkId: "license" //% "License" linkTitle: qsTrId("vpn.aboutUs.license") openUrl: VPN.LinkLicense } } VPNMenu { id: menu objectName: "aboutUsBackButton" //% "About us" title: qsTrId("vpn.settings.aboutUs") isSettingsView: true } Rectangle { id: aboutUsCopy anchors.top: menu.bottom anchors.left: viewAboutUs.left anchors.topMargin: Theme.vSpacing anchors.leftMargin: Theme.windowMargin anchors.rightMargin: Theme.windowMargin height: childrenRect.height width: viewAboutUs.width - (Theme.windowMargin * 2) color: "transparent" VPNBoldLabel { id: mozillaLabel width: aboutUsCopy.width text: qsTrId("vpn.main.productName") } VPNTextBlock { id: mozillaText text: qsTrId("vpn.main.productDescription") anchors.top: mozillaLabel.bottom anchors.topMargin: 8 width: aboutUsCopy.width } VPNBoldLabel { id: releaseLabel //% "Release Version" //: Refers to the installed version. For example: "Release Version: 1.23" text: qsTrId("vpn.aboutUs.releaseVersion") anchors.top: mozillaText.bottom anchors.topMargin: 16 } VPNTextBlock { anchors.top: releaseLabel.bottom anchors.topMargin: 8 text: VPN.buildNumber === "" ? VPN.versionString : (VPN.versionString + " (" + VPN.buildNumber + ")") } } Rectangle { id: divider height: 1 width: viewAboutUs.width anchors.top: aboutUsCopy.bottom anchors.left: viewAboutUs.left anchors.right: viewAboutUs.right anchors.topMargin: 12 anchors.leftMargin: Theme.windowMargin anchors.rightMargin: Theme.windowMargin color: "#0C0C0D0A" } VPNList { id: settingList objectName: "aboutUsList" anchors.top: divider.bottom anchors.topMargin: 16 anchors.bottomMargin: Theme.vSpacing height: parent.height - menu.height - aboutUsCopy.height - divider.height width: viewAboutUs.width spacing: Theme.listSpacing model: aboutUsListModel listName: menu.title delegate: VPNExternalLinkListItem { objectName: "aboutUsList-" + linkId title: linkTitle accessibleName: linkTitle onClicked: VPN.openLink(openUrl) } ScrollBar.vertical: ScrollBar { Accessible.ignored: true } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNAlert.qml000066400000000000000000000104641404202232700227770ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme Rectangle { id: alertBox property var alertType: "" property var alertColor: Theme.redButton property var alertText: "" property var alertLinkText: "" color: "transparent" height: Math.max(40, (labelWrapper.height + Theme.windowMargin)) width: parent.width - Theme.windowMargin y: fullscreenRequired()? iosSafeAreaTopMargin.height + Theme.windowMargin : Theme.windowMargin anchors.horizontalCenter: parent.horizontalCenter anchors.margins: Theme.windowMargin / 2 radius: Theme.cornerRadius VPNButtonBase { id: alertAction anchors.fill: alertBox radius: Theme.cornerRadius onClicked: { switch (alertType) { case ("update"): stackview.push("../views/ViewUpdate.qml", StackView.Immediate); break; case ("authentication-failed"): VPN.authenticate(); break; case ("backend-service"): VPN.backendServiceRestore(); break; case ("connection-failed"): case ("no-connection"): case ("subscription-failed"): case ("geoip-restriction"): default: VPN.hideAlert(); } } VPNUIStates { itemToFocus: parent colorScheme: alertColor setMargins: -3 } Rectangle { id: labelWrapper color: "transparent" height: label.paintedHeight anchors.left: alertAction.left width: alertAction.width - Theme.rowHeight anchors.verticalCenter: parent.verticalCenter Label { id: label anchors.centerIn: parent text: alertBox.alertText + " " + "" + alertLinkText + "" horizontalAlignment: Text.AlignHCenter font.pixelSize: Theme.fontSizeSmall color: Theme.white width: labelWrapper.width - Theme.windowMargin wrapMode: Label.WordWrap } } VPNMouseArea { } } VPNFocusOutline { focusColorScheme: alertColor focusedComponent: closeButton anchors.fill: closeButton setMargins: -3 } VPNButtonBase { // Hack to create the two right angle corners // where closeButton meets alertAction id: closeButton height: parent.height width: Theme.rowHeight clip: true anchors.right: parent.right anchors.rightMargin: 0 radius: 0 Accessible.name: "Close" onClicked: { if (alertType === "update") { closeAlert.start(); return VPN.hideUpdateRecommendedAlert(); } return VPN.hideAlert(); } VPNFocusBorder { anchors.fill: closeButton border.color: alertColor.focusBorder opacity: closeButton.activeFocus ? 1 : 0 z: 1 } Rectangle { id: backgroundRect height: parent.height width: parent.width + 10 anchors.left: closeButton.left anchors.leftMargin: -10 radius: 4 color: "transparent" clip: true state: closeButton.state VPNUIStates { colorScheme: alertColor setMargins: -3 } Behavior on color { ColorAnimation { duration: 200 } } } Image { id: alertBoxClose source: "../resources/close-white.svg" sourceSize.width: 12 sourceSize.height: 12 anchors.centerIn: closeButton } VPNMouseArea { } } VPNFocusBorder { anchors.fill: alertBox border.color: alertColor.focusBorder opacity: alertAction.activeFocus ? 1 : 0 } } mozilla-vpn-client-2.2.0/src/ui/components/VPNAnimatedRings.qml000066400000000000000000000152461404202232700244600ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtGraphicalEffects 1.14 import QtQuick.Layouts 1.14 import QtQml 2.14 import Mozilla.VPN 1.0 Rectangle { id: animatedRingsWrapper property var yCenter: logo.y + 40 - 1 property bool startAnimation: false property bool isCurrentyVisible: true property bool canRender:true property var animationPuffer: 0 onIsCurrentyVisibleChanged: { // In case we got Visible, start a delay-timer // so that we switch canRender to true after 300ms. // - Otherwise this animation will require Draw-Cycles // that might be needed for the pan-navigation-animation. if(isCurrentyVisible){ canRenderTimer.start(); return; } canRender=false; } Timer { interval: 300 id: canRenderTimer running: false repeat: false onTriggered: { canRender = true;} } onStartAnimationChanged: animatedRings.requestPaint() anchors.fill: box radius: box.radius color: "transparent" antialiasing: true visible: false Canvas { id: animatedRings property real maxRadius: 175 property real startNextRing: 95 property real startingRadius: 50 property real startingBorderWidth: 1 property real ring1Radius property real ring1BorderWidth property real ring2Radius property real ring2BorderWidth property real ring3Radius property real ring3BorderWidth property bool drawingRing2 property var drawingRing3 property var ringXCenter: parent.width / 2 property var ringYCenter: animatedRingsWrapper.yCenter function updateRing(rRadius, rBorderWidth) { if (rRadius >= maxRadius) { // Restore to factory defaults rRadius = startingRadius; rBorderWidth = startingBorderWidth; } //Increase border width quickly on new ring creation if (rRadius < 115 && rBorderWidth <= 4.5) rBorderWidth += 0.15; // Start decrementing ring border width if (rRadius >= 115 && rBorderWidth >= 0.1) rBorderWidth -= 0.05; if (rRadius >= 135) rRadius += 0.45; else rRadius += 0.5; return { "rRadius": rRadius, "rBorderWidth": rBorderWidth }; } function updateRing1() { let data = updateRing(ring1Radius, ring1BorderWidth); ring1Radius = data.rRadius; ring1BorderWidth = data.rBorderWidth; } function updateRing2() { let data = updateRing(ring2Radius, ring2BorderWidth); ring2Radius = data.rRadius; ring2BorderWidth = data.rBorderWidth; } function updateRing3() { let data = updateRing(ring3Radius, ring3BorderWidth); ring3Radius = data.rRadius; ring3BorderWidth = data.rBorderWidth; } function animateRings() { if(!canRender){ return; } updateRing1(); if (drawingRing2) updateRing2(); if (drawingRing3) updateRing3(); } Timer { interval: 20 running: animatedRingsWrapper.startAnimation repeat: true onTriggered: animatedRings.animateRings() } function drawRing(ctx, ringRadius, borderWidth) { ctx.beginPath(); ctx.arc(ringXCenter, ringYCenter, ringRadius, 0, Math.PI * 2, true); ctx.lineWidth = borderWidth; ctx.strokeStyle = "#FFFFFF"; ctx.closePath(); ctx.stroke(); } function resetRingValues() { ring1Radius = startingRadius; ring2Radius = startingRadius; ring3Radius = startingRadius; ring1BorderWidth = startingBorderWidth; ring2BorderWidth = startingBorderWidth; ring3BorderWidth = startingBorderWidth; drawingRing2 = false; drawingRing3 = false; } opacity: 0.1 height: animatedRingsWrapper.height width: animatedRingsWrapper.width anchors.fill: animatedRingsWrapper onRing1RadiusChanged: animatedRings.requestPaint() renderStrategy: Canvas.Threaded contextType: "2d" onPaint: { // Dont paint if not needed if(!canRender){ return } let ctx = getContext("2d"); ctx.reset(); if (!animatedRingsWrapper.startAnimation) { resetRingValues(); return ; } // Draw first ring drawRing(ctx, ring1Radius, ring1BorderWidth); // Draw second ring when the first ring's radius is 95 if (!drawingRing2 && ring1Radius === startNextRing) drawingRing2 = true; // Draw third ring when the second ring's radius is 95 if (!drawingRing3 && ring2Radius === startNextRing) drawingRing3 = true; if (drawingRing2) drawRing(ctx, ring2Radius, ring2BorderWidth); if (drawingRing3) drawRing(ctx, ring3Radius, ring3BorderWidth); } Component.onCompleted: { resetRingValues(); } } Rectangle { anchors.horizontalCenterOffset: 0 anchors.horizontalCenter: animatedRingsWrapper.horizontalCenter y: 45 height: 90 width: 90 radius: 50 color: "#321C64" antialiasing: true } RadialGradient { id: bgGradient antialiasing: true anchors.fill: animatedRingsWrapper verticalOffset: -68 layer.enabled: true gradient: Gradient { GradientStop { position: 0.26 color: "transparent" } GradientStop { position: 0.5 color: "#321C64" } } layer.effect: OpacityMask { maskSource: Item { width: animatedRingsWrapper.width height: animatedRingsWrapper.height Rectangle { anchors.fill: parent radius: animatedRingsWrapper.radius } } } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNBoldLabel.qml000066400000000000000000000006471404202232700235520ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import "../themes/themes.js" as Theme // VPNBoldLabel Label { font.pixelSize: Theme.fontSize font.family: Theme.fontBoldFamily color: Theme.fontColorDark } mozilla-vpn-client-2.2.0/src/ui/components/VPNButton.qml000066400000000000000000000025211404202232700231760ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import "../themes/themes.js" as Theme VPNButtonBase { id: button height: Theme.rowHeight Layout.preferredHeight: Layout ? Theme.rowHeight : undefined width: Math.min(parent.width * 0.83, Theme.maxHorizontalContentWidth) Layout.preferredWidth: Layout ? Math.min(parent.width * 0.83, Theme.maxHorizontalContentWidth) : undefined Layout.alignment: Layout ? Qt.AlignHCenter : undefined Component.onCompleted: { state = uiState.stateDefault; } VPNUIStates { colorScheme: Theme.blueButton setMargins: -5 } VPNButtonLoader { id: loader state: loaderVisible ? "active" : "inactive" } VPNMouseArea { hoverEnabled: loaderVisible === false } contentItem: Label { id: label color: Theme.white text: button.text horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter elide: Text.ElideRight width: button.width font.family: Theme.fontBoldFamily font.pixelSize: Theme.fontSize } } mozilla-vpn-client-2.2.0/src/ui/components/VPNButtonBase.qml000066400000000000000000000027201404202232700237720ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.0 import QtQuick.Controls 2.5 import QtQuick.Layouts 1.14 import "../themes/themes.js" as Theme RoundButton { id: root property var visualStateItem: root property var uiState: Theme.uiState property var loaderVisible: false property var handleKeyClick: function() { clicked() } focusPolicy: Qt.StrongFocus Keys.onPressed: { if (loaderVisible) { return; } if (event.key === Qt.Key_Return || event.key === Qt.Key_Space) visualStateItem.state = uiState.statePressed; } Keys.onReleased: { if (loaderVisible) { return } if (event.key === Qt.Key_Return || event.key === Qt.Key_Space) { visualStateItem.state = uiState.stateDefault; } if (event.key === Qt.Key_Return) handleKeyClick(); } Accessible.role: Accessible.Button Accessible.onPressAction: handleKeyClick() Accessible.focusable: true onActiveFocusChanged: { if (!activeFocus) return visualStateItem.state = uiState.stateDefault; if (typeof (ensureVisible) !== "undefined") return ensureVisible(visualStateItem); } background: Rectangle { color: "transparent" } } mozilla-vpn-client-2.2.0/src/ui/components/VPNButtonLoader.qml000066400000000000000000000043761404202232700243370ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import "../themes/themes.js" as Theme Rectangle { id: loader anchors.fill: parent anchors.margins: -1 radius: Theme.cornerRadius opacity: 0 color: "#98bff2" state: "inactive" states: [ State { name: "active" }, State { name: "inactive" } ] transitions: [ Transition { to: "active" ParallelAnimation { PropertyAnimation { target: loader property: "opacity" from: 0 to: 1 duration: 200 easing.type: Easing.OutCurve } PropertyAnimation { target: loadingIcon property: "scale" from: 0.4 to: 1 duration: 200 easing.type: Easing.OutCurve } } }, Transition { to: "inactive" ParallelAnimation { PropertyAnimation { target: loader property: "opacity" from: 1 to: 0 duration: 200 easing.type: Easing.OutCurve } PropertyAnimation { target: loadingIcon property: "scale" from: 1 to: 0.4 duration: 200 easing.type: Easing.OutCurve } } } ] VPNIcon { id: loadingIcon source: "../resources/buttonLoader.svg" anchors.centerIn: loader sourceSize.height: 28 sourceSize.width: 28 } PropertyAnimation { id: animation running: loader.state == "active" target: loadingIcon property: "rotation" from: 0 to: 360 duration: 4000 loops: Animation.Infinite } } mozilla-vpn-client-2.2.0/src/ui/components/VPNCallout.qml000066400000000000000000000035171404202232700233340ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme RowLayout { id: callout property var calloutTitle property var calloutSubtitle property var calloutImage Layout.minimumHeight: 40 spacing: 0 Layout.alignment: Qt.AlignHCenter Rectangle { Layout.minimumHeight: Theme.vSpacing Layout.minimumWidth: Theme.vSpacing Layout.maximumWidth: Theme.vSpacing color: "transparent" Layout.alignment: Qt.AlignTop Layout.topMargin: 2 Layout.leftMargin: Theme.windowMargin * 2 VPNIcon { source: calloutImage sourceSize.width: Theme.iconSize sourceSize.height: Theme.iconSize antialiasing: true Layout.alignment: Qt.AlignCenter } } Column { id: calloutCopy spacing: 2 Layout.leftMargin: Theme.windowMargin Layout.rightMargin: Theme.windowMargin * 2 Layout.alignment: Qt.AlignLeft Layout.fillWidth: true VPNTextBlock { color: Theme.fontColorDark Layout.fillWidth: true width: calloutCopy.width Layout.alignment: Qt.AlignTop | Qt.AlignLeft font.pixelSize: Theme.fontSize text: calloutTitle } VPNTextBlock { color: Theme.fontColor font.pixelSize: Theme.fontSizeSmall Layout.fillWidth: true width: calloutCopy.width Layout.alignment: Qt.AlignTop | Qt.AlignLeft text: calloutSubtitle } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNCheckBox.qml000066400000000000000000000105431404202232700234140ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtGraphicalEffects 1.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme CheckBox { // TODO // property var accessibleName id: checkBox property var uiState: Theme.uiState signal clicked() height: 20 width: 20 Layout.alignment: Qt.AlignTop Component.onCompleted: state = uiState.stateDefault hoverEnabled: false onActiveFocusChanged: { if (!activeFocus) mouseArea.changeState(uiState.stateDefault); if (activeFocus && typeof (ensureVisible) !== "undefined") ensureVisible(checkBox); } Keys.onPressed: { if (event.key === Qt.Key_Return || event.key === Qt.Key_Space) mouseArea.changeState(uiState.statePressed); } Keys.onReleased: { if (event.key === Qt.Key_Return || event.key === Qt.Key_Space) mouseArea.changeState(uiState.stateDefault); } Keys.onReturnPressed: checkBox.clicked() // TODO // Accessible.name: accessibleName Accessible.onPressAction: clicked() Accessible.onToggleAction: clicked() Accessible.focusable: true states: [ State { name: uiState.stateDefault PropertyChanges { target: checkBoxIndicator border.color: checkBox.checked || checkBox.activeFocus ? Theme.blue : Theme.fontColor } PropertyChanges { target: checkmark opacity: checkBox.checked ? 1 : 0 checkmarkColor: checkBox.checked ? Theme.blue : "#DBDBDB" } }, State { name: uiState.statePressed PropertyChanges { target: checkBoxIndicator border.color: checkBox.checked ? Theme.bluePressed : Theme.fontColorDark } PropertyChanges { target: checkmark opacity: 1 checkmarkColor: checkBox.checked ? Theme.bluePressed : Theme.greyPressed } }, State { name: uiState.stateHovered PropertyChanges { target: checkBoxIndicator border.color: checkBox.checked ? Theme.blueHovered : Theme.fontColorDark } PropertyChanges { target: checkmark opacity: 1 checkmarkColor: checkBox.checked ? Theme.blueHovered : "#DBDBDB" } } ] Item { id: checkmark property var checkmarkColor: "#DBDBDB" height: 20 width: 20 anchors.fill: checkBoxIndicator Rectangle { id: checkmarkBg color: checkmark.checkmarkColor height: 20 width: 20 antialiasing: true smooth: true visible: false Behavior on color { PropertyAnimation { duration: 200 } } } Image { id: checkmarkIcon source: "../resources/checkmark.svg" sourceSize.height: 13 sourceSize.width: 12 visible: false anchors.centerIn: checkmark } OpacityMask { anchors.centerIn: checkmark height: checkmarkIcon.height width: checkmarkIcon.width source: checkmarkBg maskSource: checkmarkIcon } Behavior on opacity { PropertyAnimation { duration: 100 } } } VPNMouseArea { id: mouseArea } indicator: Rectangle { id: checkBoxIndicator height: 20 width: 20 color: Theme.bgColor border.color: Theme.fontColor border.width: 2 radius: 4 antialiasing: true state: checkBox.state VPNUIStates { itemToFocus: checkBox colorScheme: Theme.blueButton visible: isEnabled } Behavior on border.color { PropertyAnimation { duration: 100 } } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNCheckBoxAlert.qml000066400000000000000000000023521404202232700244030ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme RowLayout { id: turnVPNOffAlert visible: (VPNController.state !== VPNController.StateOff) anchors.left: parent.left anchors.right: parent.right anchors.topMargin: 12 anchors.leftMargin: 56 anchors.rightMargin: Theme.windowMargin spacing: 0 property var errorMessage: "..." Rectangle { color: "transparent" Layout.preferredHeight: message.lineHeight Layout.maximumHeight: message.lineHeight Layout.preferredWidth: 14 Layout.rightMargin: 8 Layout.leftMargin: 4 Layout.alignment: Qt.AlignTop VPNIcon { id: warningIcon source: "../resources/warning.svg" sourceSize.height: 14 sourceSize.width: 14 Layout.alignment: Qt.AlignVCenter } } VPNTextBlock { id: message text: errorMessage color: Theme.red Layout.fillWidth: true } } mozilla-vpn-client-2.2.0/src/ui/components/VPNCheckBoxRow.qml000066400000000000000000000031421404202232700241010ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import QtGraphicalEffects 1.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme RowLayout { id: checkBoxRow property var labelText property var subLabelText property bool isChecked property bool isEnabled: true property bool showDivider: true property var leftMargin: 18 signal clicked() spacing: 0 VPNCheckBox { id: checkBox Layout.leftMargin: leftMargin onClicked: checkBoxRow.clicked() checked: isChecked enabled: isEnabled opacity: isEnabled ? 1 : 0.5 } ColumnLayout { id: labelWrapper Layout.fillWidth: true spacing: 4 VPNInterLabel { id: label Layout.alignment: Qt.AlignLeft Layout.fillWidth: true text: labelText color: Theme.fontColorDark horizontalAlignment: Text.AlignLeft } VPNTextBlock { id: subLabel Layout.fillWidth: true text: subLabelText visible: !!subLabelText.length wrapMode: Text.WordWrap } Rectangle { id: divider Layout.topMargin: 16 Layout.preferredHeight: 1 Layout.fillWidth: true color: "#E7E7E7" visible: showDivider } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNChevron.qml000066400000000000000000000006741404202232700233360ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Layouts 1.14 import "../themes/themes.js" as Theme // VPNChevron Image { source: "../resources/chevron.svg" sourceSize.height: 24 sourceSize.width: 24 fillMode: Image.PreserveAspectFit } mozilla-vpn-client-2.2.0/src/ui/components/VPNClickableRow.qml000066400000000000000000000037501404202232700242710ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtGraphicalEffects 1.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme VPNButtonBase { id: mainRow property var rowShouldBeDisabled: false property var accessibleName property var backgroundColor: Theme.iconButtonLightBackground property var handleMouseClick: function() { mainRow.clicked(); } property var canGrowVertical: false visualStateItem: rowVisualStates height: Theme.rowHeight anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: Theme.windowMargin / 2 anchors.rightMargin: Theme.windowMargin / 2 width: parent.width - (Theme.windowMargin * 2) opacity: rowShouldBeDisabled ? 0.7 : 1 enabled: !rowShouldBeDisabled Accessible.ignored: rowShouldBeDisabled Accessible.name: accessibleName PropertyAnimation on opacity { duration: 200 } // visual state changes are applied to this // component to prevent state overwrite conflicts // in VPNServerCountry {} Rectangle { id: rowVisualStates width: mainRow.width height: canGrowVertical? mainRow.height : Theme.rowHeight anchors.top: mainRow.top radius: Theme.cornerRadius border.width: Theme.focusBorderWidth border.color: "transparent" color: "transparent" Component.onCompleted: rowVisualStates.state = uiState.stateDefault } VPNUIStates { id: vpnFocus itemToAnchor: rowVisualStates colorScheme: backgroundColor } VPNMouseArea { anchors.fill: rowVisualStates hoverEnabled: !rowShouldBeDisabled targetEl: rowVisualStates onMouseAreaClicked: handleMouseClick } } mozilla-vpn-client-2.2.0/src/ui/components/VPNConnectionInfo.qml000066400000000000000000000127541404202232700246470ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtCharts 2.0 import QtQuick.Controls 2.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme Popup { id: popup height: box.height width: box.width padding: 0 leftInset: 0 rightInset: 0 modal: true focus: true closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent onClosed: VPNConnectionData.deactivate() onOpened: { VPNConnectionData.activate(txSeries, rxSeries, axisX, axisY); VPNCloseEventHandler.addView(chartWrapper); popup.forceActiveFocus() } // TODO: We can not use Accessible type on Popup because it does not inherit // from an Item. The code below generates the following warning: // "...Accessible must be attached to an Item..." // See https://github.com/mozilla-mobile/mozilla-vpn-client/issues/322 for // more details. // Accessible.focusable: true // Accessible.role: Accessible.Dialog // Accessible.name: connectionInfoButton.accessibleName contentItem: Item { id: chartWrapper property var rBytes: VPNConnectionData.rxBytes property var tBytes: VPNConnectionData.txBytes width: box.width height: box.height antialiasing: true ChartView { id: chart antialiasing: true backgroundColor: "#321C64" width: chartWrapper.width height: (chartWrapper.height / 2) - 32 legend.visible: false anchors.horizontalCenter: chartWrapper.horizontalCenter anchors.top: chartWrapper.top anchors.topMargin: 48 anchors.left: chartWrapper.left margins.top: 0 margins.bottom: 0 margins.left: 0 margins.right: 0 animationOptions: ChartView.NoAnimation ValueAxis { id: axisX tickCount: 1 min: 0 max: 29 lineVisible: false labelsVisible: false gridVisible: false visible: false } ValueAxis { id: axisY tickCount: 1 min: 10 max: 80 lineVisible: false labelsVisible: false gridVisible: false visible: false } SplineSeries { id: txSeries axisX: axisX axisY: axisY color: "#F68953" width: 2 } SplineSeries { id: rxSeries axisX: axisX axisY: axisY color: "#EE3389" width: 2 } } VPNBoldLabel { anchors.top: parent.top anchors.topMargin: Theme.windowMargin * 1.5 anchors.left: parent.left anchors.right: parent.right anchors.horizontalCenter: parent.center anchors.horizontalCenterOffset: 0 horizontalAlignment: Text.AlignHCenter color: Theme.white //% "IP: %1" //: The current IP address text: qsTrId("vpn.connectionInfo.ip").arg(VPNConnectionData.ipAddress) Accessible.name: text Accessible.role: Accessible.StaticText } Row { spacing: 48 anchors.bottom: parent.bottom anchors.bottomMargin: 32 anchors.horizontalCenter: parent.horizontalCenter VPNGraphLegendMarker { //% "Download" //: The current download speed. The speed is shown on the next line. markerLabel: qsTrId("vpn.connectionInfo.download") rectColor: "#EE3389" markerData: chartWrapper.rBytes } VPNGraphLegendMarker { //% "Upload" //: The current upload speed. The speed is shown on the next line. markerLabel: qsTrId("vpn.connectionInfo.upload") rectColor: "#F68953" markerData: chartWrapper.tBytes } } VPNIconButton { id: backButton objectName: "connectionInfoBackButton" onClicked: popup.close() buttonColorScheme: Theme.iconButtonDarkBackground anchors.top: parent.top anchors.left: parent.left anchors.topMargin: Theme.windowMargin / 2 anchors.leftMargin: Theme.windowMargin / 2 //% "Close" accessibleName: qsTrId("vpn.connectionInfo.close") Image { anchors.centerIn: backButton source: "../resources/close-white.svg" sourceSize.height: 16 sourceSize.width: 16 } } Connections { function onGoBack(item) { if (item === chartWrapper) backButton.clicked(); } target: VPNCloseEventHandler } } background: Rectangle { color: box.color height: box.height width: box.width radius: box.radius antialiasing: true } Overlay.modal: Rectangle { color: Theme.bgColor30 opacity: 0.5 } } mozilla-vpn-client-2.2.0/src/ui/components/VPNConnectionStability.qml000066400000000000000000000101501404202232700257040ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme RowLayout { property var numGridColumns: grid.columns function setColumns() { grid.columns = grid.childrenRect.width > (window.width - 48) ? 1 : 3; col.handleMultilineText(); } id: stability Layout.preferredHeight: Theme.controllerInterLineHeight Layout.alignment: Qt.AlignHCenter spacing: 0 state: VPNConnectionHealth.stability onStateChanged: setColumns(); states: [ State { name: VPNConnectionHealth.Stable PropertyChanges { target: stability visible: false opacity: 0 } PropertyChanges { target: logoSubtitle visible: true } }, State { name: VPNConnectionHealth.Unstable PropertyChanges { target: stability visible: true opacity: 1 } PropertyChanges { target: logoSubtitle visible: false } PropertyChanges { target: stabilityLabel color: Theme.orange } PropertyChanges { target: warningIcon source: "../resources/warning-orange.svg" } }, State { name: VPNConnectionHealth.NoSignal extend: VPNConnectionHealth.Unstable PropertyChanges { target: stabilityLabel color: Theme.red } PropertyChanges { target: warningIcon source: "../resources/warning.svg" } } ] GridLayout { id: grid columnSpacing: 0 state: parent.state Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true Component.onCompleted: setColumns() RowLayout { spacing: 6 Layout.alignment: Qt.AlignHCenter Rectangle { height: 16 width: 14 color: "transparent" Image { id: warningIcon sourceSize.height: 14 sourceSize.width: 14 fillMode: Image.PreserveAspectFit } } VPNInterLabel { //% "Unstable" //: This refers to the user’s internet connection. readonly property var textUnstable: qsTrId("vpn.connectionStability.unstable") //% "No Signal" readonly property var textNoSignal: qsTrId("vpn.connectionStability.noSignal") id: stabilityLabel lineHeight: Theme.controllerInterLineHeight onPaintedWidthChanged: setColumns(); text: VPNConnectionHealth.stability === VPNConnectionHealth.Unstable ? textUnstable : textNoSignal } } Rectangle { color: "#FFFFFF" opacity: 0.8 radius: 3 Layout.leftMargin: Theme.windowMargin / 2 Layout.rightMargin: Layout.leftMargin Layout.preferredHeight: 4 Layout.preferredWidth: 4 visible: parent.columns === 3 } VPNInterLabel { //% "Check Connection" //: Message displayed to the user when the connection is unstable or //: missing, asking them to check their connection. text: qsTrId("vpn.connectionStability.checkConnection") color: "#FFFFFF" opacity: 0.8 horizontalAlignment: Text.AlignHCenter Layout.alignment: Qt.AlignCenter onPaintedWidthChanged: setColumns(); lineHeight: grid.columns > 1 ? Theme.controllerInterLineHeight : 10 } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNControllerNav.qml000066400000000000000000000047421404202232700245220ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme ColumnLayout { id: controller property var titleText property var subtitleText property var descriptionText property var imgSource property var imgSize: 20 property var imgIsVector: false property var disableRowWhen spacing: 4 width: parent.width - Theme.windowMargin anchors.horizontalCenter: parent.horizontalCenter VPNBoldLabel { text: titleText Layout.leftMargin: Theme.windowMargin } VPNClickableRow { id: btn accessibleName: titleText + ": " + descriptionText Accessible.ignored: rowShouldBeDisabled activeFocusOnTab: true anchors.left: undefined anchors.right: undefined Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter Layout.preferredHeight: Theme.rowHeight onClicked: handleClick() rowShouldBeDisabled: disableRowWhen RowLayout { width: parent.width - (Theme.windowMargin * 2) anchors.centerIn: parent spacing: 0 RowLayout { spacing:8 Rectangle { Layout.preferredWidth: 24 Layout.preferredHeight: 24 Layout.alignment: Qt.AlignLeft | Qt.AlignCenter color: "transparent" Image { id: flag anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left width: imgIsVector? undefined : imgSize sourceSize.width: imgIsVector ? imgSize : undefined fillMode: Image.PreserveAspectFit source: imgSource } } VPNLightLabel { id: serverLocation text: subtitleText Accessible.ignored: true Layout.alignment: Qt.AlignLeft elide: Text.ElideRight } } VPNChevron { id: icon Layout.alignment: Qt.AlignVCenter | Qt.AlignRight } } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNControllerView.qml000066400000000000000000000421461404202232700247100ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtGraphicalEffects 1.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme Rectangle { id: box readonly property alias connectionInfoVisible: connectionInfo.visible function formatSingle(value) { if (value === 0) return "00"; return (value < 10 ? "0" : "") + value; } function formatTime(time) { var secs = time % 60; time = Math.floor(time / 60); var mins = time % 60; time = Math.floor(time / 60); return formatSingle(time) + ":" + formatSingle(mins) + ":" + formatSingle(secs); } state: VPNController.state anchors.top: parent.top anchors.left: parent.left anchors.margins: 16 radius: 8 height: 318 width: parent.width - 32 antialiasing: true states: [ State { name: VPNController.StateInitializing PropertyChanges { target: box color: "#FFFFFF" } PropertyChanges { target: logoTitle //% "VPN is off" text: qsTrId("vpn.controller.deactivated") color: Theme.fontColorDark } PropertyChanges { target: logoSubtitle //% "Turn on to protect your privacy" text: qsTrId("vpn.controller.activationSloagan") color: Theme.fontColor } PropertyChanges { target: settingsImage source: "../resources/settings.svg" } PropertyChanges { target: connectionInfoButton visible: false } PropertyChanges { target: connectionInfo visible: false } PropertyChanges { target: connectionStability visible: false } PropertyChanges { target: animatedRingsWrapper visible: false } }, State { name: VPNController.StateOff PropertyChanges { target: box color: "#FFFFFF" } PropertyChanges { target: logoTitle text: qsTrId("vpn.controller.deactivated") color: Theme.fontColorDark } PropertyChanges { target: logoSubtitle text: qsTrId("vpn.controller.activationSloagan") color: Theme.fontColor opacity: 1 } PropertyChanges { target: settingsImage source: "../resources/settings.svg" } PropertyChanges { target: connectionInfoButton visible: false } PropertyChanges { target: connectionInfo visible: false } PropertyChanges { target: connectionStability visible: false } PropertyChanges { target: animatedRingsWrapper visible: false } }, State { name: VPNController.StateConnecting PropertyChanges { target: box color: "#321C64" } PropertyChanges { target: logoTitle //% "Connecting…" text: qsTrId("vpn.controller.connectingState") color: "#FFFFFF" } PropertyChanges { target: logoSubtitle //% "Masking connection and location" text: qsTrId("vpn.controller.activating") color: "#FFFFFF" opacity: 0.8 } PropertyChanges { target: settingsImage source: "../resources/settings-white.svg" } PropertyChanges { target: settingsButton buttonColorScheme: Theme.iconButtonDarkBackground } PropertyChanges { target: connectionInfoButton visible: false } PropertyChanges { target: connectionInfo visible: false } PropertyChanges { target: connectionStability visible: false } PropertyChanges { target: animatedRingsWrapper visible: false } }, State { name: VPNController.StateConfirming PropertyChanges { target: box color: "#321C64" } PropertyChanges { target: logoTitle text: qsTrId("vpn.controller.connectingState") color: "#FFFFFF" } PropertyChanges { target: logoSubtitle text: VPNController.connectionRetry > 1 ? //% "Attempting to confirm connection" qsTrId("vpn.controller.attemptingToConfirm") : qsTrId("vpn.controller.activating") color: "#FFFFFF" opacity: 0.8 } PropertyChanges { target: settingsImage source: "../resources/settings-white.svg" } PropertyChanges { target: settingsButton buttonColorScheme: Theme.iconButtonDarkBackground } PropertyChanges { target: connectionInfoButton visible: false } PropertyChanges { target: connectionInfo visible: false } PropertyChanges { target: connectionStability visible: false } PropertyChanges { target: animatedRingsWrapper visible: false } }, State { name: VPNController.StateOn PropertyChanges { target: box color: "#321C64" } PropertyChanges { target: logoTitle //% "VPN is on" text: qsTrId("vpn.controller.activated") color: "#FFFFFF" } PropertyChanges { target: logoSubtitle //% "Secure and private" //: This refers to the user’s internet connection. text: qsTrId("vpn.controller.active") + " • " + formatTime(VPNController.time) color: "#FFFFFF" opacity: 0.8 } PropertyChanges { target: settingsButton buttonColorScheme: Theme.iconButtonDarkBackground } PropertyChanges { target: settingsImage source: "../resources/settings-white.svg" } PropertyChanges { target: connectionInfoButton visible: true } PropertyChanges { target: animatedRingsWrapper visible: true opacity: 1 startAnimation: true } }, State { name: VPNController.StateDisconnecting PropertyChanges { target: box color: "#FFFFFF" } PropertyChanges { target: logoTitle //% "Disconnecting…" text: qsTrId("vpn.controller.disconnecting") color: Theme.fontColorDark } PropertyChanges { target: logoSubtitle //% "Unmasking connection and location" text: qsTrId("vpn.controller.deactivating") color: Theme.fontColor opacity: 1 } PropertyChanges { target: settingsImage source: "../resources/settings.svg" } PropertyChanges { target: settingsButton buttonColorScheme: Theme.iconButtonLightBackground } PropertyChanges { target: connectionInfoButton visible: false } PropertyChanges { target: connectionInfo visible: false } PropertyChanges { target: connectionStability visible: false } PropertyChanges { target: animatedRingsWrapper visible: false } }, State { name: VPNController.StateSwitching PropertyChanges { target: box color: "#321C64" } PropertyChanges { target: logoTitle //% "Switching…" text: qsTrId("vpn.controller.switching") color: "#FFFFFF" } PropertyChanges { target: logoSubtitle //% "From %1 to %2" //: Switches from location 1 to location 2 text: qsTrId("vpn.controller.switchingDetail").arg(VPNController.currentCity).arg(VPNController.switchingCity) color: "#FFFFFF" opacity: 0.8 } PropertyChanges { target: settingsImage source: "../resources/settings-white.svg" } PropertyChanges { target: settingsButton buttonColorScheme: Theme.iconButtonDarkBackground } PropertyChanges { target: connectionInfoButton visible: true } PropertyChanges { target: connectionStability visible: false } PropertyChanges { target: animatedRingsWrapper visible: false opacity: 1 startAnimation: false } } ] transitions: [ Transition { to: VPNController.StateConnecting ColorAnimation { target: box property: "color" duration: 200 } ColorAnimation { target: logoTitle property: "color" duration: 200 } ColorAnimation { target: logoSubtitle property: "color" duration: 200 } }, Transition { to: VPNController.StateDisconnecting ColorAnimation { target: box property: "color" duration: 200 } ColorAnimation { target: logoTitle property: "color" duration: 200 } ColorAnimation { target: logoSubtitle property: "color" duration: 200 } } ] VPNAnimatedRings { id: animatedRingsWrapper isCurrentyVisible: stackview.depth === 1 } VPNMainImage { id: logo anchors.horizontalCenter: parent.horizontalCenter y: 48 height: 80 width: 80 } VPNIconButton { id: connectionInfoButton objectName: "connectionInfoButton" onClicked: connectionInfo.open() buttonColorScheme: Theme.iconButtonDarkBackground opacity: connectionInfoButton.visible ? 1 : 0 anchors.top: parent.top anchors.left: parent.left anchors.topMargin: Theme.windowMargin / 2 anchors.leftMargin: Theme.windowMargin / 2 //% "Connection Information" accessibleName: qsTrId("vpn.controller.info") Accessible.ignored: connectionInfoVisible VPNIcon { id: connectionInfoImage source: "../resources/connection-info.svg" anchors.centerIn: connectionInfoButton sourceSize.height: 20 sourceSize.width: 20 visible: connectionInfoButton.visible } Behavior on opacity { NumberAnimation { duration: 300 } } } VPNIconButton { id: settingsButton objectName: "settingsButton" opacity: 1 onClicked: stackview.push("../views/ViewSettings.qml", StackView.Immediate) anchors.top: parent.top anchors.right: parent.right anchors.topMargin: Theme.windowMargin / 2 anchors.rightMargin: Theme.windowMargin / 2 //% "Settings" accessibleName: qsTrId("vpn.main.settings") Accessible.ignored: connectionInfoVisible VPNIcon { id: settingsImage anchors.centerIn: settingsButton } Component { id: aboutUsComponent VPNAboutUs { isSettingsView: false } } Connections { target: VPN function onSettingsNeeded() { while(stackview.depth > 1) { stackview.pop(null, StackView.Immediate); } stackview.push("../views/ViewSettings.qml", StackView.Immediate); } function onAboutNeeded() { while(stackview.depth > 1) { stackview.pop(null, StackView.Immediate); } stackview.push(aboutUsComponent); } } } ColumnLayout { id: col spacing: 0 width: box.width - Theme.windowMargin anchors.horizontalCenter: parent.horizontalCenter function handleMultilineText() { const titleIsWrapped = logoTitle.lineCount > 1; const subTitleIsWrapped = (logoSubtitle.lineCount > 1 || (connectionStability.visible && connectionStability.numGridColumns === 1)); if (titleIsWrapped && subTitleIsWrapped) { topTextMargin.Layout.preferredHeight = topTextMargin._preferredHeight - 12 bottomTextMargin.Layout.preferredHeight = 6; return; } if (subTitleIsWrapped) { topTextMargin.Layout.preferredHeight = topTextMargin._preferredHeight - 6 bottomTextMargin.Layout.preferredHeight = 2; return; } if (titleIsWrapped) { topTextMargin.Layout.preferredHeight = topTextMargin._preferredHeight - 4 bottomTextMargin.Layout.preferredHeight = 8; return; } bottomTextMargin.Layout.preferredHeight = 8; topTextMargin.Layout.preferredHeight = topTextMargin._preferredHeight; } VPNVerticalSpacer { property var _preferredHeight: logo.y + logo.height + 24 id: topTextMargin Layout.preferredHeight: _preferredHeight } ColumnLayout { Layout.minimumHeight: 32 Layout.fillWidth: true spacing: 0 VPNHeadline { id: logoTitle objectName: "controllerTitle" Layout.alignment: Qt.AlignCenter Layout.fillWidth: true Layout.minimumHeight: 32 lineHeight: 22 font.pixelSize: 22 Accessible.ignored: connectionInfoVisible Accessible.description: logoSubtitle.text onPaintedHeightChanged: col.handleMultilineText() } } VPNVerticalSpacer { id: bottomTextMargin Layout.preferredHeight: 8 Layout.fillWidth: true } VPNInterLabel { id: logoSubtitle objectName: "controllerSubTitle" lineHeight: Theme.controllerInterLineHeight Layout.preferredWidth: parent.width Accessible.ignored: true onPaintedHeightChanged: col.handleMultilineText() } VPNConnectionStability { id: connectionStability visible: false Accessible.ignored: connectionInfoVisible || !visible } } VPNToggle { id: toggle objectName: "controllerToggle" anchors.bottom: parent.bottom anchors.bottomMargin: 48 anchors.horizontalCenterOffset: 0 anchors.horizontalCenter: parent.horizontalCenter Accessible.ignored: connectionInfoVisible } VPNConnectionInfo { id: connectionInfo Behavior on opacity { NumberAnimation { target: connectionInfo property: "opacity" duration: 200 } } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNDeviceListItem.qml000066400000000000000000000152371404202232700246050ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme Item { id: device Layout.preferredWidth: content.width Layout.preferredHeight: deviceRow.height Rectangle { color: Theme.greyHovered opacity: device.focus || iconButton.focus ? 1 : 0 anchors.fill: device anchors.topMargin: -Theme.windowMargin / 1.5 anchors.bottomMargin: anchors.topMargin } activeFocusOnTab: true onFocusChanged: if (focus && typeof(vpnFlickable.ensureVisible) !== "undefined") vpnFlickable.ensureVisible(device) RowLayout { id: deviceRow property var deviceName: name spacing: 0 //% "%1 %2" //: Example: "deviceName deviceDescription" Accessible.name: qsTrId("vpn.devices.deviceAccessibleName").arg(name).arg(deviceDesc.text) Accessible.role: Accessible.ListItem Accessible.ignored: root.isModalDialogOpened focus: true width: device.width Layout.alignment: Qt.AlignHCenter SequentialAnimation { id: deviceRemovalTransition ParallelAnimation { PropertyAnimation { target: iconButton property: "opacity" from: 1 to: 0 duration: 100 } PropertyAnimation { targets: [deviceName, deviceDesc, deviceIcon] property: "opacity" from: 1 to: 0.6 duration: 100 } } PropertyAction { target: iconButton property: "iconHeightWidth" value: 20 } PropertyAction { target: iconButton property: "iconSource" value: "../resources/spinner.svg" } ParallelAnimation { PropertyAnimation { target: iconButton property: "opacity" from: 0 to: 1 duration: 300 } PropertyAction { target: iconButton property: "startRotation" value: true } } } Connections { function onDeviceRemoving(devName) { if (name === devName) deviceRemovalTransition.start(); } target: VPN } VPNIcon { id: deviceIcon source: "../resources/devices.svg" fillMode: Image.PreserveAspectFit Layout.leftMargin: Theme.windowMargin Layout.rightMargin: Theme.windowMargin Layout.alignment: Qt.AlignTop | Qt.AlignLeft Layout.topMargin: Theme.windowMargin / 2 } ColumnLayout { id: deviceInfo Layout.alignment: Qt.AlignLeft Layout.topMargin: Theme.windowMargin / 2 Layout.preferredWidth: deviceRow.Layout.preferredWidth - deviceIcon.Layout.rightMargin - deviceIcon.sourceSize.width - iconButton.width - Theme.windowMargin spacing: 0 VPNInterLabel { id: deviceName text: name color: Theme.fontColorDark elide: Text.ElideRight Layout.alignment: Qt.AlignLeft horizontalAlignment: Text.AlignLeft Layout.preferredWidth: deviceInfo.Layout.preferredWidth - Theme.windowMargin / 2 Layout.fillWidth: true Accessible.ignored: root.isModalDialogOpened } VPNTextBlock { id: deviceDesc function deviceSubtitle() { if (currentOne) { //% "Current Device" return qsTrId("vpn.devices.currentDevice"); } const diff = (Date.now() - createdAt.valueOf()) / 1000; if (diff < 3600) { //% "Added less than an hour ago" return qsTrId("vpn.devices.addedltHour"); } if (diff < 86400) { //: %1 is the number of hours. //% "Added a few hours ago (%1)" return qsTrId("vpn.devices.addedXhoursAgo").arg(Math.floor(diff / 3600)); } //% "Added %1 days ago" //: %1 is the number of days. //: Note: there is currently no support for proper plurals return qsTrId("vpn.devices.addedXdaysAgo").arg(Math.floor(diff / 86400)); } text: deviceSubtitle() color: currentOne ? Theme.blue : Theme.fontColor Accessible.ignored: root.isModalDialogOpened } } VPNIconButton { id: iconButton property var iconSource: "../resources/delete.svg" property real iconHeightWidth: 22 property bool startRotation: false buttonColorScheme: Theme.removeDeviceBtn visible: !currentOne Layout.alignment: Qt.AlignTop | Qt.AlignRight Layout.rightMargin: Theme.windowMargin / 2 Layout.preferredHeight: Theme.rowHeight Layout.preferredWidth: Theme.rowHeight onClicked: removePopup.initializeAndOpen(name, index) //: Label used for accessibility on the button to remove a device. %1 is the name of the device. //% "Remove %1" accessibleName: qsTrId("vpn.devices.removeA11Y").arg(deviceRow.deviceName) // Only allow focus within the current item in the list. Accessible.ignored: root.isModalDialogOpened VPNIcon { source: iconButton.iconSource anchors.centerIn: iconButton sourceSize.height: iconButton.iconHeightWidth sourceSize.width: iconButton.iconHeightWidth rotation: iconButton.startRotation ? 360 : 0 Behavior on rotation { PropertyAnimation { duration: 5000 loops: Animation.Infinite } } } } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNDevicesListHeader.qml000066400000000000000000000042141404202232700252530ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme Item { id: listHeader property var pendingDeviceRemoval: false width: deviceList.width states: [ State { when: vpnFlickable.state === "deviceLimit" || vpnFlickable.wasmShowMaxDeviceWarning === true PropertyChanges { target: listHeader height: spacer.height * 2 + vpnPanel.height opacity: 1 } }, State { when: vpnFlickable.state === "active" PropertyChanges { target: listHeader height: 8 opacity: 0 pendingDeviceRemoval: false } } ] transitions: [ Transition { to: "deviceLimitNotReached" SequentialAnimation { PropertyAnimation { property: "opacity" duration: 200 } PropertyAnimation { property: "height" duration: 300 easing.type: Easing.Linear } } } ] Rectangle { id: spacer anchors.top: listHeader.top height: Theme.windowMargin * 2 width: listHeader.width color: "transparent" } VPNPanel { id: vpnPanel anchors.top: spacer.bottom logoSize: 80 logo: "../resources/devicesLimit.svg" //% "Remove a device" logoTitle: qsTrId("vpn.devices.doDeviceRemoval") //% "You’ve reached the device limit. To turn on the VPN on this device, you’ll need to remove one." logoSubtitle: qsTrId("vpn.devices.maxDevicesHeader") } Rectangle { id: bottomSpacer anchors.top: vpnPanel.bottom height: Theme.windowMargin * 2 width: listHeader.width color: "transparent" } } mozilla-vpn-client-2.2.0/src/ui/components/VPNDropShadow.qml000066400000000000000000000031471404202232700240020ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.0 import QtGraphicalEffects 1.0 import Mozilla.VPN 1.0 DropShadow { id: dropShadow horizontalOffset: 1 verticalOffset: 1 radius: 5.5 color: "#0C0C0D" opacity: .1 state: VPNController.state states: [ State { name: VPNController.StateConnecting PropertyChanges { target: dropShadow opacity: .3 } }, State { name: VPNController.StateConfirming PropertyChanges { target: dropShadow opacity: .3 } }, State { name: VPNController.StateOn PropertyChanges { target: dropShadow opacity: .3 } }, State { name: VPNController.StateSwitching PropertyChanges { target: dropShadow opacity: .3 } }, State { name: VPNController.StateDisconnecting PropertyChanges { target:dropShadow opacity: .1 } }, State { name: VPNController.StateOff PropertyChanges { target: dropShadow opacity: .1 } } ] Behavior on opacity { PropertyAnimation { duration: 200 } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNExpandableAppList.qml000066400000000000000000000164101404202232700252650ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import QtGraphicalEffects 1.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme ColumnLayout { id: appListContainer property var listModel: undefined property var header: "" property var description: "" property var actionText: "" property var onAction: ()=>{} property var isEnabled: vpnFlickable.vpnIsOff property var isListVisible : true && applist.count > 0 property var isActionEnabled: isEnabled && applist.count > 0 property var dividerVisible: false opacity: isEnabled && applist.count > 0 ? 1 : 0.5 anchors.horizontalCenter: parent.horizontalCenter visible: VPNSettings.protectSelectedApps width: parent.width spacing: 0 Behavior on y { PropertyAnimation { duration: 200 } } VPNClickableRow { id: appRow Keys.onReleased: if (event.key === Qt.Key_Space) handleKeyClick() handleMouseClick: function() { isListVisible = !isListVisible && applist.count > 0; } handleKeyClick: function() { isListVisible = !isListVisible && applist.count > 0; } canGrowVertical: true accessibleName: description Layout.preferredWidth: parent.width - Theme.windowMargin Layout.alignment: Qt.AlignHCenter Layout.minimumHeight: Theme.rowHeight * 1.5 enabled: vpnFlickable.vpnIsOff && applist.count > 0 anchors.left: undefined anchors.right: undefined anchors.rightMargin: undefined anchors.leftMargin: undefined ColumnLayout { id: appRowHeader width: appRow.Layout.preferredWidth anchors.verticalCenter: parent.verticalCenter spacing: 0 RowLayout { spacing: 0 Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter Behavior on y { PropertyAnimation { duration: 100 } } VPNIcon { id: toggleArrow Layout.leftMargin: 6 Layout.rightMargin: 14 Layout.alignment: Qt.AlignVCenter source: "../resources/arrow-toggle.svg" transformOrigin: Image.Center smooth: true rotation: isListVisible ? 0 :-90 } RowLayout { Layout.alignment: Qt.AlignLeft spacing: 0 VPNBoldLabel { id: label text: header Accessible.role: Accessible.Heading color: Theme.fontColorDark horizontalAlignment: Text.AlignLeft Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter } VPNTextBlock{ text: " (%0)".arg(applist.count) verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignLeft Layout.alignment: Qt.AlignLeft Layout.fillWidth: true } } } VPNVerticalSpacer { Layout.preferredHeight: 2 visible: isListVisible } VPNTextBlock { id: enabledAppSubtext text: description // "These apps will (not) use vpn.." Layout.fillWidth: true Layout.maximumWidth: parent.width width: undefined visible: isListVisible Layout.leftMargin: 44 wrapMode: Text.Wrap } } } VPNLinkButton{ Layout.alignment: Qt.AlignRight fontSize: Theme.fontSizeSmall // (Un)protect-All Button labelText: actionText onClicked: { onAction(); isListVisible=false } enabled: isActionEnabled visible: isListVisible } VPNList { id: applist model: listModel Layout.fillWidth: true spacing: 26 listName: header Layout.preferredHeight: contentItem.childrenRect.height Layout.topMargin: 4 visible: isListVisible && count > 0 PropertyAnimation on opacity { duration: 200 } removeDisplaced: Transition { NumberAnimation { properties: "x,y" duration: 200 } } remove: Transition { PropertyAnimation { property: "opacity" from: 1 to: 0 duration: 200 } } addDisplaced: Transition { NumberAnimation { properties: "x,y" duration: 200 } } add: Transition { PropertyAnimation { property: "opacity" from: 0 to: 1 duration: 200 } } delegate: RowLayout { width: parent.width - Theme.windowMargin anchors.rightMargin: 0 anchors.right: parent.right spacing: Theme.windowMargin Image { source: "image://app/"+appID visible: appID !== "" sourceSize.width: Theme.windowMargin sourceSize.height: Theme.windowMargin Layout.alignment: Qt.AlignTop Layout.leftMargin: 36 Layout.topMargin: 4 asynchronous: true fillMode: Image.PreserveAspectFit } ColumnLayout { id: labelWrapper Layout.alignment: Qt.AlignTop spacing: 4 VPNInterLabel { Layout.alignment: Qt.AlignLeft | Qt.AlignTop Layout.fillWidth: true text: appName color: Theme.fontColorDark horizontalAlignment: Text.AlignLeft } VPNTextBlock { Layout.fillWidth: true text: appID visible: !!text.length } } VPNCheckBox { Layout.alignment: Qt.AlignTop Layout.topMargin: 6 onClicked: VPNAppPermissions.flip(appID) checked: appIsEnabled Layout.rightMargin: 4 enabled: appListContainer.isEnabled } } } VPNVerticalSpacer { Layout.preferredHeight: Theme.windowMargin * 3 Layout.fillWidth: true visible: dividerVisible && isListVisible && applist.count > 0 Rectangle { height: 1 width: parent.width - Theme.windowMargin * 2 color: "#e7e7e7" visible: isListVisible && applist.count > 0 anchors.centerIn: parent } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNExternalLinkListItem.qml000066400000000000000000000015451404202232700260030ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme // VPNExternalLinkListItem VPNClickableRow { property alias title: title.text property var iconSource: "../resources/externalLink.svg" backgroundColor: Theme.clickableRowBlue RowLayout { anchors.fill: parent anchors.leftMargin: Theme.windowMargin / 2 anchors.rightMargin: Theme.windowMargin / 2 VPNBoldLabel { id: title } Item { Layout.fillWidth: true } VPNIcon { source: iconSource } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNFlickable.qml000066400000000000000000000036651404202232700236110ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import "../themes/themes.js" as Theme Flickable { id: vpnFlickable property var flickContentHeight property var windowHeightExceedsContentHeight: (window.safeContentHeight > flickContentHeight) property bool hideScollBarOnStackTransition: false function ensureVisible(item) { if (windowHeightExceedsContentHeight) { return; } let yPosition = item.mapToItem(contentItem, 0, 0).y; let ext = item.height + yPosition; if (yPosition < contentY || yPosition > contentY + height || ext < contentY || ext > contentY + height) { let destinationY = Math.max(0, Math.min(yPosition - height + item.height, contentHeight - height)); ensureVisAnimation.to = destinationY; ensureVisAnimation.start(); } } contentHeight: Math.max(window.safeContentHeight, flickContentHeight) boundsBehavior: Flickable.StopAtBounds opacity: 0 Component.onCompleted: { opacity = 1; if (Qt.platform.os === "windows") { maximumFlickVelocity = 700; } } NumberAnimation on contentY { id: ensureVisAnimation duration: 300 easing.type: Easing.OutQuad } PropertyAnimation on opacity { duration: 200 } ScrollBar.vertical: ScrollBar { policy: windowHeightExceedsContentHeight ? ScrollBar.AlwaysOff : ScrollBar.AlwaysOn Accessible.ignored: true opacity: hideScollBarOnStackTransition && (vpnFlickable.StackView.status !== StackView.Active) ? 0 : 1 Behavior on opacity { PropertyAnimation { duration: 100 } } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNFocusBorder.qml000066400000000000000000000010241404202232700241350ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import "../themes/themes.js" as Theme Rectangle { id: focusInnerBorder color: "transparent" anchors.fill: parent radius: parent.radius border.width: Theme.focusBorderWidth border.color: colorScheme.focusBorder opacity: itemToFocus.activeFocus ? 1 : 0 antialiasing: true } mozilla-vpn-client-2.2.0/src/ui/components/VPNFocusOutline.qml000066400000000000000000000011731404202232700243440ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.0 import "../themes/themes.js" as Theme Rectangle { property variant focusedComponent property var focusColorScheme: Theme.blueButton property var setMargins: -3 color: focusColorScheme.focusOutline antialiasing: true anchors.fill: parent anchors.margins: setMargins radius: parent.radius + ((setMargins * -1) - 1) opacity: focusedComponent.activeFocus ? 1: 0 z: -1 } mozilla-vpn-client-2.2.0/src/ui/components/VPNFooterLink.qml000066400000000000000000000006711404202232700240030ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import "../themes/themes.js" as Theme VPNLinkButton { anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.bottomMargin: Math.min(window.safeContentHeight * .08, 60) } mozilla-vpn-client-2.2.0/src/ui/components/VPNGetHelp.qml000066400000000000000000000026251404202232700232600ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme Item { property alias isSettingsView: menu.isSettingsView VPNMenu { id: menu objectName: "getHelpBack" //% "Get Help" title: qsTrId("vpn.main.getHelp") isSettingsView: true } VPNList { objectName: "getHelpBackList" height: parent.height - menu.height width: parent.width anchors.top: menu.bottom spacing: Theme.listSpacing anchors.topMargin: Theme.windowMargin listName: menu.title model: VPNHelpModel delegate: VPNExternalLinkListItem { objectName: "getHelpBackList-" + id title: name accessibleName: name iconSource: externalLink ? "../resources/externalLink.svg" : "../resources/chevron.svg" backgroundColor: externalLink ? Theme.clickableRowBlue : Theme.iconButtonLightBackground onClicked: { VPNHelpModel.open(id) } } ScrollBar.vertical: ScrollBar { Accessible.ignored: true } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNGraphLegendMarker.qml000066400000000000000000000057511404202232700252550ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import "../themes/themes.js" as Theme Row { property var markerLabel property var rectColor property var markerData function computeRange() { if (markerData < 1024) { //% "B/s" //: Bytes per second return qsTrId("vpn.connectionInfo.Bps"); } if (markerData < 1048576 /* 1024^2 */) { //% "kB/s" //: Kilobytes per second return qsTrId("vpn.connectionInfo.kBps"); } if (markerData < 1073741824 /* 1024^3 */) { //% "MB/s" //: Megabytes per second return qsTrId("vpn.connectioInfo.mBps"); } if (markerData < 1099511627776 /* 1024^4 */) { //% "GB/s" //: Gigabytes per second return qsTrId("vpn.connectioInfo.gBps"); } //% "TB/s" //: Terabytes per second return qsTrId("vpn.connectionInfo.tBps"); } function roundValue(value) { return Math.round(value * 100) / 100; } function computeValue() { if (markerData < 1024) return roundValue(markerData); if (markerData < 1048576 /* 1024^2 */) return roundValue(markerData / 1024); if (markerData < 1073741824 /* 1024^3 */) return roundValue(markerData / 1048576 /* 1024^2 */); if (markerData < 1099511627776 /* 1024^4 */) return roundValue(markerData / 1073741824 /* 1024^3 */); return roundValue(markerData / 1099511627776 /* 1024^4 */); } Accessible.focusable: true Accessible.role: Accessible.StaticText //% "%1: %2 %3" //: Used as accessibility description for the connection info: //: %1 is the localized label for “Upload” or “Download”, %2 is the speed //: value, %3 is the localized unit. Example: “Upload: 10 Mbps”. Accessible.name: qsTrId("vpn.connectionInfo.accessibleName") .arg(label.text) .arg(value.text) .arg(range.text) spacing: 12 Rectangle { height: 12 width: 12 radius: 2 color: rectColor anchors.top: parent.top anchors.topMargin: 22 } Column { spacing: 6 Text { id: range font.pixelSize: 10 height: 16 text: computeRange() font.family: Theme.fontInterFamily color: "#FFFFFF" } Text { id: label font.pixelSize: 14 text: markerLabel font.family: Theme.fontBoldFamily color: "#FFFFFF" } Text { id: value font.pixelSize: 16 text: computeValue() font.family: Theme.fontInterFamily color: "#FFFFFF" } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNGreyLink.qml000066400000000000000000000007271404202232700234550ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme VPNLinkButton { fontSize: Theme.fontSizeSmallest Layout.preferredHeight: 18 linkColor: Theme.greyLink buttonPadding: Theme.hSpacing / 2 } mozilla-vpn-client-2.2.0/src/ui/components/VPNHeaderLink.qml000066400000000000000000000006171404202232700237350ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 //VPNHeaderLink VPNLinkButton { anchors.top: parent.top anchors.right: parent.right anchors.topMargin: 12 anchors.rightMargin: 12 horizontalPadding: 4 } mozilla-vpn-client-2.2.0/src/ui/components/VPNHeadline.qml000066400000000000000000000016101404202232700234320ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtGraphicalEffects 1.14 import QtQuick.Layouts 1.14 import "../themes/themes.js" as Theme //VPNHeadline Text { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap width: Theme.maxTextWidth color: Theme.fontColorDark font.family: Theme.fontFamily font.pixelSize: 22 lineHeightMode: Text.FixedHeight lineHeight: 32 Layout.alignment: Qt.AlignHCenter Accessible.role: Accessible.StaticText Accessible.name: text Component.onCompleted: { if (paintedWidth > Theme.maxTextWidth) { fontSizeMode = Text.Fit minimumPixelSize = 16 } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNIcon.qml000066400000000000000000000006221404202232700226130ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Layouts 1.14 import "../themes/themes.js" as Theme // VPNIcon Image { sourceSize.height: 24 sourceSize.width: 24 fillMode: Image.PreserveAspectFit } mozilla-vpn-client-2.2.0/src/ui/components/VPNIconAndLabel.qml000066400000000000000000000016761404202232700242100ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtGraphicalEffects 1.14 import QtQuick.Layouts 1.14 import "../themes/themes.js" as Theme // VPNIconAndLabel RowLayout { property alias icon: icon.source property alias title: title.text spacing: 0 height: parent.height VPNIcon { id: icon Layout.alignment: Qt.AlignHCenter } VPNBoldLabel { id: title Layout.leftMargin: 14 Layout.alignment: Qt.AlignVCenter // VPNIconAndLabel is only used inside a VPNClickableRow // which acts as atomic interactive control thus we want // to hide its content (such as VPNIconAndLabel) from // assistive technology. Accessible.ignored: true } } mozilla-vpn-client-2.2.0/src/ui/components/VPNIconButton.qml000066400000000000000000000014751404202232700240160ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme VPNButtonBase { id: iconButton property var accessibleName property var buttonColorScheme: Theme.iconButtonLightBackground height: Theme.rowHeight width: Theme.rowHeight Accessible.name: accessibleName Component.onCompleted: state = uiState.stateDefault VPNToolTip { text: accessibleName } VPNMouseArea { id: mouseArea } VPNUIStates { itemToFocus: iconButton colorScheme: buttonColorScheme } } mozilla-vpn-client-2.2.0/src/ui/components/VPNInterLabel.qml000066400000000000000000000012001404202232700237350ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme // VPNInterLabel Text { horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter font.pixelSize: Theme.fontSize font.family: Theme.fontInterFamily lineHeightMode: Text.FixedHeight lineHeight: Theme.labelLineHeight wrapMode: Text.Wrap Accessible.role: Accessible.StaticText Accessible.name: text } mozilla-vpn-client-2.2.0/src/ui/components/VPNLightLabel.qml000066400000000000000000000006401404202232700237320ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import "../themes/themes.js" as Theme // VPNLightLabel Label { font.pixelSize: Theme.fontSize font.family: Theme.fontFamily color: Theme.fontColor } mozilla-vpn-client-2.2.0/src/ui/components/VPNLinkButton.qml000066400000000000000000000050051404202232700240140ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import "../themes/themes.js" as Theme VPNButtonBase { id: root property var labelText property variant fontName: Theme.fontInterFamily property var baseColor: Theme.linkButton property var linkColor: Theme.blueButton property var fontSize: Theme.fontSize property var textAlignment: Text.AlignHCenter property var buttonPadding: Theme.hSpacing radius: 4 onFocusChanged: if (focus && typeof(ensureVisible) !== "undefined") ensureVisible(root) horizontalPadding: buttonPadding Keys.onReleased: { if (loaderVisible) { return } if (event.key === Qt.Key_Return || event.key === Qt.Key_Space) { root.clicked(); state = uiState.stateDefault; } } Accessible.name: labelText Component.onCompleted: state = uiState.stateDefault; states: [ State { name: uiState.stateHovered PropertyChanges { target: label color: root.linkColor.buttonHovered } }, State { name: uiState.statePressed PropertyChanges { target: label color: root.linkColor.buttonPressed } }, State { name: uiState.stateDefault PropertyChanges { target: label color: root.linkColor.defaultColor } } ] VPNUIStates { colorScheme: root.baseColor itemToFocus: root VPNFocusBorder { border.color: root.linkColor.defaultColor opacity: root.activeFocus ? 1 : 0 } } VPNButtonLoader { id: loader state: loaderVisible ? "active" : "inactive" } VPNMouseArea { hoverEnabled: loaderVisible === false } background: Rectangle { id: backgroundRect color: "transparent" } contentItem: Label { id: label text: labelText horizontalAlignment: textAlignment verticalAlignment: Text.AlignVCenter font.pixelSize: fontSize font.family: fontName Behavior on color { ColorAnimation { duration: 200 } } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNList.qml000066400000000000000000000013131404202232700226340ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.5 import "../themes/themes.js" as Theme ListView { id: list property var listName Accessible.role: Accessible.List Accessible.name: listName activeFocusOnTab: true interactive: false // disable scrolling on list since the entire window is scrollable boundsBehavior: Flickable.StopAtBounds highlightFollowsCurrentItem: true Keys.onDownPressed: list.incrementCurrentIndex() Keys.onUpPressed: list.decrementCurrentIndex() } mozilla-vpn-client-2.2.0/src/ui/components/VPNLoader.qml000066400000000000000000000042041404202232700231310ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtGraphicalEffects 1.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme import "../components" Item { id: root property var headlineText property var footerLinkIsVisible: true Component.onCompleted: fade.start() height: window.safeContentHeight width: parent.width VPNHeadline { id: headline anchors.top: root.top anchors.topMargin: root.height * 0.08 anchors.horizontalCenter: root.horizontalCenter width: Math.min(Theme.maxTextWidth, root.width * .85) text: headlineText } Image { id: spinner anchors.horizontalCenter: root.horizontalCenter anchors.verticalCenter: root.verticalCenter sourceSize.height: 80 fillMode: Image.PreserveAspectFit source: "../resources/spinner.svg" ParallelAnimation { id: startSpinning running: true PropertyAnimation { target: spinner property: "opacity" from: 0 to: 1 duration: 300 } PropertyAnimation { target: spinner property: "scale" from: 0.7 to: 1 duration: 300 } PropertyAnimation { target: spinner property: "rotation" from: 0 to: 360 duration: 8000 loops: Animation.Infinite } } } VPNFooterLink { id: footerLink objectName: "cancelFooterLink" visible: footerLinkIsVisible //% "Cancel" labelText: qsTrId("vpn.authenticating.cancel") onClicked: VPN.cancelAuthentication() } PropertyAnimation on opacity { id: fade from: 0 to: 1 duration: 1000 } } mozilla-vpn-client-2.2.0/src/ui/components/VPNLogsButton.qml000066400000000000000000000031021404202232700240170ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // IMPORTANT: this file is used only for mobile builds. import QtQuick 2.5 import QtQuick.Layouts 1.14 import "../themes/themes.js" as Theme VPNButtonBase { id: base property var buttonText property var iconSource Layout.alignment: Qt.AlignCenter Layout.preferredHeight: parent.height Layout.fillWidth: true radius: 0 Accessible.name: buttonText VPNUIStates { colorScheme: Theme.iconButtonLightBackground radius: 0 } VPNMouseArea { id: mouseArea } contentItem: Item { anchors.fill: parent ColumnLayout { spacing: 0 anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter Image { source: iconSource sourceSize.height: 24 sourceSize.width: 24 Layout.alignment: Qt.AlignHCenter } Text { text: buttonText color: Theme.fontColor font.pixelSize: 11 verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter Layout.alignment: Qt.AlignHCenter font.family: Theme.fontInterFamily lineHeight: 18 lineHeightMode: Text.FixedHeight } } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNMainImage.qml000066400000000000000000000250441404202232700235570ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme Rectangle { id: logo property var showVPNOnIcon: false color: "transparent" opacity: 1 onStateChanged: gradientGlobe.requestPaint() state: VPNController.state states: [ State { name: VPNController.StateConnecting PropertyChanges { target: logo opacity: 0.6 showVPNOnIcon: true } PropertyChanges { target: insetCircle color: "#3FE1B0" } PropertyChanges { target: insetIcon source: "../resources/shield-on.svg" opacity: 1 } }, State { name: VPNController.StateConfirming PropertyChanges { target: logo opacity: 0.6 showVPNOnIcon: true } PropertyChanges { target: insetCircle color: "#3FE1B0" } PropertyChanges { target: insetIcon source: "../resources/shield-on.svg" opacity: 1 } }, State { name: VPNController.StateDisconnecting PropertyChanges { target: logo opacity: 0.55 showVPNOnIcon: false } PropertyChanges { target: insetCircle color: "#FF4F5E" } PropertyChanges { target: insetIcon source: "../resources/shield-off.svg" opacity: 1 } }, State { name: VPNController.StateSwitching PropertyChanges { target: logo opacity: 0.55 showVPNOnIcon: true } PropertyChanges { target: insetCircle color: "#3FE1B0" } }, State { name: VPNController.StateOff PropertyChanges { target: insetCircle color: "#FF4F5E" } PropertyChanges { target: insetIcon source: "../resources/shield-off.svg" opacity: 1 } }, State { name: VPNController.StateOn PropertyChanges { target: logo showVPNOnIcon: true } PropertyChanges { target: insetCircle color: "#3FE1B0" } PropertyChanges { target: insetIcon source: "../resources/shield-on.svg" opacity: 1 } }, State { name: VPNController.StateInitializing PropertyChanges { target: logo showVPNOnIcon: false opacity: 0.55 } PropertyChanges { target: insetCircle color: "#FF4F5E" } PropertyChanges { target: insetIcon source: "../resources/shield-off.svg" opacity: 1 } } ] transitions: [ Transition { to: VPNController.StateConnecting ParallelAnimation { PropertyAnimation { target: logo property: "opacity" duration: 200 } PropertyAnimation { target: insetCircle property: "scale" to: 0.9 duration: 200 easing.type: Easing.Linear } } }, Transition { to: VPNController.StateConfirming ParallelAnimation { PropertyAnimation { target: insetIcon property: "opacity" duration: 100 } } }, Transition { to: VPNController.StateDisconnecting ParallelAnimation { PropertyAnimation { target: logo property: "opacity" duration: 200 } PropertyAnimation { target: insetCircle property: "scale" to: 0.9 duration: 150 easing.type: Easing.Linear } } }, Transition { to: VPNController.StateOff ParallelAnimation { PropertyAnimation { target: logo property: "opacity" duration: 100 } PropertyAnimation { target: insetCircle property: "scale" to: 1 duration: 100 easing.type: Easing.InOutBounce } } }, Transition { to: VPNController.StateSwitching ParallelAnimation { PropertyAnimation { target: insetCircle property: "scale" to: 0.9 duration: 200 easing.type: Easing.Linear } PropertyAnimation { target: logo property: "opacity" duration: 500 } PropertyAnimation { target: insetIcon property: "opacity" to: 0 duration: 50 } } }, Transition { to: VPNController.StateOn ParallelAnimation { PropertyAnimation { target: insetIcon property: "opacity" to: 1 duration: 200 } PropertyAnimation { target: logo property: "opacity" duration: 300 } PropertyAnimation { target: insetCircle property: "color" duration: 100 } PropertyAnimation { target: insetCircle property: "scale" to: 1 duration: 150 easing.type: Easing.InOutBounce } } } ] Rectangle { // green or red circle in upper right hand area of // gradientGlobe id: insetCircle height: 32 width: 32 radius: 16 x: 43 y: 5 antialiasing: true smooth: true Image { id: insetIcon sourceSize.height: 32 sourceSize.width: 32 anchors.centerIn: insetCircle opacity: 1 } Image { id: switchingIcon anchors.centerIn: insetCircle sourceSize.height: 32 sourceSize.width: 32 source: "../resources/switching.svg" opacity: VPNController.state === VPNController.StateSwitching ? 1 : 0 PropertyAnimation { target: switchingIcon property: "rotation" from: 0 to: 360 loops: Animation.Infinite duration: 8000 running: true } } } Canvas { id: gradientGlobe anchors.fill: logo contextType: "2d" antialiasing: true smooth: true onPaint: { if (!context) { return; } context.reset(); // background fill let gradient = context.createRadialGradient(90, 0, 21, 90, 0, 100); if (!logo.showVPNOnIcon) { gradient.addColorStop(0, "#9D62FC"); gradient.addColorStop(1, "#FD3296"); } else { gradient.addColorStop(0, "#B833E1"); gradient.addColorStop(0.25, "#9059FF"); gradient.addColorStop(0.75, "#5B6DF8"); gradient.addColorStop(1, "#0090ED"); } // globe icon outline context.beginPath(); context.path = "M38.4944 25.5505H27.8068C30.3243 17.2 34.8938 11.5 40 11.5C40.0882 11.5 40.1762 11.5017 40.264 11.5051C42.0749 7.93887 44.874 4.95904 48.3009 2.92608C45.5972 2.31825 42.8148 2.00316 40 2C32.4843 2 25.1374 4.22866 18.8883 8.40415C12.6393 12.5796 7.76872 18.5144 4.89259 25.458C2.01646 32.4016 1.26394 40.0421 2.73018 47.4134C4.19641 54.7847 7.81556 61.5557 13.13 66.87C18.4444 72.1844 25.2153 75.8036 32.5866 77.2698C39.9579 78.7361 47.5984 77.9835 54.542 75.1074C61.4856 72.2313 67.4204 67.3607 71.5958 61.1117C75.7713 54.8626 78 47.5157 78 40C77.9968 37.1852 77.6818 34.4028 77.0739 31.6991C75.0416 35.1249 72.063 37.9234 68.4982 39.7343C68.4992 39.8228 68.4998 39.9114 68.5 40C68.4933 43.1673 67.9555 46.3111 66.9088 49.3005H58.259C58.6472 46.8835 58.8834 44.4452 58.9663 42C57.3373 41.9974 55.7518 41.8094 54.2298 41.4559C54.157 44.0865 53.8876 46.7087 53.4235 49.3005H26.5765C25.4725 43.1493 25.4725 36.8507 26.5765 30.6995H40.3694C39.5317 29.0938 38.8955 27.3663 38.4944 25.5505ZM27.8068 54.4495C30.3243 62.8 34.8938 68.5 40 68.5C45.1063 68.5 49.6758 62.8 52.198 54.4495H27.8068ZM13.0913 30.6995C12.0446 33.6889 11.5067 36.8327 11.5 40C11.5067 43.1673 12.0446 46.3111 13.0913 49.3005H21.741C20.7526 43.1395 20.7526 36.8604 21.741 30.6995H13.0913ZM22.8478 25.5505C23.8606 21.5329 25.5487 17.7166 27.84 14.2645C22.6866 16.7113 18.3813 20.6411 15.4758 25.5505H22.8478ZM22.8478 54.4495H15.49H15.4758C18.3813 59.3588 22.6866 63.2887 27.84 65.7355C25.5487 62.2834 23.8606 58.4671 22.8478 54.4495ZM52.1808 65.7256C57.3302 63.2805 61.6331 59.3544 64.5385 54.4495H57.1665C56.1547 58.4633 54.4688 62.2761 52.1808 65.7256Z"; context.closePath(); // globe icon fill rules context.fillStyle = gradient; context.clip("evenodd"); context.fill("evenodd"); } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNMenu.qml000066400000000000000000000045331404202232700226340ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import "../themes/themes.js" as Theme Item { id: menuBar property alias objectName: iconButton.objectName property alias title: title.text property alias rightTitle: rightTitle.text property bool isSettingsView: false property bool isMainView: false property bool accessibleIgnored: false property bool btnDisabled: false property alias forceFocus: iconButton.focus width: parent.width height: 56 // Ensure that menu is on top of possible scrollable // content. z: 2 MouseArea { // Prevent mouse events from passing through to // underlying elements anchors.fill: menuBar preventStealing: true propagateComposedEvents: false hoverEnabled: true } Rectangle { id: menuBackground color: Theme.bgColor y: 0 width: parent.width height: 55 } VPNIconButton { id: iconButton onClicked: isMainView ? mainStackView.pop() : (isSettingsView ? settingsStackView.pop() : stackview.pop()) anchors.top: parent.top anchors.left: parent.left anchors.topMargin: Theme.windowMargin / 2 anchors.leftMargin: Theme.windowMargin / 2 //% "Back" //: Go back accessibleName: qsTrId("vpn.main.back") Accessible.ignored: accessibleIgnored enabled: !btnDisabled opacity: enabled ? 1 : .4 Image { id: backImage source: "../resources/back.svg" sourceSize.width: Theme.iconSize fillMode: Image.PreserveAspectFit anchors.centerIn: iconButton } } VPNBoldLabel { id: title anchors.top: menuBar.top anchors.centerIn: menuBar Accessible.ignored: accessibleIgnored } VPNLightLabel { id: rightTitle anchors.verticalCenter: menuBar.verticalCenter anchors.right: menuBar.right anchors.rightMargin: Theme.windowMargin Accessible.ignored: accessibleIgnored } Rectangle { color: "#0C0C0D0A" y: 55 width: parent.width height: 1 } } mozilla-vpn-client-2.2.0/src/ui/components/VPNMouseArea.qml000066400000000000000000000020001404202232700235740ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import "../themes/themes.js" as Theme MouseArea { id: mouseArea property var targetEl: parent property var uiState: Theme.uiState property var onMouseAreaClicked: function() { parent.clicked() } function changeState(stateName) { if (mouseArea.hoverEnabled) targetEl.state = stateName; } anchors.fill: parent hoverEnabled: true cursorShape: !hoverEnabled ? Qt.ForbiddenCursor : Qt.PointingHandCursor onEntered: changeState(uiState.stateHovered) onExited: changeState(uiState.stateDefault) onPressed: changeState(uiState.statePressed) onCanceled: changeState(uiState.stateDefault) onReleased: { if (hoverEnabled) { changeState(uiState.stateDefault); onMouseAreaClicked(); } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNPanel.qml000066400000000000000000000060701404202232700227650ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Layouts 1.14 import QtGraphicalEffects 1.14 import "../themes/themes.js" as Theme Item { property alias logo: logo.source property alias logoTitle: logoTitle.text property alias logoSubtitle: logoSubtitle.text property var logoSize: 76 property var maskImage: false property var isSettingsView: false anchors.horizontalCenter: parent.horizontalCenter width: Math.min(parent.width, Theme.maxHorizontalContentWidth) height: panel.height ColumnLayout { id: panel anchors.leftMargin: Theme.windowMargin * 1.5 anchors.rightMargin: Theme.windowMargin * 1.5 width: parent.width - Theme.windowMargin * 3 anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter spacing: 0 Rectangle { id: logoWrapper color: "transparent" Layout.preferredHeight: Math.max(logoSize, 76) Layout.fillWidth: true Image { id: logo anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: logoWrapper.bottom verticalAlignment: Image.AlignBottom anchors.bottomMargin: 0 sourceSize.height: isSettingsView ? undefined : logoSize sourceSize.width: isSettingsView ? undefined : logoSize fillMode: Image.PreserveAspectFit layer.enabled: true Component.onCompleted: { if (isSettingsView ) { logo.height = logoSize; logo.width = logoSize; logo.smooth = true; } } Rectangle { id: mask anchors.fill: parent radius: logoSize / 2 visible: false } layer.effect: OpacityMask { maskSource: maskImage ? mask : undefined } } } VPNHeadline { id: logoTitle Layout.preferredWidth: parent.width Layout.alignment: Qt.AlignHCenter Layout.topMargin:24 // In Settings, the headline wrapMode is set to 'WrapAtWordBoundaryOrAnywhere' to // prevent very long, unbroken display names from throwing the layout wrapMode: isSettingsView ? Text.WrapAtWordBoundaryOrAnywhere : Text.WordWrap } VPNSubtitle { id: logoSubtitle Layout.alignment: Qt.AlignHCenter Layout.topMargin: 12 Layout.leftMargin: Theme.windowMargin / 2 Layout.rightMargin: Theme.windowMargin / 2 Layout.maximumWidth: Theme.maxHorizontalContentWidth Layout.fillWidth: true } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNPopupButton.qml000066400000000000000000000020701404202232700242210ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import "../themes/themes.js" as Theme VPNButtonBase { id: button property alias buttonText: buttonText.text property alias buttonTextColor: buttonText.color property var colorScheme property var uiState:Theme.uiState enabled: popup.visible Layout.fillWidth: true Layout.fillHeight: true Accessible.name: buttonText.text state: "state-default" Component.onCompleted: state = uiState.stateDefault VPNUIStates { borderWidth: 1 colorScheme: parent.colorScheme } VPNMouseArea { targetEl: button } contentItem: VPNInterLabel { id: buttonText lineHeight: 15 horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter wrapMode: Text.WordWrap } } mozilla-vpn-client-2.2.0/src/ui/components/VPNRadioButtonLabel.qml000066400000000000000000000015161404202232700251200ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import "../themes/themes.js" as Theme // VPNRadioButtonLabel Label { anchors.left: radioButton.right anchors.leftMargin: Theme.hSpacing - 2 font.family: Theme.fontInterFamily font.pixelSize: Theme.fontSize color: Theme.fontColorDark states: State { when: radioControl.checked PropertyChanges { target: radioButtonLabel color: Theme.blue } } transitions: Transition { ColorAnimation { target: radioButtonLabel properties: "color" duration: 100 } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNRadioDelegate.qml000066400000000000000000000072701404202232700244220ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme RadioDelegate { id: radioControl property bool isHoverable: true property var radioButtonLabelText property var accessibleName property var uiState: Theme.uiState signal clicked() ButtonGroup.group: radioButtonGroup width: parent.width height: Theme.rowHeight Component.onCompleted: { state = uiState.stateDefault } onFocusChanged: { if (!radioControl.focus) return mouseArea.changeState(uiState.stateDefault); if (typeof (ensureVisible) !== "undefined") ensureVisible(radioControl); } Keys.onPressed: { if (event.key === Qt.Key_Return || event.key === Qt.Key_Space) mouseArea.changeState(uiState.stateDefault); } Keys.onReleased: { if (event.key === Qt.Key_Return || event.key === Qt.Key_Space) radioControl.clicked(); } Accessible.name: accessibleName Accessible.onPressAction: clicked() Accessible.focusable: true states: [ State { name: uiState.statePressed PropertyChanges { target: radioButtonInsetCircle color: radioControl.checked ? Theme.bluePressed : "#C2C2C2" scale: 0.55 } PropertyChanges { target: radioButton border.color: radioControl.checked? Theme.bluePressed : "#3D3D3D" } }, State { name: uiState.stateDefault PropertyChanges { target: radioButtonInsetCircle color: radioControl.checked ? Theme.blue : Theme.bgColor scale: 0.6 } PropertyChanges { target: radioButton border.color: radioControl.checked || radioControl.activeFocus ? Theme.blue : Theme.fontColor } }, State { name: uiState.stateHovered PropertyChanges { target: radioButtonInsetCircle color: radioControl.checked ? Theme.bluePressed : Theme.greyHovered scale: 0.6 } PropertyChanges { target: radioButton border.color: radioControl.checked ? Theme.bluePressed : Theme.fontColor } } ] VPNRadioButtonLabel { id: radioButtonLabel text: radioButtonLabelText } background: Rectangle { color: "transparent" } VPNMouseArea { id: mouseArea } indicator: Rectangle { id: radioButton anchors.left: parent.left implicitWidth: 20 implicitHeight: 20 radius: implicitWidth * 0.5 border.width: Theme.focusBorderWidth color: Theme.bgColor antialiasing: true smooth: true Rectangle { id: radioButtonInsetCircle anchors.fill: parent radius: radioButton.implicitHeight / 2 Behavior on color { ColorAnimation { duration: 200 } } } // Radio focus outline VPNFocusOutline { focusedComponent: radioControl setMargins: -3 radius: height / 2 } Behavior on border.color { ColorAnimation { duration: 200 } } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNRadioSublabel.qml000066400000000000000000000006401404202232700244330ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import "../themes/themes.js" as Theme VPNTextBlock { width: parent.width anchors.left: parent.left anchors.leftMargin: 38 anchors.top: parent.top anchors.topMargin: 24 } mozilla-vpn-client-2.2.0/src/ui/components/VPNRemoveDevicePopup.qml000066400000000000000000000131261404202232700253270ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import QtGraphicalEffects 1.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme Popup { id: popup property var deviceName property var wasmView leftInset: Theme.windowMargin rightInset: Theme.windowMargin topPadding: Theme.windowMargin * 2 bottomPadding: Theme.windowMargin * 2 rightPadding: Theme.windowMargin leftPadding: Theme.windowMargin modal: true focus: true anchors.centerIn: parent closePolicy: Popup.CloseOnEscape // TODO: We can not use Accessible type on Popup because it does not inherit // from an Item. The code below generates the following warning: // "...Accessible must be attached to an Item..." // See https://github.com/mozilla-mobile/mozilla-vpn-client/issues/322 for // more details. // Accessible.role: Accessible.Dialog // Accessible.name: popupTitle.text onClosed: { // When closing the dialog, put the focus back on the // remove button that originally triggered the dialog. if (wasmView) { return; } if (deviceList.focusedIconButton) { deviceList.focusedIconButton.forceActiveFocus(); } } Overlay.modal: Rectangle { color: "#4D0C0C0D" } background: Rectangle { id: popupBackground color: "#E1E1E1" radius: 8 RectangularGlow { id: rectangularGlow anchors.fill: popupBackground glowRadius: 10 spread: 0.1 color: "black" cornerRadius: popupBackground.radius + glowRadius opacity: 0.3 z: -1 } } contentItem: Item { id: contentRoot ColumnLayout { id: col spacing: 0 anchors.centerIn: contentRoot width: contentRoot.width - Theme.windowMargin * 2 opacity: 1 Behavior on opacity { PropertyAnimation { duration: 200 } } Image { Layout.alignment: Qt.AlignHCenter fillMode: Image.PreserveAspectFit source: "../resources/removeDevice.png" Layout.preferredHeight: 64 } VPNInterLabel { id: popupTitle Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true horizontalAlignment: Text.AlignHCenter font.family: Theme.fontBoldFamily color: Theme.fontColorDark Layout.leftMargin: Theme.windowMargin Layout.rightMargin: Theme.windowMargin Layout.minimumHeight: 36 verticalAlignment: Text.AlignBottom //% "Remove device?" text: qsTrId("vpn.devices.removeDeviceQuestion") } VPNTextBlock { id: popupText Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true Layout.topMargin: 10 horizontalAlignment: Text.AlignHCenter color: Theme.fontColorDark //: %1 is the name of the device being removed. The name is displayed on purpose on a new line. //% "Please confirm you would like to remove\n%1." text: qsTrId("vpn.devices.deviceRemovalConfirm").arg(popup.deviceName) } GridLayout { Layout.fillWidth: true Layout.minimumHeight: Theme.windowMargin * 2 Layout.topMargin: Theme.windowMargin columnSpacing: 8 columns: { const cancelText = qsTrId("vpn.devices.cancelDeviceRemoval"); const removeText = qsTrId("vpn.devices.removeDeviceButton"); if (cancelText.length > 8 || removeText.length > 8) { return 1 } return 2; } VPNPopupButton { id: cancelBtn //% "Cancel" buttonText: qsTrId("vpn.devices.cancelDeviceRemoval") buttonTextColor: "#262626" colorScheme: Theme.popupButtonCancel onClicked: { popup.close(); } focus: true Accessible.defaultButton: true } VPNPopupButton { id: removeBtn //: This is the “remove” device button. //% "Remove" buttonText: qsTrId("vpn.devices.removeDeviceButton") buttonTextColor: Theme.white colorScheme: Theme.redButton onClicked: { VPN.removeDevice(popup.deviceName); if (vpnFlickable.state === "deviceLimit") { // there is no further action the user can take on the deviceList // so leave the modal open until the user is redirected back to the main view col.opacity = .5 return; } popup.close(); } } } } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNServerCountry.qml000066400000000000000000000125321404202232700245600ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme VPNClickableRow { id: serverCountry objectName: "serverCountry-" + code property bool cityListVisible: (code === VPNCurrentServer.countryCode) property var currentCityIndex property alias serverCountryName: countryName.text function openCityList() { cityListVisible = !cityListVisible; const itemDistanceFromWindowTop = serverCountry.mapToItem(null, 0, 0).y; const listScrollPosition = vpnFlickable.contentY if (itemDistanceFromWindowTop + cityList.height < vpnFlickable.height || !cityListVisible) { return; } scrollAnimation.to = (cityList.height > vpnFlickable.height) ? listScrollPosition + itemDistanceFromWindowTop - Theme.rowHeight * 1.5 : listScrollPosition + cityList.height + Theme.rowHeight; scrollAnimation.start(); } Keys.onReleased: if (event.key === Qt.Key_Space) handleKeyClick() handleMouseClick: openCityList handleKeyClick: openCityList clip: true activeFocusOnTab: true onActiveFocusChanged: parent.scrollDelegateIntoView(serverCountry) accessibleName: VPNLocalizer.translateServerCountry(code, name) Keys.onDownPressed: repeater.itemAt(index + 1) ? repeater.itemAt(index + 1).forceActiveFocus() : repeater.itemAt(0).forceActiveFocus() Keys.onUpPressed: repeater.itemAt(index - 1) ? repeater.itemAt(index - 1).forceActiveFocus() : menu.forceActiveFocus() Keys.onBacktabPressed: { focusScope.lastFocusedItemIdx = index; menu.forceActiveFocus(); } state: cityListVisible states: [ State { when: !cityListVisible PropertyChanges { target: serverCountry height: serverCountryRow.height } PropertyChanges { target: cityList opacity: 0 } }, State { when: cityListVisible PropertyChanges { target: serverCountry height: serverCountryRow.height + cityList.height } PropertyChanges { target: cityList opacity: 1 } } ] // Override default VPNClickableRow transition. transitions: [] Behavior on height { NumberAnimation { easing.type: Easing.InSine duration: 260 } } RowLayout { id: serverCountryRow spacing: 0 height: Theme.rowHeight width: parent.width VPNServerListToggle { id: serverListToggle Layout.leftMargin: Theme.windowMargin / 2 } Image { id: flag source: "../resources/flags/" + code.toUpperCase() + ".png" fillMode: Image.PreserveAspectFit Layout.preferredWidth: Theme.iconSize Layout.preferredHeight: Theme.iconSize Layout.leftMargin: Theme.hSpacing } VPNBoldLabel { id: countryName text: VPNLocalizer.translateServerCountry(code, name) Layout.leftMargin: Theme.hSpacing Layout.fillWidth: true } } Column { id: cityList objectName: "serverCityList" anchors.top: serverCountryRow.bottom anchors.topMargin: 22 anchors.left: serverCountry.left anchors.leftMargin: Theme.hSpacing + Theme.vSpacing + 6 width: serverCountry.width - anchors.leftMargin Accessible.role: Accessible.List //% "Cities" //: The title for the list of cities. Accessible.name: qsTrId("cities") Behavior on opacity { PropertyAnimation { duration: 300 } } Repeater { id: citiesRepeater model: cities delegate: VPNRadioDelegate { id: del objectName: "serverCity-" + modelData.replace(/ /g, '_') activeFocusOnTab: cityListVisible onActiveFocusChanged: if (focus) serverList.scrollDelegateIntoView(del) Keys.onDownPressed: if (citiesRepeater.itemAt(index + 1)) citiesRepeater.itemAt(index + 1).forceActiveFocus() Keys.onUpPressed: if (citiesRepeater.itemAt(index - 1)) citiesRepeater.itemAt(index - 1).forceActiveFocus() radioButtonLabelText: VPNLocalizer.translateServerCity(code, modelData) accessibleName: VPNLocalizer.translateServerCity(code, modelData) onClicked: { VPNController.changeServer(code, modelData); stackview.pop(); } height: 54 checked: code === VPNCurrentServer.countryCode && modelData === VPNCurrentServer.city isHoverable: cityListVisible enabled: cityListVisible Component.onCompleted: { if (checked) { currentCityIndex = index } } } } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNServerListToggle.qml000066400000000000000000000022251404202232700251700ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Layouts 1.14 // VPNServerListToggle VPNIcon { source: "../resources/arrow-toggle.svg" transformOrigin: Image.Center smooth: true rotation: -90 transitions: [ Transition { to: "rotated" RotationAnimation { properties: "rotation" duration: 200 direction: RotationAnimation.Clockwise easing.type: Easing.InOutQuad } }, Transition { to: "" RotationAnimation { properties: "rotation" duration: 200 direction: RotationAnimation.Counterclockwise easing.type: Easing.InOutQuad } } ] states: State { name: "rotated" when: serverCountry.cityListVisible PropertyChanges { target: serverListToggle rotation: 0 } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNSettingsItem.qml000066400000000000000000000017001404202232700243400ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import "../themes/themes.js" as Theme VPNClickableRow { property var settingTitle property var imageLeftSrc property var imageRightSrc accessibleName: settingTitle anchors.left: undefined anchors.right: undefined Layout.fillWidth: true Layout.preferredHeight: Theme.rowHeight RowLayout { anchors.fill: parent anchors.leftMargin: 8 anchors.rightMargin: 8 VPNIconAndLabel { icon: imageLeftSrc title: settingTitle } Item { Layout.fillWidth: true } VPNIcon { id: imageRight source: imageRightSrc } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNSettingsToggle.qml000066400000000000000000000073211404202232700246700ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.0 import QtQuick.Controls 2.5 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme CheckBox { id: vpnSettingsToggle property var toggleColor: Theme.vpnToggleConnected property var uiState: Theme.uiState property alias forceFocus: vpnSettingsToggle.focus property var toolTipTitle onClicked: toolTip.hide() height: 24 width: 45 states: [ State { when: checked PropertyChanges { target: vpnSettingsToggle toggleColor: Theme.vpnToggleConnected } PropertyChanges { target: cursor x: 24 } }, State { when: !checked PropertyChanges { target: vpnSettingsToggle toggleColor: Theme.vpnToggleDisconnected } PropertyChanges { target: cursor x: 3 } } ] VPNToolTip { id: toolTip text: toolTipTitle } transitions: [ Transition { NumberAnimation { target: cursor property: "x" duration: 200 } } ] VPNFocusBorder { id: focusHandler anchors.fill: hoverPressHandler border.color: toggleColor.focusBorder color: "transparent" anchors.margins: -1 radius: 50 opacity: vpnSettingsToggle.activeFocus ? 1: 0 } Rectangle { id: cursor height: 18 width: 18 radius: 9 color: Theme.white z: 1 anchors.verticalCenter: vpnSettingsToggle.verticalCenter } VPNMouseArea { id: mouseArea anchors.fill: hoverPressHandler targetEl: uiPlaceholder } Rectangle { id: hoverPressHandler color: "#C2C2C2" opacity: 0 z: -1 anchors.fill: uiPlaceholder radius: height / 2 anchors.margins: -4 state: uiPlaceholder.state states: [ State { name: uiState.stateHovered PropertyChanges { target: hoverPressHandler opacity: 0.2; } }, State { name: uiState.statePressed PropertyChanges { target: hoverPressHandler opacity: .3 } } ] transitions: [ Transition { PropertyAnimation { target: hoverPressHandler property: "opacity" duration: 200 } } ] } focusPolicy: Qt.StrongFocus function handleKeyClick() { vpnSettingsToggle.clicked() } Keys.onReleased: { if (event.key === Qt.Key_Return) handleKeyClick(); uiPlaceholder.state = uiState.stateDefault; } Keys.onPressed: { if (event.key === Qt.Key_Return || event.key === Qt.Key_Space) uiPlaceholder.state = uiState.statePressed; } Rectangle { id: uiPlaceholder /* Binding loop hack-around */ height: 24 width: 45 color: "transparent" } indicator: VPNUIStates { id: toggleBackground itemToFocus: vpnSettingsToggle itemToAnchor: uiPlaceholder colorScheme: toggleColor radius: height / 2 showFocusRings: false } } mozilla-vpn-client-2.2.0/src/ui/components/VPNSignOut.qml000066400000000000000000000006311404202232700233130ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.0 import "../themes/themes.js" as Theme VPNFooterLink { //% "Sign Out" labelText: qsTrId("vpn.main.signOut") fontName: Theme.fontBoldFamily linkColor: Theme.redButton } mozilla-vpn-client-2.2.0/src/ui/components/VPNStackView.qml000066400000000000000000000010771404202232700236300ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.0 import QtQuick.Controls 2.14 import Mozilla.VPN 1.0 StackView { id: stackView Component.onCompleted: VPNCloseEventHandler.addStackView(stackView) Connections { target: VPNCloseEventHandler function onGoBack(item) { if (item === stackView) { stackView.pop(); } } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNSubtitle.qml000066400000000000000000000011441404202232700235160ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme // VPNSubtitle Text { horizontalAlignment: Text.AlignHCenter font.pixelSize: Theme.fontSize font.family: Theme.fontInterFamily wrapMode: Text.Wrap width: Theme.maxTextWidth color: Theme.fontColor lineHeightMode: Text.FixedHeight lineHeight: Theme.labelLineHeight } mozilla-vpn-client-2.2.0/src/ui/components/VPNSystemAlert.qml000066400000000000000000000106141404202232700242010ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.0 import QtQuick.Controls 2.5 import Mozilla.VPN 1.0 VPNAlert { id: alertBox visible: false state: VPN.alert states: [ State { name: VPN.NoAlert PropertyChanges { target: alertBox opacity: 0 visible: false } }, State { name: VPN.updateRecommended PropertyChanges { target: alertBox opacity: 0 visible: false } }, State { name: VPN.AuthenticationFailedAlert PropertyChanges { target: alertBox alertType: "authentication-failed" //% "Authentication error" alertText: qsTrId("vpn.alert.authenticationError") //% "Try again" alertLinkText: qsTrId("vpn.alert.tryAgain") opacity: 1 visible: true } }, State { name: VPN.ConnectionFailedAlert PropertyChanges { target: alertBox alertType: "connection-failed" //% "Unable to connect" alertText: qsTrId("vpn.alert.unableToConnect") alertLinkText: qsTrId("vpn.alert.tryAgain") opacity: 1 visible: true } }, State { name: VPN.NoConnectionAlert PropertyChanges { target: alertBox alertType: "no-connection" //% "No internet connection" alertText: qsTrId("vpn.alert.noInternet") alertLinkText: qsTrId("vpn.alert.tryAgain") opacity: 1 visible: true } }, State { name: VPN.ControllerErrorAlert PropertyChanges { target: alertBox alertType: "backend-service" //% "Background service error" alertText: qsTrId("vpn.alert.backendServiceError") //% "Restore" //: Restore a service in case of error. alertLinkText: qsTrId("vpn.alert.restore") opacity: 1 visible: true } }, State { name: VPN.UnrecoverableErrorAlert PropertyChanges { target: alertBox alertType: "backend-service" alertText: qsTrId("vpn.alert.backendServiceError") alertLinkText: "" opacity: 1 visible: true } }, State { name: VPN.RemoteServiceErrorAlert PropertyChanges { target: alertBox alertType: "backend-service" //% "Remote service error" alertText: qsTrId("vpn.alert.remoteServiceError") alertLinkText: "" opacity: 1 visible: true } }, State { name: VPN.SubscriptionFailureAlert PropertyChanges { target: alertBox alertType: "subscription-failed" //% "Subscription failed" alertText: qsTrId("vpn.alert.subscriptionFailureError") alertLinkText: qsTrId("vpn.alert.tryAgain") opacity: 1 visible: true } }, State { name: VPN.GeoIpRestrictionAlert PropertyChanges { target: alertBox alertType: "geoip-restriction" //% "Operation not allowed from current location" alertText: qsTrId("vpn.alert.getIPRestrictionError") alertLinkText: "" opacity: 1 visible: true } }, State { name: VPN.LogoutAlert PropertyChanges { target: alertBox //% "Signed out and device removed" alertText: qsTrId("vpn.alert.deviceRemovedAndLogout") opacity: 1 visible: true } } ] } mozilla-vpn-client-2.2.0/src/ui/components/VPNTextBlock.qml000066400000000000000000000011701404202232700236210ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import "../themes/themes.js" as Theme // VPNTextBlock Text { color: Theme.fontColor font.family: Theme.fontInterFamily font.pixelSize: Theme.fontSizeSmall lineHeightMode: Text.FixedHeight lineHeight: 21 width: Theme.maxTextWidth wrapMode: Text.Wrap Accessible.role: Accessible.StaticText Accessible.name: text } mozilla-vpn-client-2.2.0/src/ui/components/VPNToggle.qml000066400000000000000000000175261404202232700231570ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.5 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme VPNButtonBase { id: toggleButton property var connectionRetryOverX: VPNController.connectionRetry > 1 property var toggleColor: Theme.vpnToggleDisconnected property var toolTipTitle: "" Accessible.name: toolTipTitle function handleClick() { if (VPNController.state !== VPNController.StateOff) { return VPN.deactivate(); } return VPN.activate(); } onClicked: handleClick() // property in VPNButtonBase {} visualStateItem: toggle state: VPNController.state height: 32 width: 60 radius: 16 hoverEnabled: false states: [ State { name: VPNController.StateInitializing PropertyChanges { target: cursor anchors.leftMargin: 4 } PropertyChanges { target: toggle color: "#9E9E9E" border.color: Theme.white } PropertyChanges { target: toggleButton toggleColor: Theme.vpnToggleDisconnected } }, State { name: VPNController.StateOff PropertyChanges { target: cursor anchors.leftMargin: 4 } PropertyChanges { target: toggle color: "#9E9E9E" border.color: Theme.white } PropertyChanges { target: toggleButton //% "Turn VPN on" toolTipTitle: qsTrId("vpn.toggle.on") } }, State { name: VPNController.StateConnecting PropertyChanges { target: cursor anchors.leftMargin: 32 color: "#998DB2" } PropertyChanges { target: toggle color: "#387E8A" border.color: Theme.ink } PropertyChanges { target: toggleButton //% "Turn VPN off" toolTipTitle: qsTrId("vpn.toggle.off") toggleColor: Theme.vpnToggleConnected } }, State { name: VPNController.StateConfirming PropertyChanges { target: cursor anchors.leftMargin: 32 color: connectionRetryOverX ? "#FFFFFF" : "#998DB2" } PropertyChanges { target: toggle color: "#387E8A" border.color: Theme.ink } PropertyChanges { target: toggleButton //% "Turn VPN off" toolTipTitle: qsTrId("vpn.toggle.off") toggleColor: Theme.vpnToggleConnected } }, State { name: VPNController.StateOn PropertyChanges { target: cursor anchors.leftMargin: 32 } PropertyChanges { target: toggle color: "#3FE1B0" border.color: Theme.ink } PropertyChanges { target: toggleButton toolTipTitle: qsTrId("vpn.toggle.off") toggleColor: Theme.vpnToggleConnected } }, State { name: VPNController.StateDisconnecting PropertyChanges { target: cursor anchors.leftMargin: 4 } PropertyChanges { target: toggle color: "#CECECE" border.color: Theme.white } PropertyChanges { target: toggleButton toolTipTitle: qsTrId("vpn.toggle.on") toggleColor: Theme.vpnToggleDisconnected } }, State { name: VPNController.StateSwitching PropertyChanges { target: cursor anchors.leftMargin: 32 color: "#998DB2" } PropertyChanges { target: toggle color: "#387E8A" border.color: Theme.ink } PropertyChanges { target: toggleButton toggleColor: Theme.vpnToggleConnected } } ] transitions: [ Transition { ParallelAnimation { NumberAnimation { target: cursor property: "anchors.leftMargin" duration: 200 } ColorAnimation { target: cursor duration: 200 } } } ] // Focus rings VPNFocusBorder { id: focusHandler anchors.fill: toggle anchors.margins: -4 radius: height / 2 border.color: toggleColor.focusBorder color: "transparent" opacity: toggleButton.activeFocus && (VPNController.state === VPNController.StateOn || VPNController.state === VPNController.StateOff) ? 1 : 0 VPNFocusOutline { id: vpnFocusOutline anchors.fill: focusHandler focusedComponent: focusHandler setMargins: -6 radius: height / 2 border.width: 7 color: "transparent" border.color: toggleColor.focusOutline opacity: 0.25 } } // Faint outline visible on hover and press Rectangle { id: hoverPressHandler color: "#C2C2C2" state: toggle.state opacity: { if (state === uiState.stateDefault || toggleButton.activeFocus) return 0; if (state === uiState.stateHovered) return 0.2; if (state === uiState.statePressed) return 0.3; } z: -1 anchors.fill: toggle radius: height / 2 anchors.margins: -5 PropertyAnimation on opacity { duration: 200 } } function toggleClickable() { return VPN.state === VPN.StateMain && (VPNController.state === VPNController.StateOn || VPNController.state === VPNController.StateOff || (VPNController.state === VPNController.StateConfirming && connectionRetryOverX)); } // Toggle background color changes on hover and press VPNUIStates { itemToFocus: toggleButton itemToAnchor: toggle colorScheme: toggleColor radius: height / 2 setMargins: -7 showFocusRings: false opacity: toggleClickable() ? 1 : 0 z: 1 Behavior on opacity { PropertyAnimation { property: "opacity" duration: 100 } } } Rectangle { id: cursor height: 24 width: 24 radius: 12 anchors.left: parent.left anchors.top: parent.top anchors.topMargin: 4 z: 2 } VPNMouseArea { id: mouseArea targetEl: toggle anchors.fill: toggle hoverEnabled: toggleClickable() cursorShape: Qt.PointingHandCursor } VPNToolTip { text: toolTipTitle } background: Rectangle { id: toggle Component.onCompleted: state = uiState.stateDefault border.width: 0 anchors.fill: toggleButton radius: height / 2 Behavior on color { ColorAnimation { duration: 200 } } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNToolTip.qml000066400000000000000000000031021404202232700233110ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtGraphicalEffects 1.0 import "../themes/themes.js" as Theme ToolTip { id: toolTip visible: mouseArea.containsMouse || parent.activeFocus onVisibleChanged: if (visible) fadeDown.start() leftMargin: Theme.windowMargin * 1.5 rightMargin: Theme.windowMargin * 1.5 delay: 1500 leftPadding: 6 rightPadding: 6 topPadding: 4 bottomPadding: 4 ParallelAnimation { id: fadeDown PropertyAnimation { target: toolTip property: "opacity" from: 0 to: 1 duration: 200 } PropertyAnimation { target: toolTip property: "y" from: parent.height + 3 to: parent.height + 6 duration: 200 } } contentItem: Text { id: toolTipText text: toolTip.text color: Theme.fontColorDark z: 1 wrapMode: Text.WordWrap } background: Rectangle { id: glowClippingPath radius: 4 color: Theme.bgColor z: -1 RectangularGlow { anchors.fill: glowClippingPath glowRadius: 2 spread: 0.5 color: "#0C0C0D" cornerRadius: glowClippingPath.radius + glowRadius opacity: 0.1 z: -2 } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNUIStates.qml000066400000000000000000000044121404202232700234250ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import "../themes/themes.js" as Theme Rectangle { id: root property var uiState:Theme.uiState property variant itemToFocus: parent property variant itemToAnchor: parent property var borderWidth: Theme.focusBorderWidth property var colorScheme: Theme.linkButton property var setMargins: -2 property var showFocusRings: true signal clicked() anchors.fill: itemToAnchor antialiasing: true color: "transparent" radius: Theme.cornerRadius z: -1 states: [ State { when: itemToAnchor.state === uiState.stateDefault PropertyChanges { target: buttonBackground color: colorScheme.defaultColor } }, State { when: itemToAnchor.state === uiState.stateHovered PropertyChanges { target: buttonBackground color: colorScheme.buttonHovered opacity: 1 } }, State { when: itemToAnchor.state === uiState.statePressed PropertyChanges { target: buttonBackground color: colorScheme.buttonPressed opacity: 1 } } ] transitions: [ Transition { ColorAnimation { target: buttonBackground duration: 200 } PropertyAnimation { target: buttonBackground property: "opacity" duration: 200 } } ] VPNFocusOutline { visible: showFocusRings anchors.margins: setMargins focusedComponent: itemToFocus focusColorScheme: colorScheme } Rectangle { id: buttonBackground anchors.fill: parent radius: parent.radius color: colorScheme.defaultColor antialiasing: true } VPNFocusBorder { id: focusInnerBorder visible: showFocusRings color: buttonBackground.color border.width: borderWidth } } mozilla-vpn-client-2.2.0/src/ui/components/VPNVerticalSpacer.qml000066400000000000000000000004411404202232700246310ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.0 Rectangle { color: "transparent" width: parent.width } mozilla-vpn-client-2.2.0/src/ui/components/VPNWasmAlerts.qml000066400000000000000000000017401404202232700240070ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.0 import "../themes/themes.js" as Theme Item { anchors.fill: parent Column { anchors.fill: parent anchors.top: parent.top anchors.topMargin: Theme.rowHeight spacing: Theme.windowMargin VPNAlert { id: alertBox1 alertType: "update" state: "recommended" alertColor: Theme.blueButton width: parent.width - (Theme.windowMargin * 2) alertLinkText: qsTrId("vpn.updates.updateNow") alertText: qsTrId("vpn.updates.newVersionAvailable") visible: true } Repeater { model: 9 delegate: VPNSystemAlert { state: index visible: true } } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNWasmHeader.qml000066400000000000000000000036761404202232700237570ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtGraphicalEffects 1.12 import QtQuick.Layouts 1.14 import QtQuick.Window 2.12 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme Button { id: wasmHeader height: 60 width: parent.width z: 2 clip: true onClicked: mainStackView.replace("../components/VPNWasmMenu.qml", StackView.Immediate) background: Rectangle { color: Theme.bgColor anchors.fill: parent } VPNUIStates { colorScheme: Theme.iconButtonLightBackground z: 2 radius: 0 } VPNMouseArea { hoverEnabled: true } Text { id: btnText font.pixelSize: 16 text: "Viewer Menu" font.family: Theme.fontBoldFamily color: Theme.fontColorDark anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter z: 3 } Rectangle { color: "#eee" z: 2 width: wasmHeader.width height: 1 anchors.bottom: wasmHeader.bottom LinearGradient { property var parentWidth: parent.width anchors.fill: parent start: Qt.point(0, 0) end: Qt.point(parentWidth, 0) source: parent z: 2 opacity: .7 gradient: Gradient { GradientStop { position: 1.0 color: "#6173ff" } GradientStop { position: 0.5 color: "#f10366" } GradientStop { position: 0.0 color: "#ff9100" } } } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNWasmMenu.qml000066400000000000000000000073551404202232700234710ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import QtQuick.Window 2.12 import "../themes/themes.js" as Theme VPNFlickable { flickContentHeight: col.childrenRect.height anchors.fill: parent Column { id: col spacing: 0 width: parent.width * .83 anchors.horizontalCenter: parent.horizontalCenter anchors.top: parent.top anchors.topMargin: Theme.rowHeight / 2 VPNBoldLabel { id: viewsLabel text: "Views" font.pixelSize: Theme.windowMargin width: parent.width height: Theme.rowHeight leftPadding: 6 verticalAlignment: Text.AlignVCenter Rectangle { height: 2 width: parent.width color: "#eee" radius: 4 anchors.bottom: parent.bottom } } VPNVerticalSpacer { height: Theme.windowMargin / 2 } VPNWasmMenuButton { Layout.fillWidth: true text: "Main" onClicked: mainStackView.replace("../states/StateMain.qml", StackView.Immediate) } VPNWasmMenuButton { Layout.fillWidth: true text: "Subscription Needed (IAP) - iOS" onClicked: mainStackView.replace("../states/StateSubscriptionNeeded.qml", {wasmView: true}, StackView.Immediate) } VPNWasmMenuButton { Layout.fillWidth: true text: "Device list: Max number of devices reached" onClicked: mainStackView.replace("../views/ViewDevices.qml", {wasmView: true}, StackView.Immediate) } VPNWasmMenuButton { Layout.fillWidth: true text: "Update Required" onClicked: mainStackView.replace("../views/ViewUpdate.qml", {state: "required"}, StackView.Immediate) } VPNWasmMenuButton { Layout.fillWidth: true text: "Update Recommended" onClicked: mainStackView.replace("../views/ViewUpdate.qml", {state: "recommended"}, StackView.Immediate) } VPNWasmMenuButton { Layout.fillWidth: true text: "Backend failure" onClicked: mainStackView.replace("../states/StateBackendFailure.qml", StackView.Immediate) } // TODO /* -- Main ( VPN Connection Unstable ) -- Main ( VPN No Connection) */ VPNVerticalSpacer { height: Theme.windowMargin } VPNBoldLabel { text: "Other" font.pixelSize: 16 width: parent.width height: Theme.rowHeight leftPadding: 6 verticalAlignment: Text.AlignVCenter Rectangle { height: 2 width: parent.width color: "#eee" radius: 4 anchors.bottom: parent.bottom } } VPNVerticalSpacer { height: Theme.windowMargin / 2 } VPNWasmMenuButton { Layout.fillWidth: true text: "Alerts" onClicked: mainStackView.replace("../components/VPNWasmAlerts.qml", StackView.Immediate) } VPNRemoveDevicePopup { z: 4 id: popup deviceName: "" } VPNWasmMenuButton { Layout.fillWidth: true text: "Device list: device removal confirmation" onClicked: popup.open() } } } mozilla-vpn-client-2.2.0/src/ui/components/VPNWasmMenuButton.qml000066400000000000000000000022231404202232700246520ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import "../themes/themes.js" as Theme VPNButtonBase { id: button height: Theme.rowHeight Layout.preferredHeight: Layout ? Theme.rowHeight : undefined width: parent.width Layout.preferredWidth: Layout ? Math.min(parent.width * 0.83, Theme.maxHorizontalContentWidth) : undefined Layout.alignment: Layout ? Qt.AlignHCenter : undefined Component.onCompleted: { state = uiState.stateDefault; } VPNUIStates { colorScheme: Theme.wasmOptionBtn setMargins: -5 } VPNMouseArea { hoverEnabled: loaderVisible === false } contentItem: Label { id: label color: Theme.fontColorDark text: button.text horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignVCenter width: button.width font.family: Theme.fontFamily font.pixelSize: 13 } } mozilla-vpn-client-2.2.0/src/ui/main.qml000066400000000000000000000152721404202232700201050ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Window 2.12 import Mozilla.VPN 1.0 import "./components" import "themes/themes.js" as Theme Window { id: window property var safeContentHeight: window.height - iosSafeAreaTopMargin.height property var isWasmApp: Qt.platform.os === "wasm" function fullscreenRequired() { return Qt.platform.os === "android" || Qt.platform.os === "ios" || Qt.platform.os === "tvos"; } screen: Qt.platform.os === "wasm" && Qt.application.screens.length > 1 ? Qt.application.screens[1] : Qt.application.screens[0] flags: Qt.platform.os === "ios" ? Qt.MaximizeUsingFullscreenGeometryHint : Qt.Window visible: true function getWidth() { return fullscreenRequired() ? Screen.width : Theme.desktopAppWidth; } function getHeight() { return fullscreenRequired() ? Screen.height : Theme.desktopAppHeight; } width: getWidth() height: getHeight() maximumHeight: getHeight() maximumWidth: getWidth() minimumHeight: getHeight() minimumWidth: getWidth() //% "Mozilla VPN" title: qsTrId("vpn.main.productName") color: "#F9F9FA" onClosing: { console.log("Closing request handling"); // No desktop, we go in background mode. if (!fullscreenRequired()) { close.accepted = false; window.hide(); return; } if (VPNCloseEventHandler.eventHandled()) { close.accepted = false; return; } console.log("closing."); } Component.onCompleted: { if (VPN.startMinimized) this.showMinimized(); } Rectangle { id: iosSafeAreaTopMargin color: "transparent" height: marginHeightByDevice() width: window.width function marginHeightByDevice() { if (Qt.platform.os !== "ios") { return 0; } switch(window.height * Screen.devicePixelRatio) { case 1624: // iPhone_XR (Qt Provided Physical Resolution) case 1792: // iPhone_XR case 2436: // iPhone_X_XS case 2688: // iPhone_XS_MAX case 2532: // iPhone_12_Pro case 2778: // iPhone_12_Pro_Max case 2340: // iPhone_12_mini return 34; default: return 20; } } } VPNWasmHeader { id: wasmMenuHeader visible: false } VPNStackView { id: mainStackView initialItem: mainView width: parent.width anchors.top: iosSafeAreaTopMargin.bottom height: safeContentHeight clip: true Component.onCompleted: { if (isWasmApp) { wasmMenuHeader.visible = true; anchors.top = wasmMenuHeader.bottom; height = safeContentHeight - wasmMenuHeader.height; } } } Component { id: mainView Item { state: VPN.state states: [ State { name: VPN.StateInitialize PropertyChanges { target: loader source: "states/StateInitialize.qml" } }, State { name: VPN.StateAuthenticating PropertyChanges { target: loader source: "states/StateAuthenticating.qml" } }, State { name: VPN.StatePostAuthentication PropertyChanges { target: loader source: "states/StatePostAuthentication.qml" } }, State { name: VPN.StateMain PropertyChanges { target: loader source: "states/StateMain.qml" } }, State { name: VPN.StateUpdateRequired PropertyChanges { target: loader source: "states/StateUpdateRequired.qml" } }, State { name: VPN.StateSubscriptionNeeded PropertyChanges { target: loader source: "states/StateSubscriptionNeeded.qml" } }, State { name: VPN.StateSubscriptionValidation PropertyChanges { target: loader source: "states/StateSubscriptionValidation.qml" } }, State { name: VPN.StateSubscriptionBlocked PropertyChanges { target: loader source: "states/StateSubscriptionBlocked.qml" } }, State { name: VPN.StateDeviceLimit PropertyChanges { target: loader source: "states/StateDeviceLimit.qml" } }, State { name: VPN.StateBackendFailure PropertyChanges { target: loader source: "states/StateBackendFailure.qml" } } ] Loader { id: loader anchors.fill: parent } } } Connections { target: VPN function onViewLogsNeeded() { if (Qt.platform.os !== "android" && Qt.platform.os !== "ios" && Qt.platform.os !== "tvos" && Qt.platform.os !== "wasm") { VPN.viewLogs(); } else { mainStackView.push("views/ViewLogs.qml"); } } function onLoadAndroidAuthenticationView() { if (Qt.platform.os !== "android") { console.log("Unexpected android authentication view request!"); } mainStackView.push("../platforms/android/androidauthenticationview.qml", StackView.Immediate) } } VPNSystemAlert { id: alertBox z: 2 } } mozilla-vpn-client-2.2.0/src/ui/resources/000077500000000000000000000000001404202232700204515ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/resources/Icon1024.png000066400000000000000000000066651404202232700223730ustar00rootroot00000000000000PNG  IHDR<qgAMA a cHRMz&u0`:pQ<xeXIfMM*JR(iZHH pHYs  iTXtXML:com.adobe.xmp 150 1 150 72 1 72 2 vn IDATxu8%yo T7$@-(X` $rRD9=ʡhrJ cHa!NB$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@$@!D> ~?,xwwuY$@0Ͱ6($ۭ!m z w CP0:< mU!Ep~u[>9ᐦ~&_l -FmxVk|c1uBPHa@ k]{Ml5 Gm]={6DݤB-4p s/ 2Cp,%ȃR1]AwOM?3k.Pt2Gp)VX W*~XK|Vm6+7l[nϱVA4^9XKx<*(APWEQm`&]!% dK{DQ^H2LYE_Bg 9mǨWG6%آ,(:?i dԒA%S}n9hK~sSXllT/`,sfec*̭.c*D#~q!pc.] 0Y0Jr'emM֗6ܝƲ@Qb+ϕ$-=>@y* s̫>ao,qbk5b/3~t% 67.=yN+Vkia k_vvۉ`Z0 g 7f;MAK֙nIfA" OXC5ﻔ- /JɮG:rq#iY#LǾߑ]V.2/Y8ܦ#!s$u2sKcVT4R7-P{L}'PJLM,߽j1}ĢAr +ˋ&Im'9labwcIv N5J fشI jjt. }/|GE^zn87d #q @љ+D<Piȳ:]Wu6>K!Bop@$W0b }9nb- 6L'nxK|Y≏ɦd6J!6BI~hs`=}Dx\">LJ*X/4f؞k q({08Le}a_ƺG5VX<1̵da:?0gVu=S_sܢH@**Dq:mzk~rM l v+'T@vA}`kI)N P]^XVA'Ϯ6] >#d,%gؓn*>.m43} X/S1ʗUO Vk ngc`k\$5yOl_T—)xr75Ŧ-CeZV{&2IYBhKJ>UD3&O]c17| sao,S(@mY*td7Ƞ/6-~O ϭ+m2l ٤dc/4iZ vI2+K c}Uwyj&i1\պS™ӘbܓFXY㑲yqavCWrbSlG[Z{%;un ʼn [m}`B+Z0;[)~\@ͪT':ΜЄR2aQQeQ*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH 4N2v#IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/arrow-toggle.svg000066400000000000000000000005111404202232700236000ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/back.svg000066400000000000000000000004521404202232700220730ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/resources/buttonLoader.svg000066400000000000000000000032331404202232700236350ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/checkmark.svg000066400000000000000000000014361404202232700231260ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/chevron.svg000066400000000000000000000007601404202232700226410ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/close-dark.svg000066400000000000000000000023011404202232700232120ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/close-white.svg000066400000000000000000000023011404202232700234110ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/connection-info-dark.svg000066400000000000000000000010401404202232700251740ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/resources/connection-info.svg000066400000000000000000000005611404202232700242640ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/connection.svg000066400000000000000000000031611404202232700233320ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/copy.svg000066400000000000000000000017411404202232700221470ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/delete.svg000066400000000000000000000024261404202232700224400ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/devices.svg000066400000000000000000000010341404202232700226120ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/devicesLimit.svg000066400000000000000000000051761404202232700236240ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/down.svg000066400000000000000000000012651404202232700221450ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/externalLink.svg000066400000000000000000000015071404202232700236350ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/flags/000077500000000000000000000000001404202232700215455ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/resources/flags/AC.png000066400000000000000000000020621404202232700225360ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL$}$}bGMY$}B!j+(Hy__$}#tS3mӻl@i ;q)H*y?j?GYjnz3[ϼKinَlPXۼY=Z+jGr_I=bf[EwQl`tŶ/ᔛ4k{峷.Vjo+P5S@=GU$4T] LCϴ:g|8ǧH358mƩ#w\7=)~^ -?pC[aaYS|A@ qdu%$f]}߹*_ؾ]ַ(%_~/]BIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/AD.png000066400000000000000000000012711404202232700225400ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL :: :: : dz: @ʶwιo4~ Osî{nѻgBKGvM,W <0]&bA(ӁlEY._ٕX[蟦ߋ6ϕz˴liㅂh]s=붸磯gվ_nՃM} tRNS￿` ` e^vIDATx^Gk@a;-ԢZ{MocEHG zA0t@NK}{Ł;*;C@ 0j?>t&R:}IX4 e tR_,׮8#mbwl0Y<,V}yc x:A@͟Vnw \=ow аy.2!RBB"?ΣI HAD#tE9Bh<(^C 4bc62l Rd&@@޽¾⺛gWSH~u4f0Y ˲<=0}?zOH$ֶ?({"Rl ZElMM/tm IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/AE.png000066400000000000000000000004371404202232700225440ustar00rootroot00000000000000PNG  IHDRHHb3CuE`R.wUeMpj0,Uuљ/TcH%5j!BL"DP<47E{h-a,޼~lNQ?/XzIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/AF.png000066400000000000000000000020641404202232700225430ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL K% z6 z6z6z6z6'1%=3B9OIZV###111{{{. 鐈읗ʹOOO𧧧tj﫦lllfXM]]]@@@Puy"?ztRNS@@@``5vIDATxSDQ E?նm:njmU+;ŝR^)z PTPL{@1W>P}xB"f" BQWC(uϗ0ϗ ^1,bx1$/_dFa$4# Q1qq ~sgڂ8g_!A@DBĨyUFLn{Ṩҿ f[/3%e U(k}:t,YvrgԎMjlwB4\/H ]oG0 #v$X?2 zmۦyrjN݌&)!o1=@C y*B_fB/?5{wۤm!D?T`Ӟ)cK IrL͎dތ%!uk@rp"u `I˸ٴmޙky"{ T$}fq{o34Ȁ^f.4 pXY vQPeѸMhw9,".j6uO 4֡&Iꗠ0 l cM\爓[j/Xyt;l@T/A4Z𵻴:gBvZH&¢NCѮ# k[1,~szg@7]O" ߹N"$%e=wh tRNS` Ͽ`6IDATx^Ij@a z=k Uih!$^^V5Pu'KК6Up@]w]rU[K%4$]d[ɏ#P*c3Qm*@rb`KRHgx hcnU3-h8pK|r*8^.>P Z8#ta)T[M~aYh>; 1p>V F&&z[J14^}YOnʯVaBfZzF<~N' 0 @V% i&)s9 =:}+BTVBٜ\Ub:AwX Κs];F B%jr]1|.@ DAw !O,8CN О4s':+NЖtˏs?P.W+w@2E>\Bܪܹ"Vls"V[$i?L3\,˿ځ`ÜMDr&0 IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/AI.png000066400000000000000000000017351404202232700225520ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL$}LX$}$}$}++$}΅04+fxX7՘E`pP>rOe`A峷4Oim5C˓ᔛ(7=N壺|̟zPJcٟFSh*H|_jA&IGXd$ 1 X\r:ˇ;,G1CҮ }' Y敡`RB17>HXT2X-|!hūRtD-AA|Yd54,B \/: 6[jA1uՄ 7<ԅ V jC(sjel 4ڴA|@3HOA8*RBryјY(5QQ|؜s:ZߟIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/AL.png000066400000000000000000000012261404202232700225500ustar00rootroot00000000000000PNG  IHDRHHb3CuEPLTEGpL     r9+ G d V LtRNS` ϿVyIDATx^n0E3tߚ=@eXJ,0eOph>o~|즛_%o~Nuw'EA4wO la6ɠ;Բ{,*:%Nuؙ]mA:gf5 KV,d@m@]JQ2hƋ1{:Ղ ="D=2'EZH9Ƚ>6"@"`D3eZڬZ_ˋU'IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/AM.png000066400000000000000000000003651404202232700225540ustar00rootroot00000000000000PNG  IHDRHHb3Cu*PLTEGpL3_ tRNS`` oIDATxЉ 0A.Nc BPb1G%~L,יn.)DQ8P$h/&%H A.RUS8P:;aIݽg9T]?'(ul1IPIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/AO.png000066400000000000000000000013231404202232700225510ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL&&&&&j]u" \QME?8ܾβ!1,xiCD/x&%ŬPN.޿ϳqjc*691zkvl&|ҷ&J"yU!'%a m2$p tRNS`` IDATxn0a񒄢6KޝC-%}(R`ȋ6e^p$x-[f-V[wH#G;i,nЎ>G*@ePGhٵV[N,rZˊ `6ݑ;ѩu TQjn QișAו618P¶|)Ύ1@ARDq VRr 9d[LhZ͗)pct@󏉔xAā"sPӑMzO?l@-m?B[n#Dۘk5lGG xn3(QIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/AR.png000066400000000000000000000007051404202232700225570ustar00rootroot00000000000000PNG  IHDRHHb3CuiPLTEGpLuuuuuX:Xu۬zdO5z-IŹq*>|BHcHFȃ1OLe@\GӁCGtRNS` ϤIDATx^n0 Eax:?)CrrkqTVvt!9bNIiTkZQh!ve]p.-E#,֬w+ #5_,zb&6`I|-zCFoqY<$NE@ ~&88 a\lNҩX' IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/AW.png000066400000000000000000000006031404202232700225610ustar00rootroot00000000000000PNG  IHDRHHb3Cu`PLTEGpLAAAAAA4A݈eYyMӼӾ=Z.MLg떦ߏ|[so͞dtRNS `8IDATx^G0@Q ur[+7 q8Nf Q&#SV YU~<&]?9Oo3 !' Z'Źg6l` 0o ݬoZW{~hsNf#SO-[Γ< Β@ dDAZ \ 1C ݉P$B@25تxfaKÏlV ̄rIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/AX.png000066400000000000000000000003461404202232700225660ustar00rootroot00000000000000PNG  IHDRHHîtPLTEGpLSSSSSS4%qtRNS Ͽ`tIDATH @PQ=$Jmavq eY&I2Z#@xhoPC0h}5IPi@ɷ˓/ } PhB׬l,2)lIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/AZ.png000066400000000000000000000006221404202232700225650ustar00rootroot00000000000000PNG  IHDRHHb3CuZPLTEGpLeeeeee4 M`A0Z@gpPs tRNSϿ `` ٽIDATx qC[m5;Zxf&1"RM֚P-)5ٝVk7 R$jL%(AׂS7wDTu}vtX[}$p]t>c orZa:`0畍d}i/Žg L%(Aׂr' *NQ&ey9iRzmIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/BA.png000066400000000000000000000012141404202232700225330ustar00rootroot00000000000000PNG  IHDRHHb3CuoPLTEGpL-1-1-1-1-1-18}k'с D8oZhL˝;dR+Ak ~ltc.Ew߇8\oV=6kO}J;6F.[DX0 j)oQ/?8ADB8Y4$ƉjȏqRZ6*yi/QT;G*ˌESb(:jSDu#6qrWQt!E8" p  d߮!`Ky-tRNS` ϿVyIDATx^׻0FaHʝ}7]BVb\E>@B|oZGGQ߾g'bDy!ubkVJ@6}o $A/ K!hlƘ| sa DN}(@ޅ nC/(>`?!J:!ƨ(N(0Nhe0A yPy`&^V. U؃؄(5jͬRïj_;k'QIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/BD.png000066400000000000000000000005201404202232700225350ustar00rootroot00000000000000PNG  IHDRHHb3Cu0PLTEGpLjMjMjMjMjMjM*A.BfLBF>ELVIkNHFFbL /tRNS` ϿCIDATx a-fcĤ~MĨ<\r|1S*`iڜO)RCr(F9A4G VPg9hV4v6Iuڭk!}E UTu0QAZTPCMbDA{l?$DEo#f?~3VC٘e5YwiZ&eaIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/BE.png000066400000000000000000000003511404202232700225400ustar00rootroot00000000000000PNG  IHDRHHît*PLTEGpL#,#,#,#,#,#,3Zl tRNS Ͽ``ϿﱶbcIDATHc`tg`.  5g+m g)mCXVŠ0!hQEF**쉪6ʈ^G}6SIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/BF.png000066400000000000000000000006061404202232700225440ustar00rootroot00000000000000PNG  IHDRHHb3CuZPLTEGpLII+-II+-+-+-+-I+-I/?,_& J)n35,?<F~0&yȉ tRNS ` `ϿIDATxϹ Q!lXQd@qM5Q 5u*R &IAC5b!0A!A@  $Csӿ-YE&sf L>?&f.AB}z,85--;96o{|A:< C a IH4()DDS@JH #pc{'W IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/BG.png000066400000000000000000000003641404202232700225460ustar00rootroot00000000000000PNG  IHDRHHb3Cu*PLTEGpL&&&&&&nNַ tRNS Ͽ``ϿﱶbnIDATx 0-{g2X4b]GBpIiWZ4!1! r?rȡ$r[LMhRtP|.e8N<1?IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/BH.png000066400000000000000000000006331404202232700225460ustar00rootroot00000000000000PNG  IHDRHHb3CuTPLTEGpL&&&&&dq⛣HX&-?3淼ހ:LVer~䩰8k? tRNS` `Ͽ9tIDATxY 1D3:bYOQB1C,nU(l7o'RW/͡3L:U잞f^4a ;aTkRRU%'[4_ۄhtT$/rp&͋pIU"(zأcO x2U"h"$f]'2O|_ؐP7$h_fN(ְRCؘ%5If[1IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/BI.png000066400000000000000000000016541404202232700225530ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL/I淼AX::Ҕ&+E淼HXyΉ&)_r:LRgE\٫r~l~ܶ3w|O.'jqrf00*Y/-?dq՟ހVe⛣8Q䩰`Xxb?+dLĈzIoJLBǓm{>COqW@^ tRNS` ``τPIDATx^ǒ0@,a[s$<9φCy| ُ\к3j1Mvim=psf5Xu1cz.fj@ps5q .3qA妩v4@syaƨ܉x滳a\gpGXMJ&."HERTP!$4 pu AHM+=,x.TTF$u܃^n:FHi).(@/I 7&.E*v$F"W^wؽh<"q_&"V8HBGrQR&GHnٛ5)+\oyUde(JrhK򏋢Pƿh{8:-_ٳ6ҷ劖U\dphOb)%ʑxb Vt<ڠ%ME4"v텪[r_z#9z0`y]1ILӬU (SiԹߵ?l"$"H2>9[[Ej<Wu)jCc6PG]8/,}<,q͒:f]47#]RV_E~з…zϋIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/BJ.png000066400000000000000000000004271404202232700225510ustar00rootroot00000000000000PNG  IHDRHHb3Cu9PLTEGpLQQQQQQ------@ȋtRNS`` ` תzIDATx׵@ f߫14[|SƘ=Ϳ*Iq/ͦ@Eys* y}ryy J`@-D!BZ"D!B:B: 5MD@mƂ }\p'IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/BL.png000066400000000000000000000017221404202232700225520ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL#)S#^E$T9?MM·>d~]lMt݂̯㗛qu]dʫص.bϿ5JzäL짞(2q7lt3oB^ɳ/ͽdބU^R ;jfff(H| tRNS` ς'*O!5DNlvd zۭ7s8h9},U8h+;)m}$檗E{tĤKSJk:hWj""3:Բ:h>" ;oC W0/kƾ[PU$6En؍*I 9DTGc2R-F Qy|h(],*; *ǝƒ#RBwە*su2PS~„Aʹ81XcE,'NQ1˓IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/BM.png000066400000000000000000000016271404202232700225570ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL++$}+&2+LX$}+4 ;V'-`w`k粷᎕δb%W.V]'3$}2mv" ."{~zQ(_)h>LS F7ՖzX'Ly$?0TaXRktc tRNS``` ϿZ zIDATx^gs:`x;Խ_#-b8+2;Fã#<@_?y *!di@pUx7ܑ͛@_ y+JOd%6e .)?-*ꗡhd@0>\()0׊S_^Bnb[er Alà|% _de1M-B[ (m2" Rϻ P')@ r1[ oeW M>T^FPR)D9P4˅-W TѥԢA bߒbб[W,bdw;<2\|k;-C~mӴ퇵 >e 2/E˥OOKA0ȴMct:Աhn ~@hRp l!L Vurx! 9Sx!kCy|_L{oEƾMd2%Z'3@1:z۔e4p@2^m =[7њ:)!t3(6A!1JY5f0;! Y|fՎT|ծNo·5z{IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/BN.png000066400000000000000000000015641404202232700225600ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL&E#$ruPX$F-7sx\,$##c Ռ߅䬰⚜7]e湼?:CLoUZm<zwmKKK22//[TfffMhv{S0tRNS` Ͽ!]IDATx^n0E(,ͽ;}kjEje|,0<]N9;zr~=^OgS5 ȍh4N;'"Yo"žƑv8N"{Zƻ4j@KDigl{ sz M&zscnY~ľd ?kDYրD}lF⏢]tCBdz%o\Ҭofc Ds@4+ΰ#TI;-k"z=&Zd&a=_I$^gviD7@}% 6jQRr:Ph\_~ [E9DH3Ph__4D[Pتg(0d50DDֈVɪȕ(5Z׆P{Λ?X&11"&BU&RL.^T(KQDVo 9O=o "ZEy(,e VM=G2΀7O ;4̴6q;2cSD%O7if!2 ƧE";O+%HJ: R*d9q7}C.cIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/BO.png000066400000000000000000000012321404202232700225510ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpLy4y4y4+y4+}1+++y4֡X S}e(ݴKNۚR*m+Frju5CaePx(*pis%E&`Jrnsj;WUP031I3p'D`k⯟ͳP+a" tRNSϿ` `Ls~IDATxr0a!V{ش *83La wPNybg J5#'zjz,*1-'B<*B88OF PU}B\9P.ٛolqJAo"}ǩkߗ(gscZXlbtD4҅1Dd)X&wSi(B3oؒφw7 F š&yeN R&<\g/ݙaRѕ8"N_ʓ)"rI6slpdi9*JuT5cAz64BZu8-BH;KJOimwHSӿi$IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/BQ.png000066400000000000000000000015471404202232700225640ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL***e***wwwJJJ-,,hhhMQ<;;b傅gkZ^ӆYYY<[B-O6~3$*iKgZsѳxGw?etRNS `` `b6HIDATx^Y03i;2}WurI%'辍8 0{^nN9ͫv>n<ך3pu9m#>pϯkߟoɸ@ōfΟ#G|5EnSjƣPkҐ(LgsA!lF+7sC^%$ҼTPXʘD>|`CB>] l(S laJ)^\OXS lμBRvEC5?TTU(Ԯ% FVsVUA\ %ZwZ1IBj0΢" gVEz@s@DPaZJ@K-ʪh/t;RL]EDH=(E!Ituv+GQ7egH j upb:uvG *z4$JF= #*<$ITHj)+dEe1QߺDEPe(BE*ʇPQ>)!T|C(BE*ʆPQ6K+;3ey(/oify <#OYs{/6?tIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/BR.png000066400000000000000000000014451404202232700225620ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL:::::: 3O( 6@,7@UY'v83oν >g0J`3X?ԟyLp^&K&0/Y| 3|@r{Do!UAx?df؈+kfyj_lJ,;XOaQ`tRNS` U IDATxPP~ڞ1Nƛ^!7U.M%V3=yd=@LbDĶضmCdݺ=tNxlE1 gQaffj!!j`Yϥ6H$vJjUjQǻGjJI̪GK2S2zVk.`VoBRk* &/O8a|7F|y~̎KY?E8/Gxۯ"a[[b/R -O?Qcvhjf+Zu_O~\5IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/BS.png000066400000000000000000000006431404202232700225620ustar00rootroot00000000000000PNG  IHDRHHb3CulPLTEGpLym("!'+ iz `o4CM:A?: MX06B7?yjzMȵ=d/Yt[Kf_ g'UVxkOrtRNS ``萗7IDATx^Ws8a9ۖT@vhk@oכ7wṅ{BΝ8}a< ;u~H׻ۋ:a;n灈nv硰/i5׀seeId:D ũBn-JWJWAM1>JW1>1>䘟JW1>JWLXQ\v~詯JW1>>Jqzen䘟~祫KV겷Xb>0tRNS `Ͽ` `Ͽe IDATxnA [q(";ǙE%Y{ބ-4x,4sW|\|ACܜ4H/&9ʷI4SOPrŘ=!Q9΍ I$@ 8Y]+&Z(9g7">03L"%I4֪(eEć捩86,q@$T_ݔ` DbY@P&CnM>; V`׮;r܂~RQ>6Tr0Jmhwi3cZ<~_(CP7bx϶N ~A] 'ZӬ4X" OP<*J݀Ϻb\Z~sUx tRNS` `MIDATxEP @WC A~@- 6Y#i}.7QJ6v Q]xk3ne?vz> ± M _0헡E Uྱ| ZXs_| s V0r(&•-[P,BGkl9g˹ՋsO@n8q9n&d@l5( 7#qkw+`̃lEf$M2 j۳ uޗrOKþHӯ/Jȹ9t+YSEڶ AaSB +L$!*h\`s Xݹm}! IMնm~zw!PiFC JE…2< $: }+8ք5m_qx: 5 Z@g{irInع =H;ۄ5l3kqMi^RIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/CC.png000066400000000000000000000013021404202232700225340ustar00rootroot00000000000000PNG  IHDRHHb3CuiPLTEGpL@ Pp`0 ,Pפ\z`n8htV+tRNS` n۷IDATx^ǎ0 @\3e Q+Ca1Ȼ PuykڮANM)0'WK0 JL]4,bAś9(0E!AT(BDщBfe"ND`܇H$!\4zdG9+PA4>arD9 Zj%Dȉ~"YD?K'+$~-{A$z\;h&Z,[z1*.%lD#~~w3_`(ۄZ`1`@Ԧ%N_$>S_l>Qnh=>s_2,),?g~̓M hL1ĕ;+JFAVk CNoEgkia!*8#lRfD`-5UD\HH?.BMF$Rai =j=j=E,OwIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/CD.png000066400000000000000000000011321404202232700225360ustar00rootroot00000000000000PNG  IHDRHHb3Cu{PLTEGpL!! !!!!>Śol]`&R.Cs) N M5|B}ELtRNS`Ͽ ``1hIDATx^n@DsT1ӣg_hah\$ww"C:Bb'W5{mVqrk6^:TTG|ʲm*1Q4,Y5*0*ƤJ 5$sJCy_I oڮJt^w&Ut$ިl0rެ*E@:\c5RJ cPPMٓdFCBAŦҐlUXU#ZTkcTim1-Du0DSVUzȨJU >OS)T >C¢JJ*MT >OS)T >OS)T >OS) tRNSϿ `` ٽIDATxI0 @QiuN?fKRN$$7FUQs@S: J&)@"*|B!+qjCܣkbʞ ">4 >TaCţ -CWok)@ݼ!@ tz/t(d,Cy96rԀ$/$9޹AIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/CG.png000066400000000000000000000005441404202232700225470ustar00rootroot00000000000000PNG  IHDRHHb3CuBPLTEGpLCCCGJJJCCJJCn0$j/$$$$G$UtRNS`` `nA0IDATx̅m@@9qhQ pm2|růf6|~p63LG/L+ͣDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDL$yx6܏s>kyޞ'NxbIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/CH.png000066400000000000000000000003341404202232700225450ustar00rootroot00000000000000PNG  IHDRHHîtPLTEGpL-'-'-'-'-'-'b]g6@tRNS Ͽ`jIDATHc`˱T5BX"pJ1 Hq)*ARdKQ1r`)JQEtRF&YIGTaOTATDTUFTH\: 8l[D,IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/CI.png000066400000000000000000000003511404202232700225450ustar00rootroot00000000000000PNG  IHDRHHît*PLTEGpL``````. { tRNS Ͽ` `(cIDATHc`tg``7  5g+4[ g)CX Š2!QEF**쉪6ʈ^G}kM*IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/CK.png000066400000000000000000000017011404202232700225470ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL$}$}$}$}+$}+LX$}2@[ ?ր`v+0MpPh1ɕh|(7w5CCP-J䬱湽]h߅@9P$(gT @@VH>%5VZ(h P3Tnf(cMf(~9t!hv ZjhkkcEWOLkIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/CN.png000066400000000000000000000006321404202232700225540ustar00rootroot00000000000000PNG  IHDRHHb3CuEPLTEGpL)))))4)m  b K @x V (LtRNS` ϿVyIDATx^; Eoԗ*TTG`Kя\؟ j^_ {}XYc%'̀#d7kvDtBьQꚵ7A6;qDdwm* ͖oHî!BZ ~ZQ4uLEmIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/CO.png000066400000000000000000000003651404202232700225600ustar00rootroot00000000000000PNG  IHDRHHb3Cu*PLTEGpL ( ( ( ( ("@ (Fq tRNS` `Ͽ tKoIDATxЉ @C}8߮b%Gon&EsvKF((4` $HAJ A_NRބ2(T}ٱӁ]ɮ mQUYIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/CP.png000066400000000000000000000003511404202232700225540ustar00rootroot00000000000000PNG  IHDRHHît*PLTEGpL$)9)9)9$$$)9$)9$)9 tRNS `Ͽ`cIDATHc`tea  * 5+8[ )PMXlŠ.1&QEF**쉪6ʈ^G}ARMIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/CR.png000066400000000000000000000007051404202232700225610ustar00rootroot00000000000000PNG  IHDRHHb3Cu{PLTEGpL++++++%.>✣epWd 2r}±tܨAлظǍiI03Y=4<0Ze7tX5tRNS` UIDATxׅ0a[<u0{^dTM'Tf:i\K'Β30PMg): |PvM yxxaPnD1! !@FC1$.Pyߣ:9IE eHF$!]b Y%xʍhH98O%|G۶.w \dղmFmxGQI1~`kɨ[IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/CU.png000066400000000000000000000010711404202232700225610ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL**+*h]*+*+3++꘢"78 )]m#8l{'&|1h]*A#p=P渽s✥/C[ c@S犕Q߁JXh>K\tWDO`4%v䪱' tRNS` `Ͽ1IDATx^n@ѵHϮkOHv3wlˌ:] < :}-]-|I+X}nW8x<ސWR BB!`!sH| TwX(<B!'B!N*ч{T23P],:DU%R^Z!EъVwްN(KgxukqJVbr[4Ya[ %״ɐmhAnsXT1Ҩm>io6akèx,6˭Fd[?UdZ?`k=RIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/CV.png000066400000000000000000000010761404202232700225670ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL888888K$]qdɴ-E>^t 'Bؾ&y1=+&|UA$Zl{\um W"L.U|Mhl6%g?b!L#htRNS Ͽ`g{WIDATx^׎ EI[jV^V8~G Ѡ1]`хxgM3yB>FNDq"fK3+ߴ=[$br(Ee91'J6 ˒D\ YHc. >hereEFel'rITw\ᗆ)(@DF][#ӻԀu:Ag(k_RTY#_`#ENZ ?օS#_bN_ԈI;OfIkfYl ?EQ7ZIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/CW.png000066400000000000000000000005451404202232700225700ustar00rootroot00000000000000PNG  IHDRHHb3CuEPLTEGpL++++++Pm@`0S8 Fp׿߀`{}tRNS` UIDATxױr a`-#!`d52)(W3˹@H1|- -ٹv*-01 J ӺԶmaCԱ!T-|E ۾ ;?BoلHCugi9*ҒLO{zt%e2P-ȥҡߗ !yy/~%H!D5f3jNQ._Z;JUIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/CX.png000066400000000000000000000016211404202232700225650ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpLkc!BD!!!BB$K9!Ʒ;)A =T@/q?:(`tԻ;>F@@YPf0K֪<eg:ptX7}O8A><`_ 6@JIB O~Vx=V߱H\q+.Cb?O  tRNS ` `ąHtIDATx^r0PZ9Y^KU) 7jI`ڐt0w ?&d| ~,GU`﹉^@F.^ 'ty!<4 S?w# }M![%Y(&ElO󢳏L^?WyNL=a˝e׸JP_?RCuy*f 1&3س]jH>iKT7n!gZ Ihy+G@jQ%?i$?vt8)Ҵ)k|t(b5яaߤYm%gt}>-uh6L|\r?xH7nIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/CY.png000066400000000000000000000010331404202232700225630ustar00rootroot00000000000000PNG  IHDRHHb3CuuPLTEGpL}oaŋѧ˙6 E(׶SとheFw]\;nQѶs~Š˫gtRNS` UNIDATxb $gu2SgۺѶ^1JV;s.i"B}kK@!PDI`iT`CUʓoߩe1 ճy1Ԁh=MA|)Dݐcx/z}^=6\ S `:3csgBe&6tJ*5XAC*3?bgbߒ/z f۲ #TB"HKyA1ᛎE-u nZY]26iyX1Q`ZZuVWt؂Q0 ;C&[nIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/CZ.png000066400000000000000000000010451404202232700225670ustar00rootroot00000000000000PNG  IHDRHHUGIDATx\Qڶm۶mVTAmF6LT|x2LPz@@93vf"5P? )j{d3Q7lF)7nNwu=$hR--:SHLJbR ҁieR]hw΀dRQmw݅FFj*of9Ƥ<PV#&G(5p_jXDao%  ;N:cIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/DE.png000066400000000000000000000003661404202232700225500ustar00rootroot00000000000000PNG  IHDRHHb3Cu*PLTEGpL$63 tRNS`Ͽ` >pIDATx90CQg# Qӌ 2ڷ-PN/Ӈq. #4d20R ')A H /rƁPNpT{Z ܠԶݿUٮIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/DG.png000066400000000000000000000026171404202232700225530ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL$}Vu7(9Wb\rwIEumK+iOHtiϤ5^l|Ic9e؉Q[_Z.D峷|طᔛħ]|kf֚vzɘ+U[ tRNS`` 9˙ uIDATx^R8ͬSqkueɲa )N5 S$_]՗"B+JT af0W/`=aWЈY\kNѿPj&<({z@QϙdmP܂( IU"tyg 0-O)#L/yv]0F8?"2BbRG9"ֹw1-lq֪X~IZ2L r@v5F 2{\4@MpS߃jAz@,DA3ug ,[|BEM-hZjg( XN`7+Ujߨ\NJZ0$Z-6M M`=;VU<qG=EF*F'S]RMx>N @t;\@%)6؀,#;΁\e8&6qES,&E\Ԍ^E f\M+[>.2ӟFv+,Z6obEGi¢z A@q ;U׋U 6 >à 5 (Z 7SDyPzC4lzA̪9I8؇8_1]gnM115f\Hqx0"u: a圎Mwo~^ <2H3~{_6ͮ *Y^<ބa8٪O3l Ɓ'~ ~LuoP%l7ѝGInL6Bo<4AU"RFmf RȮZ!x)2~luQuQ$yPJXhkHB]n%f$C6MOVmd[6rĐ!ԑܽE5$ifW5-A=.E#R"PZޟ Y6WHkF`̀$$r *C)9͙{6둺]]ˠ~A3 c.0ROf4S=ۑ.dl<6h{@V18kM+UEX}K| IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/DJ.png000066400000000000000000000012031404202232700225440ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL++++jjjj+j+ 7;P踹/5\l΍ڱ޽rɀ.CI\ҙ֥֜いKOsw"'窬fjr土X\j=B{vtRNS` Ͽ` Ͽ(`W#^IDATxr@ዊ6Q28hVi+XUT˷JyF\Y;!+֋1}& 蕕|9пc'z;QxHYy4<Bހ7–CFB !G@^+;Y(JgzƠ yP! Ħy8$0$7tJ֙' PVPBͿՃrƴtYPhZZLr0YPcKQv!Ab!dsCEݫ2 Y tάR眒c扜,nE9,fYPS;DgO `1IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/DK.png000066400000000000000000000003731404202232700225540ustar00rootroot00000000000000PNG  IHDRHHb3Cu'PLTEGpL 0 0 0 0 0 0 0 0 0 0 0. tRNS` @hOxIDATxױ @ CQ'wp^B+u. [ >h73%YȜ.gcvJ:BhȐ!CUheאfȐ*hC hf4Y㔢 EW]Q>u J/IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/DM.png000066400000000000000000000012701404202232700225530ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpLk?k?k?k?k?.Dk?1@|D&[i?8[@4>/鲹$k3v0UaJuuhN9CKr?DaOA˘Ts`sR<ç`|HB-WQj͞g}0* v;?[7Z@P5۲(7tRNS `m<IDATx^Ǯ 4kz?׿]yK`VߕGrgpM,݉jSdMZf8fLCXWHa& 6Xf k@zH]5i'ȓJKӇrHq\6]%7I:QB䩡JUI/iz//EeJ ̀s^ctv]w@.φJEІH@S z"ݢ]Stjۥ=QBJtL"uE@E'ZQ0Y6-JB"%Bb>ۯ~D i,|o{\ 1 dw//ԪNig ںCyIr݃ssdݎZ(|PyßsoudC=daj16fL[_yȌIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/DO.png000066400000000000000000000011101404202232700225460ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL-b-b-b-b.D 5%.D-b%.D%.DT4-b%_F~g.D寴Rp暢p6*5W2tRNS ` ` u,IDATx^7v@@OP΁sp< RUp8pG7++Qd_E#i!Af@@ R"ߋG.Zzǫ8h8YFA^f؟<4l0wy`Cyvhj `]8o A@# &.HB=RpI lXŘƅXp 26+ٌb9˃aj ~M@ Rf @]B& Zm#XHo.ToY IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/DZ.png000066400000000000000000000010141404202232700225640ustar00rootroot00000000000000PNG  IHDRHHb3CuxPLTEGpLb3b3b3b3b34b3cz䚨⌝qGc5M3'R3 ]3$44Un9W@\>3.44OC3娴)4i94+K= tRNS` Ͽ`58IDATx^WR1 @S H!] xؖVK$vw6^_g+"x1]lVn%xD@EQ>2M$ѹƙ,e'G+Fhlu6h,:,S&U(<1CсMTڿM.PIt "9T ^qAGDG+ԉEU@-i"բPDf}rho4b FS@ H1%2e^ظK-ۑ jG߲(vka[?UiDD(u(VIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/EA.png000066400000000000000000000005521404202232700225420ustar00rootroot00000000000000PNG  IHDRHHb3CuQPLTEGpL     E`g fu3-: If`n_)Yn.D!tRNS` ϤIDATx^;0 EQ;ƒ dHjKRřt3MӜۙ"+6G1.䌕B7M뛊j=SvXz**KpfBpܔdCu ]~Rh=u1(N`Ax@ȂBʄJ ] ~Ʌ b$ ۯDb?bBjԈ,'5E/c|G IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/EC.png000066400000000000000000000012601404202232700225410ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL#####!).PW~lд]q}MR;2[q[?1Bnb}B>B)NmFٮ#4Pp-ʱ՟Ȫf! |x0\ XiOT]x9wx@fL"3}5p3 tRNS` ` >IDATxR0a:NODVep֧iG"-v+b)fŬP6NERA%P$@E*[{}W4֐lنSAlkzl^iA>DaQvP.K8˾^_,XypŒ;zrgR+ s9sVUNG@& o89#f yvn@mAЃsF6ǂ4HE%hkU>>>>>8xh>88 5<#tk8)5*Z1f0754+N2'()-A36,r. B6vw_B8BBBz]BBBک3,tRNS` `` /(IDATxG@ ${WKw/Fw-rs$'&9ɋz/ROeRDuF;t39 au6K?j~i,*ILq+ŭP @+-,)նĭwRvH+Qx'+mcX6 [HOV2X; )n-s -I[I*uORz@K-KE[3H>UX(lqvpe蛤 'K* h^}<I``ÌtQ =jHR[gh0x^ɝ* ]4f j;$vi4h24Nߕޡ1'3=0wZCJ(f\{z }H tX-Zwg hpYv{]hqJmHqg tRNS`` Ͽf&aIDATxn0FZ0ۭ\v|*ZMGB!8vve>`a$y<њh񬨟$,4uv}8?/ʏ'13j$o3D"'|?l\~PgcukK {,+ 5$@l,E";(QчC 1P3Ԍ@aD  SF D_ڞn qVEmI $o :k]+ ܐCy3TP Ѱ \t+Ԉ1V撅a.*LCj DWVrPX llڮz!k0vj㝋xD K)_O!CQF){z,-HʰM*b+QXhԋ KNsCU,m (ˮ',NEА>/d |QIC;O_qV(,"G!R LJvD"d'Z5v1bW"|(sH>o[7lvM7 ![f:q#İfܘ5l1Xoo6J7{Yu;IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/FI.png000066400000000000000000000004041404202232700225470ustar00rootroot00000000000000PNG  IHDRHHît*PLTEGpL5,AtRNS`` `{IDATxѱ@Fx+d$`ԀZk5w+۶`StZcQԃTtL2*%T51_GL(:ts#GubZ%5(J0Qо9;$†,De ڂH@qyP(16U'R> v7fQpH0]y8Ԑ9&{jkj!q*zߜWĤIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/FK.png000066400000000000000000000017211404202232700225540ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL$}$}$}$}+'6$}LX$}߫<3r+k|3NތZ쇼▢ʈijKIgRPɺMV45C㿺um·fƭ q`_tOwNHs#jD`iRFSQ tRNS ``YIDATx^go@a'PM{fտ%MЁ"⇅,?>!ۭ[~U">3l{?90?~-~ 5Ӳ[AP/xQyw_$=\á7} ᦦu092 (_lg<"6phj&>#շQCҐ?!9%gYư*.!ztDVRy=8S KaQ H|L! dZ/mB,SՏ'HAXzHlr1>]52:g$ cV+Wkd_jA@QgxGŪGk(gπ{P Yx&C1 DP}knTrD ڄ҇- IZ U2b1{ طVX zȮ˝`hވs$E$,=(V"Ú{t3BQױ ,5hqV'rB?I S )H[rdds@SJ'EDeɜNTO(PS3e )jҥU"N !ϕ&)L:]b>Ѕ ݠuֹѴ5iw7 GԹhvIPh qIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/FO.png000066400000000000000000000003511404202232700225560ustar00rootroot00000000000000PNG  IHDRHHîtPLTEGpLe)9\KtRNS Ͽ`tIDATHc`t`0-2:Ѐ HP~E  00uRԡAXQ aE u*Šr"rzGhF抈*쉪6ʈ^G}x܍IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/FR.png000066400000000000000000000003511404202232700225610ustar00rootroot00000000000000PNG  IHDRHHît*PLTEGpL$)9)9)9$$$)9$)9$)9 tRNS `Ͽ`cIDATHc`tea  * 5+8[ )PMXlŠ.1&QEF**쉪6ʈ^G}ARMIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/GA.png000066400000000000000000000003641404202232700225450ustar00rootroot00000000000000PNG  IHDRHHb3Cu*PLTEGpL`:u:u`:u`:u`:u``:ud tRNS` `A9VnIDATx 0-{g2X4b]DBpI}izt!)!Qr?rȡ$rUө4MRt0|c 8ZM0?䬙IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/GB.png000066400000000000000000000012731404202232700225460ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL+++䨭+.=+1$}䬱यJch|w]hYpCP=8CWFjy\fn["di$J<5+X<m6xJ=?I,"Qשׂ(V]昊&\j9Kxf\I u,"&-@{ȯ ܰm[D Z 04 Y=PwznP#8,:Q:9]YՉЃEb? 8[C )Vʥ(("-+b{!hŠZydQD A4|$ZE)JVD8*v 7`l~'e;l;7"Gֱs cMlL(ZAяbQbB4Bɠ³̂{!BL{jBD$NzcI1?e2z*(L|IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/GD.png000066400000000000000000000013121404202232700225420ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL&&&&&&z^)$%qe U(OH5#Y ~:-$/QA"?Ln?ZCeL28}M!mW_C1'fT]XE6{:1tRNS` n۷IDATx^׎@ Em&˵lO $m\)zY</WcLr26y2jVsX~f2W= hpVbc7[p^k;K,g pڹ*)spTZYSp%\(D4f n;D!!( R:7EDk6TW!zҀ=YTjsFIK:"YXG$°gP("R zJP(P ^JVG.O^q W%czD## El<?_^EV8kNXYye*y}~J! _E⒄ X, ob@X,% qff^$'IUoEPxo8܌>?:6\PA Uf^KARui֨|uzo]cJO k$5&!hטpiטpiטpy!\c@TXc kK(XcOdlnS#GCZJ++&~q@pV<&U)i h:=PPu(ڨ؁kbc':9fJr픦}vPNgeDXIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/GL.png000066400000000000000000000006731404202232700225630ustar00rootroot00000000000000PNG  IHDRHHb3CuNPLTEGpL 3 3 3 3 3 3綿Eb6V}㙨Sm姴?(Jay% tRNSϿ` ` b]IDATx^ي0 Qeo*E7e6V*Jn!vTb\UrכT UR uxz&X]ށ V9eA x* d4+(!d ylҐeCHMiC 1lӐgC &|蠠 6(̙v31 g:P b4fG);#,)sDxȇ 4qF@@kҝؐtqT|۫vIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/GM.png000066400000000000000000000004011404202232700225510ustar00rootroot00000000000000PNG  IHDRHHb3Cu-PLTEGpLԆ֣ņT!_g+Uz(SϐQ@nزPuO\/ HI# Aem"y- & F)t{J$d}4R#d:A]?9.mUMhDP A!8{n-F]N6Jf iL Z Ca(5^g_sjtG>e?2@*P- &8]:*@Ɯh< ڦ6 sd7RVt|^AIϜe: 4Zd$i;Ru (GtZ<&I KV kmz^iZ9G&ܠb1DƼ#sà[z>Zsz+Ѝj.﷠j>/t+GQ:(c$yq*9:.)VmGq+6YhYA+@ x>! qss>>!@\f)e‰ lv<s00>!k r9"XCtRNSϿ`` ` OIDATxr0`Z+$&!"b[VQD萓dbsMA<0W .iACJt v&˺<VjZP]w5}A5Af J׆ Hv ؚ`m Jw HLA`eN7_'DnNaAp_%tþwւ=՚F[ ҐSJ_}urjA(!N6Zt,5JM9yՀZj>,?4&DIm@у*e00F'˖+ LA8/$hP/'"m ʅ)cT1Ac6bL0əܯClfnP☁ґ!CeAx(ҹʂCT~HԺ. ¤?$XpaoG9E1|gjIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/GR.png000066400000000000000000000003551404202232700225660ustar00rootroot00000000000000PNG  IHDRHHîtPLTEGpL ^ ^ ^ ^ ^ ^tRNS` ϿC~IDATx^ѱ @A[L;0;/^߱ds/禎/eZF]#lDс<dEJ*@n(_(|Q$Oeݲzs{z4lΜݵY)5T IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/GS.png000066400000000000000000000020121404202232700225570ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL$}$}'6Yd*u$}+$}D_#]3cag+i~jM.-KugAQXX|yu:=XW-<gC?ƿᔛ$qhbxs2\-jeb㳷l($R*%^?r.T,@3@x\ybGJ>DL ">uK)~k ` >%LwDk@``yǯи}r;Pt&ŭy8Bbk!)ׯK+f!("ZHc$bBE8:Y8'n b39`۽FߚPopk×_㤆;G4{  Y5ܶo;Uh$JmjEP$w9Y_| M*"^AS"JFA'6(*!9SA7 g"Su(b3`َE~ c@6}PP >߿]AN8.sߧy~( 4-WP +82@F(AF+HVS,6<_e4Gu8X.K5qW"Ǚ%+V;5&ţaא"dL˼YAuEء\A]2~@!<,4814)&I.]_۪+&)IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/GT.png000066400000000000000000000011761404202232700225720ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpLIIIIIC~$IcJX=ãѼN1ã晶M+l0kSxco˯]M |+şůȻy~nW،͓𪨶V{UشMRa"Mk#n᧗߷W~MorDԥb*utRNS `"gIDATx^r0aaWŽ{O=VLnL.kfl:lN?b'_0FtNvڬ)ZrXs90\3bf ! Y(ºdu) nz)1>5|Q`VIRߕbaׇ2`n莫xP ru4A4j1UlqBL]!G Qx:%rpJ)eeyXZ*< 0˺K/MyB®,+ @? #O1&mY&,ڽ ЀqٗezГXe-B:wuqw-P4Ψ^Lɥ!1^0d5n;j6] i@}$GzmWƖi7uՁ8yLWL:q/-ǙVa;jC;GVÃ3@9̧I-aAgryd9uTчs%3F89v66JmY[-֞5֞Y~_k~ l*IJIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/GW.png000066400000000000000000000006521404202232700225730ustar00rootroot00000000000000PNG  IHDRHHb3CufPLTEGpLI&I&&&&IIII&4 $@ ' t ZM!   :"`3tRNS ` Ͽ ``[uIDATx0 (ҲK\Ai^$i 趠2S @aAJ aGCXT@B $? knX uMZcՐ5+!p*~4'uqjd~dZ43= #dC0CpوP럀5 gK4O bP1+r@fK? *J\If{hIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/GY.png000066400000000000000000000013001404202232700225640ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpLIIII _0&&)1/&8T`\Ppș `կI0k# $ۻ%!|{ձm1,@7#k[n:SGzgS +]]]{A4 tRNS` `G$IDATx^n1EiJ03C!HT;V)~oϐM,|| fӢ|DZÐNAQBo"9|2RbR\06y|5lKT+D:_/(FW;s Huvj> |CNWY m5Y (xt p.(69v}ߘb;@V)@m]Ρƿ銜  4l!/ņj?9?'?D~Mo'zZ_|!%$P>Né N"z T\@YYR_7MIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/HT.png000066400000000000000000000012351404202232700225670ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL '8 333 3 3j@PpFQ⧿$}/z&ZXlP_`m}lU0ӧ̯b8sf*N*0B/Uf@E{߷CoFiC%̞,`p|9,8j@ħnq\H#41 tRNS` ` 4APIDATxَ0kk움/3:Ք9dZN4䣨yuBڕ F kأ.T$\RTA!jSMU!OIiL !(b;\16(A=2Phv Vlu֫<L>&Y~-Ea𝳌Kn`pƲqh%J1NntzĖe8 cs]oRgKAJ&ޒ,_l␄}oMa(сAah!HsH> l.$`V| QS rtGc*ݪ*1f9NˁpzV]ݩP!pGn*sUU=m G*tIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/HU.png000066400000000000000000000003661404202232700225740ustar00rootroot00000000000000PNG  IHDRHHb3Cu*PLTEGpLCoM*>*>*>CoMCoM*>CoM*>CoM*>CoM/`J; tRNSϿ` `pIDATx CQ&݈(g./2:=P.?S̓Yƒ PRA@NJ A$H`I $hi$׌;’RxrV~P^*ԬAIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/IC.png000066400000000000000000000011451404202232700225470ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpLhZ2toъrboh*vY~n[)^it|G~W{UO}Jko[^UlZ8deL>~Wj{@aEmQiih_C!e>O2-EJ` i"nGY^! tRNS `` "ťIIDATx^n1@Q O{'U e3&W,I_ySz!GC/:@4Ȁa'vod$@~&R/lЗD 4u10헦ieibSrww @ U|=TZ @~98܇+}t?Y>0E(#$Cm ah"d:iy q w~R6d ! ("s E Ja]rM淜kA!yP Ӗn8Is*HA `BZjY?YӦRx,N3IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/ID.png000066400000000000000000000003611404202232700225470ustar00rootroot00000000000000PNG  IHDRHHb3Cu'PLTEGpL&&&&&&A tRNS` ` NlIDATx 0CQ!kyFpdݛRv䖐b) 4RXR"D<$E!BC-i9PF3w' z صH;sIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/IE.png000066400000000000000000000003511404202232700225470ustar00rootroot00000000000000PNG  IHDRHHît*PLTEGpL>>>bb>bbb>b>~ tRNS` ` Ͽ#IcIDATHc`ta   5+:[ )MXEnŠ.3&QEF**쉪6ʈ^G}m޿\IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/IL.png000066400000000000000000000006041404202232700225570ustar00rootroot00000000000000PNG  IHDRHHb3CuKPLTEGpL88Y|̳= 69UҏuQnP:JT`H4=B J*i @ȚY2?!Lr@ ʲ@94 dMx bag[!5kZEo6}Y IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/IN.png000066400000000000000000000006301404202232700225600ustar00rootroot00000000000000PNG  IHDRHHb3Cu`PLTEGpL333333ߑř<<--ss33$$iP tRNS`` C|wIDATx0 aG=oY8*SC>s R#( N&)"9Ra*R@QCRP`P⬭$Pw]?S 2ølhC5͖nP_융c2tu+:;tbf=|kڇ-~?"CӇJyCb'P r '';eFs8Ga,:|ET7XIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/IO.png000066400000000000000000000026171404202232700225700ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL$}Vu7(9Wb\rwIEumKپiOHtiϤ5^l|Ic9e؉Q[_Z.D峷|طᔛħ]|kf֚vzɘ+Uɞ) tRNS`` 9˙ uIDATx^r8؎,:{Uս&X ;,[:!\}K.IP @Vbp0os~ c0Uc Z+l h”L[d:],ֳcZ#BHw ]d4r`(}r=\j¸ G)^(Ԕ;_->mI ~lYk@OqQ=΁\e8&6qR,f&2Pf5c*Ņp eeQ1pS >lrʶM(_t4F-, G!4H(TO{{/bRJ%p|  s~t;+(ŀ"-D#!t‡7HAKbo[g8a}2W19 ~b̘ /y ,%̘5'+`$,‹k)uQط9AOݯpڽ>7@Oyd. ece 'D&o*TdgA=xkxS f^EF%N c3Bb)Jn&֝G~3~+ym$TCY٥I#O)dX4"_]E nJ$J v Ie[ɩC6MVmdlF!ԑ޽ŒanP6+햠IϢl)8Pw-OqG,+lE`̓`y+ ǡl\{؎%F}͉0.W iܦ%B{v 6lj1ӏd~4.*l!f\mMǹWҗ|Hy` IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/IQ.png000066400000000000000000000007301404202232700225640ustar00rootroot00000000000000PNG  IHDRHHb3CuZPLTEGpL.D.D.D.D.Dz<PyqsHN7FZ@%IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/IR.png000066400000000000000000000007121404202232700225650ustar00rootroot00000000000000PNG  IHDRHHb3Cu]PLTEGpL#@#@#@#@#@#@fg{Ō1⹿4I&=\lN`ّixքܞATv tRNS`` sdDKIDATxn a'M6grxlh=Xh'@h޷nM]pu.! Z"u0@hЏPrPb"hYT%٦rQr*2YN(P y!#_ -Lf;n! 9TC%:¥Rfa\ V`jW|L*⳨x%ch}QbKo/,@=(p;+X+s=jm1 IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/IS.png000066400000000000000000000003451404202232700225700ustar00rootroot00000000000000PNG  IHDRHHîtPLTEGpL88888((8W6tRNS` ϿCsIDATHc`t"P^^ށAj;+0b`` U Š<+ja cTpWT`E":hUA\aOTATDTUFTH\: [fIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/IT.png000066400000000000000000000003511404202232700225660ustar00rootroot00000000000000PNG  IHDRHHît*PLTEGpL+7FF+7+7FF+7+7FF+7 tRNSϿ ` `̡`cIDATHc`tɻa.8 5E+:# L ):#MX kŠ2&̨QEF**쉪6ʈ^G}OZIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/JE.png000066400000000000000000000015101404202232700225460ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL H⇛ H(N H H HMLڍ`ţ`WFK4DߧzzVbQ㔥3H߽wI-R:],@̳{^JMަSGgmTrz`|殺塰⇛P`iG׊SyF_hMsHȡBO7Q;)9{d tRNS `Ͽ`.IDATx^k#A ID^R{[ { $ ziFRߗ#G.O6iMN yІک2TzT@Zo1^gB5֚!ogR#ͬTd]L?ϙ+"*ݑswCñi˜_'IٗYS wE"7)\-jۻ8綸{^Vw֥fn{BLK3cW ߒHyLU9aNdS'h4AXq6]r9#F- IRFc-1BNl$HtVb a>.B̬҇?-,$O`i!5典CEF[;P]AEVM[).{1e/^af ?G9 ~ Π !GF?p1lx,qr@B.55 ,^0xUr_>NNXIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/JM.png000066400000000000000000000010771404202232700225660ustar00rootroot00000000000000PNG  IHDRHHb3CurPLTEGpL # 7@8 00{g ӮP)]OODp" ģ@,s 1,  3:D 5tRNS `ijuIDATx^׎*1M9gr${ږA>$xY錣F$x$τ7{ּ1gD|-,3>ݘM %cEAAyJ l)YxVl@QP]_i]E[ 9"`G%k %;@yGA=RuIA7_=%#d@LTB$ =s>3%k'3jGSKJ J8UG @3։t*0X%&RDCE:chHe Ɍev"ßf7lÿ@%b5bx]vi6\"En[Fc a1{fY=_/!!IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/JO.png000066400000000000000000000011521404202232700225620ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpLz=z=&z=z=&&&&z=z=&r~3  sO+,%*ZMS4[j֡LtRNS ` ` `IIDATxn@QA+"jm2$cbbx/ g$7Nt=vG;G>+iTd~G?R'5f%G$g+)%i荄УBBGCGB7#!JBhsXHj*uBBOBZ]B|( tZƞ.ydx%J2stImÈ&a"dGME6ii־?Fvc{r/4|pw}\,a=cݻFc000Z1fqqɬR5fn~ҳ*ѥG;:I?\Ǣ IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/JP.png000066400000000000000000000005171404202232700225670ustar00rootroot00000000000000PNG  IHDRHHb3Cu0PLTEGpL/(;jw]kw."tRNS Ͽ`IDATxi a]Ҥm&#CxĨ,fq: ,:Y)y䝋)#P$5+?\_%9 埴6޵٠PÊ=TPF 7TP5A emHM .D DP76Vk6q: AG֌.5,EgaUZ&IIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/KE.png000066400000000000000000000013271404202232700225550ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpLffffffſh S ###i YayP@>樟m`ħ궯$ ᙏ.ʶ#6"@3Ӈ%lll]]]_BPDOOOrrʢa/BBJ tRNS` `HIDATxr0j!D@uXtQo8"aڎG. Ν;jUZcir+**C?K5V2ȩ<9;r5 V~!d.lD|9b,}X Z# yC" !cX@ r)C|D e%c 5SZӳj 1Գ9$$Be =աUKg(F#kK _Է}ҷi A#2 <\y]2L[{5АC= >N:z,4Q;X P<W26S=$\0.`2<+G/SɀIu98%>.=Gv8.Q㢆 P:{"I9$@fѣ 2e%88N,*86c4 xπ=΀~ mt(`haB"DAHC B!S ! =B(vBj[?Um+~ĿIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/KI.png000066400000000000000000000017041404202232700225600ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL$}4&$}$}&$}&&$}~+#WݯjqE`1 ΂RhTbhk8 E%jtÁ HM*׶.r 2tD,͉ZhhyLDY`^ڂVÍs2W#e :IbҲiX_Тo}B=Fc$o5YQܹ>~2\O+(ӣ [NԀX=o ]A8:C4V:egtDz!/gz7 E՘W(NO'Us~r?dmwfIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/KM.png000066400000000000000000000013001404202232700225540ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL:u=3:u:u:u=3=3=3=3:u&=3:unfI@`Xճ̪ΦǙ4)':w\s0;zF2=CX/zsܿ|W-ۼ";&ٷд׆+00fB %A؆LF&l 4x2!D]wVO3XtqYwE;;1e!!#bf1E4yLjQijxY!6QxZ>yVuijD!,q[z>K[f1b$H6Q 㩬Cr,xo)9ZBu( Fr3㑖D6SK'Fze 7>f8wi0wS.xx@s"&O_BЁgx8HkSl[ld V9wˑ#`vU.ԎIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/KP.png000066400000000000000000000007161404202232700225710ustar00rootroot00000000000000PNG  IHDRHHb3CuKPLTEGpLOOOOOO'ck*58BqxU]GP=GtRNS` U+IDATxUb0 E7,S0vx:g8r=g^NqVYuy}"Yp"*ȅ:k TX:#DZl:vfP u j@zVhD2Ȱ|^ PgIBtd\Cs/Cc[GC՝6bѲB@*1+@̣92^CaXGa4OG;E4:&M;i!5P6D={apB]j.w' C{~ީV.vԈ].~RW?k\h}IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/KR.png000066400000000000000000000014201404202232700225640ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL˘)/35;?װZ_bNSVswyBGJ⧳aw(H 0䶿oѭ9hc T2*f>(b4xJ%]5W#Y 2t%-kCDyh%|2MRqqT{=l8x`@GqWF`¶Xx$2M_,rYBܺ[Jϋp{,,j rl,N$M aғ(#$(#Rㅍ/F/\wFS~֎0UB֎I;WvF 7 ˇ(ct?'CX#0h~èx,7!5r,Q~!ߩ}PQRIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/KW.png000066400000000000000000000006331404202232700225760ustar00rootroot00000000000000PNG  IHDRHHb3CuuPLTEGpL&&z=z=&z=&z=&z=F/&z= Z#g5 :#T.t:%} '+!Z07 G)mtRNS `Ͽ `` NIDATxI@ DQC3 R^$N, vTdAܨw 8a΅Ύjn. !*&ăv\GJ;YkYG=B,k}%I#IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/KY.png000066400000000000000000000017561404202232700226070ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL$}$}$}LX .B!jP5$+$} qn RPD@]+@C`$Vh]gg&TxDp>b6xDooI}e~ᑙ7JsӘ\鵸;fӷX_R'6[ʫ"N~6wB&cZe9jAպL=⟥[rm> tRNS`Ͽ `H=jUIDATx^ׇn@al!sߪwlI iFH};dvڷ/#SF4O8aM)1/Azo ֺ@3̻;܇(}=YH79Ե!;&D[}Ĥz24pb^USj!F1HYC\1 |"wAePq]I\/!]-h<( cKxӺX0@ָ&䁬r>_B|Ϫ=0{ܘٜSH=+ !Kl˔-O {TFf:F|@Ɩ].)1{{ϋP,d[V+&% K&VC4iܗ6!SĿsuϷ Pf$a:~Ab^ ۠jZX Cs63p6C?ҫCBFCH %vBLkWX@ cruH LQXrJ hgCk7pChU_=L7D}7@9UW GAƢPv fPrf3+{#W }WPDL[k-ZsWN[-Ӟ5ӞY~/0[AIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/LA.png000066400000000000000000000004541404202232700225520ustar00rootroot00000000000000PNG  IHDRHHb3Cu'PLTEGpL&&&&&&(h C{5qQtRNS` UIDATx 0 YgՉPaPA]$[ T˃Y RSBM("UQR 7t!5[nl<3|[CIbPS= Y4>C~ķq?jP 1FPCXkͲvK++RIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/LB.png000066400000000000000000000007001404202232700225450ustar00rootroot00000000000000PNG  IHDRHHb3CuHPLTEGpL#####wwӳ--ŕYYhhJJ<<#̤6Q~tRNS Ͽ`g{!IDATx^Yn0 E$v'613"]HyF44O:-Mù-^Ҵthzg2ҩ h@w9-fhcb mP̀A^b g.@<,i4-`C$%Z @@B%'g1ͼjhy "ETR2GЕ T5 lP9wV݊YPPNQX6KOQ> I1mٚ`Rߵ7G~4n?N^GM3F/#ZQIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/LC.png000066400000000000000000000013171404202232700225530ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpLfffffͪyfn[ pP OA?4 3ϯ/' %?7@@@pppPPP```ϋߏ|~i OtRNS` ϤIDATx^G0EѲݮs9vajQ>/Ŷ82|grvTrT(AM9Y&)+ʥr`r>Tj@> *+C]Į!| C_8㗅[||CXgg| !z#Ć.,7rZDSxne{c!j% wb9n~P's 7hIZnR Y&dZnQ Y!d:nQ?ɝ! 6.N=Pw<: 7(1QR-֟uՈr$uNInG9^tqZB'%nPRĞk/JnPSP57=ʨwTqzRFNruqQfC>b}*w9+ҴrL*-i*C)iN i61܅ 1za/Scf:~v/*KG IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/LI.png000066400000000000000000000010771404202232700225640ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL&&&&++&+++&=+@Vo3A6{ Aw0:`lf.R^NϸIԬ&ʢ!8VPajE5ϧ##gŝZ _ptZ(w`Q0:@6 (PD0) pwb0Ks( tRNS` `Ͽ 8y8IDATx׎0aB7zHe{}'["ݍٛȿ# rn>=0[3 k WgM=m\C.%$G xQu2Bue, AFT X(U$hJ_N!)PBHR(P aRH(PTFl"$ڊ! J`HS`:&}Z8f 9Bf,A3\>??r":^_К6r ?k1cӄaNjc>czV{u:`$IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/LK.png000066400000000000000000000012321404202232700225570ustar00rootroot00000000000000PNG  IHDRHHb3CuWPLTEGpL!) lG=!*&4$uYcۈ PVA\=`z)߫[ϥ utRNS` n۷IDATx^N@@фÏ3/Hn],joGO'r_>'^@z _:wa0r\7(M&)L&'ߡ[ ^@r1W "7!qU M.܃D|D>$ r >P7~H, 쬳a{kE<(($E1 v*E&Ubl lMLZS2bif%ce)z YI0\AXk }FYiAINH٪e& q#kƮSݐWPn:LPTp= ] q˷CUnU狒a R{9w@\+?aۡh(U2 TĠ۞bR\;uC;5 z yrEΎ$PakWE+wbt PO5Z~èx Rۚ*k{ԟ]IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/LR.png000066400000000000000000000006661404202232700226000ustar00rootroot00000000000000PNG  IHDRHHb3CucPLTEGpL 0 0 0 0Vo 0(h(h(h(h(h(h 05q C{pC`⵿|0P`yPkjF tRNS `` `'IDATxɎ agc%龼S)w_ E?u-?[\ ͳܐ!5pݘ F>M \z(z].2!CdDBatLBg2oPBH)kFjC^Y!43yAnHӓF D1t&g3tܐ=g53dNa VD14&CPBrMBr^IHY/BG!9/d-өE0@S)i h;еP;qIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/LS.png000066400000000000000000000007171404202232700225760ustar00rootroot00000000000000PNG  IHDRHHb3Cu{PLTEGpLC C CC C C )/3Sdm5;?NSVZ_b>JP4fkmGZj0BҠkN2dk 2d(?Q zq UUX g;+g]-+}?.U/I[W'EJ US7bV̵R#5wG\*J\ q*~uNP;A>P 44LhV>%Ht.mC v::H=m+;γB41LFh`a؍ =ڠX]*uoU$ ݰ^Cۘ5y(+0~-IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/MC.png000066400000000000000000000003611404202232700225520ustar00rootroot00000000000000PNG  IHDRHHb3Cu'PLTEGpL&&&&&&jǞ tRNS` ` NlIDATx 0CQ!kyFpdݛRv䖐b) 4RXR"D<$E!BC-i9PF3w' z صH;sIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/MD.png000066400000000000000000000010431404202232700225510ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL&_&_.D.D.D&_&_.D.D&_MĔK&_rJ.DxJj}J"f۪LiGJLLwUK>sʙKMMN~LzTPR|TMKKեLQfHPdIRR`JZ tRNSϿ`` vQ7IDATx^n0EQ۱8oH^8p 6 gpB#s]41r޷΃ KCis>FrHȶ" P.3D !` ¯S} ~4 t @^= Ci BmJc @B C Eʠcy4g< !߮TpB#GTUR"2C@r";"" D^_J{wQ-vbkH®6Bh 5jc5^m7jϸ IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/ME.png000066400000000000000000000011721404202232700225550ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpLMMMMMM.DK8EiGLLFL}IVFJ"fJBEsHIȳ`eK\;ֹ[_G5Og¶FEp=jYn2IE\IPRnӯ[ׄSdWֽIJVVnkjWm2tRNS` n۷IDATx^70@Qi keZoՐF4 \okk7S[f:͛X5l/烷23ov8c;YkVȶ)vxP`EJD"H:ȁƵ5.hT.GhEjC@ `8'  aS @%A:O? 5hWc?jH"ab(f8U}Z0T=Pr<<:*FgSq;,M@Rxx:TP OwH!OtA4Pc0$DPސLHLLޭO[žNj}[!Zj[ZZElQIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/MF.png000066400000000000000000000003511404202232700225540ustar00rootroot00000000000000PNG  IHDRHHît*PLTEGpL$)9)9)9$$$)9$)9$)9 tRNS `Ͽ`cIDATHc`tea  * 5+8[ )PMXlŠ.1&QEF**쉪6ʈ^G}ARMIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/MG.png000066400000000000000000000004301404202232700225530ustar00rootroot00000000000000PNG  IHDRHHb3Cu9PLTEGpL=2=2=2=2=2=2~:~:~:~:~:~:R2tRNS`` ` ת{IDATxP  ^1N>Xn^Lǂн\ĵ|B`\Wn cB"D(!B"drnf:~mh (/^IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/MH.png000066400000000000000000000014641404202232700225640ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL8 88ytx8 88TOu D>8@jz.k_Xƀ%.Iz`˙u6zdPօCs@(HLTia=NqY|̆jG\Y`O׶hJqw-ZӴ1_v tRNS` ǿ`e`IDATx^n@`vzw*[`%G``ȯ>+sLϗ^@vttu6هR2ҖNrcAЊsRwR˙'@|K^ YHΊjmŒJ(jTCT1c!ƊJ"IY^)5P$e&RDLj3b$%n<0܇h$bL`D"FqG_kyPI2* ;3S%8@δz5/ ͳA yx.h,-:h mpFFC sJi?l+8ff;vj7YÁESn^@&HOm Bi֋m=v^Ǘ6#?H@~;l oc p5ny?vWǗ7k*g3)IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/ML.png000066400000000000000000000003511404202232700225620ustar00rootroot00000000000000PNG  IHDRHHît*PLTEGpL&&&&:::::&:&:)< tRNS` `ϿD_cIDATHc`ta.8B 5+::S ):MX!nŠ2&̨QEF**쉪6ʈ^G})\@lLUGѳ|C[NIg3μ3gyQnQn$7sq#Eg/F`֝\"<m(-;6>Om(+QYeyvwqW{UEs$#DlBˌ&< >Oujjޖo]ѶGIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/MN.png000066400000000000000000000007701404202232700225710ustar00rootroot00000000000000PNG  IHDRHHb3CuHPLTEGpL'/'/'/'/'/ Q$'/{<)G&f\!q2, QtRNS` pjYIDATx^׻ a'vp}Mu)چ*_P~€龷~~>/@{ 7H9@>Z[O;(E 8 48R8>ErYR9o"@ (҂%ѷ d# R9@ ě2Qh¶%WK$$ 4fLB mGA#P َ-؍&DD`"!KۂR229{w&5_Pw;JZ4t}l@\߇i"ƬFѯ}ņIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/MO.png000066400000000000000000000013041404202232700225640ustar00rootroot00000000000000PNG  IHDRHHb3CurPLTEGpLx^x^x^x^x^x^NH1/Q(?LU~:~Zn?6P`0|p r-$ÀĹ@hH;tRNS` UIDATxq|am@QNo-{wtϷv]`j,ٱl% V) Z0 B~ 9':(3Ege\ٌ ߦTד+KkZg*3vS(ر73%vʠg"x{;~гLȀ2;|ZPDO &[5-Q212ٶDC!h?ʙcd#f!L4{`@S(`RFDhv氱s45MhA%."RhG' Lo[{8_O^#WLAu8}_-z^v hVXYbWd+*HcR(E0DxE)9e"# LKn Fp{pXDsS4ɞP{o"B5&a8 E@3#̷QGķHUEHwFPmF_ XE':ܨ){-ͳQ\ p s`DhQ+r~;S]^Y 3\׬]^uy |y(IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/MR.png000066400000000000000000000007661404202232700226020ustar00rootroot00000000000000PNG  IHDRHHb3CuHPLTEGpL%@%@%@%@%@j1íi'$atRNS` ϤWIDATx^늃@ F_2w"tfeXR0"$HHZwtin2N&֧Z2ꄐMBHHd9$"29*"fHDiDY"w,ܳ E}~CףBQBDxL.-1 m"PG) i6/XR8`Di++Y ilLy_(i96bybD[ E$$ R C =~Y]8,K4y#ϛ!=c o[=edpvogqፍA)Vl`[!5KjZE#qIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/MS.png000066400000000000000000000015061404202232700225740ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL$}$}$}+$}'6Xb$}"xS+䳵1+<5~t>[*m $wz75<>*H` Fٔ(*q+2!`JcC1]ࢪRvGblQv/'#r6m2IfQ(Ţ`Hv'2 $Q!1]EݲOz;_ŧ+0$ RB.z&B~PN3T(42$^>'oK eHH._JR{QC9HZÑt^Aa7rw񯭫|"b0l%nP0 1Y|ʥ %ğOPqcpDZa@eԃ/ >gҪ'j'~4gZڢZK txlajU߈:؈H̛I.| NIhJmZ.[Us5/i==Ӈ,-kԳXZuҷwqu,h $4hᦐaXj GihBxҸW FaхM !fs(~ !6@H&BX)g\Ez(IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/MT.png000066400000000000000000000007231404202232700225750ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL++++++sK\=P渽hy{䪱/CܜGZȟ۩›Һ٬įǪãݘ✥W tRNS`` IDATxA ضgڸ嶲~5K㪛T̷YZ# _y42muR?B@ h<h:# ^媷l׻=|B=OKꢠuy B(Z.'4rIDATxЉ 0 CQN/?nqBP꿥aFz2-,[ݥi&R@FJВAJ AA')A}]- uӑ9Piw Zx;^3m7IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/MV.png000066400000000000000000000004751404202232700226030ustar00rootroot00000000000000PNG  IHDRHHb3Cu?PLTEGpL444444F~:0_pPx` SϵatRNS` ϤIDATx^헋 0 ~Og- ] tH_/AP9yTsRg07T T l9UM‹B4G_CZf6Iԉ͒vBJz&iD\i|_~i36ԆEav,BPYTm [_yuӓnIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/MW.png000066400000000000000000000007241404202232700226010ustar00rootroot00000000000000PNG  IHDRHHb3CuWPLTEGpLwUwUwUwUwU.DwU!F :-(8);+>#/$2l)S#&5y!,,A_&]ڱ tRNS`￿ `vm!!IDATxn@E@08gOw&FlȎZ=޷Ȋ9Xts.$8rE*qH&|RJg:q[:'@֔lJBf ֖>n(䣸XC<  Àr;u$%(! 9v</$.ʽ{0L%{dG g!\ec vƹj{ۨ UBCU /谍s06Pcy lدwfΫ,RsXjoy \;IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/MX.png000066400000000000000000000013411404202232700225760ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpLhG&hG&hG&hG&&\;fEw\jhGٯӝMU˭Ύ{ݯnO޾oiO҉&˃iqY_씶~YԘӷĹNuNzc~a@ڡBں֨ЌŠJ|]{NmȎuI4 tRNS `` ǿϓABIDATx^WOPMBilUNH;XQJ#{lyw:ﷶ]UKW0/'}#u&t܅?t;N3PZ(JaTU\J)ֱmK6$sǿBsN9 ش^z HLѝ[2 -21ah9,HgR yaC44%*E,vDj~ŷf3{J w3 ޝ WJ$,Dt(&]"<#"i"soH_<1$Pқ-c`TWVyLT-Bqh@M-@ tRNS`` `IDATxr0@SmbJq[%5>9Gx9dn.\8_#fԴDc>d~L7|kbŞj&3z3ٞ*D#0oGaUydQy{󨢱){WsU>)5|`, -LJvBy("q9W(Y9摻K)G"v6 a=FsK Dt@v/<4J^[9*<ܣF L΃70YHWB›BƸ.EUln(|Mޡ'NsT-sOU;(RQp3xCh9]d_DEaIY4fDEa]&TGFbc7EYcmȢҸfuXj-.\8['>TIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/NA.png000066400000000000000000000013471404202232700225560ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL"D4Cgb>5Cf{545C4@Av5ϱC`nP0RhpxH(@+K߻ 9WqXN⌝Un娴0Ahw™ǣ3gTGc٤]}%R@gӳwҹqe tRNS`` ` ﭛIDATx^n0EGk8խq ȾУ$xDeÆW005C57UƘ|wy=m_YLyz{`";<_NMd4p3?fz>M=T享,ݤ4ىom/ɻ*-7Dȩꏣ*-p k:=rpz_uj NPmHK׽<{?qXԴ®YmE? 㤢:,%-#Өqk==)臤G{Ml(zQF"":-y΋pt^"c>:F*Aѯ*yCtyqk#8:<3>1}:m44 D&$=@} Ͽ5001)t 'j/\Vjc +}NI yp ?;31 M:'"!1.y2 tRNS` `R@IDATxv0ajSCkwA%r x&Y򿃋`:{vt2EŚsVj.jE-uN=PZ?<#2C[VnH1hJ@kLј֦帋Yqwc-czptwA ACk`uڐQ`(ǽ@Wz uE9(wý4 dž3$Q֟Hm<@) )i_vWb/ Uj㴬v=P۲u8 ˲Ϫ;gVjTO[s:u؃[BIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/NE.png000066400000000000000000000005001404202232700225500ustar00rootroot00000000000000PNG  IHDRHHb3Cu9PLTEGpLRRRRRR鴗y@o2\ + + + + + +[(ntRNS` ` %IDATxD!CѠߵ^}I2nf9;11Nty/ (.Hr#, *Q&jjjpkå%.ԕCu.{)Cћ?~?D}dA1i  `Yyg]fz> M#,gol IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/NF.png000066400000000000000000000007051404202232700225600ustar00rootroot00000000000000PNG  IHDRHHb3CuEPLTEGpLy4y4y4y4y4y4-W@JnN V_CZa>Zn]xA ڂT#Kv2,v*9١vH1"3$ZBdʑ[`ڌPi`3AŒT@:xH-l@ b*1#pvԈTd6~p}J1{34$Jg,T0BĴMB4$I("Κ(BFN4uO)BYdx; ^B}v 5jf.\IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/NG.png000066400000000000000000000003201404202232700225520ustar00rootroot00000000000000PNG  IHDRHHîtPLTEGpLIIIIIIy}tRNSϿ` 6<aIDATHc`tF0 500RTXNHQ9aE 1VT>hTѨQE" { * 2*EQ@_ *0rGSBIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/NI.png000066400000000000000000000011231404202232700225560ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL&_&_&_&_&_کLщ&_ם`ݯVF痴H܁tӅu߻܌uNwRXRdWo𹠽ʐһݗeåwvkfU\;_G}IÃh܂kmH}qu LtRNS `jk4U>?.TQ 9?!Ѹkhc{i%<šk$:kD8ENR$ ~~ȊJebIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/NL.png000066400000000000000000000003641404202232700225670ustar00rootroot00000000000000PNG  IHDRHHb3Cu*PLTEGpL G(( G G(( G G(( GK tRNS ``2nIDATx 0-{g2X4b]EBpIiwV,!1i!r?rȡ$rClMhCQt Я|t8H1?$g/IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/NO.png000066400000000000000000000003451404202232700225710ustar00rootroot00000000000000PNG  IHDRHHîtPLTEGpL+-+-+-+-+-+-(hOhtRNS Ͽ`sIDATHc`ti^^^ёL@j+JSd`` #(U)47ŠR+JfH#(mTpWԁ`E":hUF\aOTATDTUFTH\: A! IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/NP.png000066400000000000000000000011671404202232700225750ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL"@"@"@"@"@"@"@"@"@"@"@"@"@"@"@/E"@I\.?0IctVh5_1N܋6dp4[\;u8hRqtRNSp0`@ Pߏ1IDATx^n0V2CR+Ǻ>9E hl,_9lZj֬/jiӎl%IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/NU.png000066400000000000000000000014431404202232700225770ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpLPPPP+O$}LX+$}Jw+P$}40h3Q Gr1*H?ɕٟ_5Cᔛh|'-B(7z䬱湽-i[`hJc@2r;IE߅_j3|po(H>7aAj+7kAۄ@FnW2ɍ ) 7SD . 3YR?sؖivwDd /.ReiG^Ct\[4#mVD:5\ݽi";j>$K7Q#]*GZDp$ QvNi4Q;rE&S="vvcȩE{::4e_9#_TJQ)*EȻceD>c%<c +Lg:qg?w-CS~ HY|ھ~Cha9GR?=Sī -Z i _x~4V7!]7d+"~/,Tgo2XpXD +irfk8Y$srçZJrp.`blf}H_>v @~n+iMIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/PA.png000066400000000000000000000007261404202232700225600ustar00rootroot00000000000000PNG  IHDRHHb3Cu{PLTEGpL4R444RRR4R4R婴cz@\Y⍝,KfHc˙BO.h*Ey9q&BRpDF4 ^PMIt y5T:>Bt9ti)9QFpq> @2!BBʐsJPJP:cv0Gv?2=m8&8? Cw8u%hg:PA+5>phRQ(TV4!PamE3lIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/PE.png000066400000000000000000000003201404202232700225520ustar00rootroot00000000000000PNG  IHDRHHîtPLTEGpL######6tRNS` ϿCaIDATHc`t"0 5100RT\NHQ8aE 0VT>hTѨQE" { * 2*EQ@_0rIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/PF.png000066400000000000000000000012451404202232700225620ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL&&&&&&⛣ˍЛ:LdqHƀ5թdvVڷHX[{3ꣅwuWڀ̌}3:PUt%,BHe ڦBj5^%߿Ʃ{Sf)<>%T0JTtI^KRm{Ɖ^etRNS` UIDATx ᙼ(h?f[)?劃]//ݞ+O` CwK}zg' D$X+:ias<-|O"JܟPuo))Ф i" JtQ 21$sQ#߈YAZU;L0N2< ( 2,0n,6(2 Ӷ53/k >+9GdzLG% aoAP`nai^+$0/j9M"ї-JS 8H:{vqP: ICPFZCrM$[[N:%UYK"8EqEP؂GY<8d=QIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/PG.png000066400000000000000000000016371404202232700225700ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpLq&&&&&%% A"q%Y &)$@@@{{{]]]5#}M!OOO###e111 llle$7 }%"N+#CqZތtRNS ` `zIDATx^r0Pʑc)콫pgDF ytu Cs ^\܀tEϗ&Ø&w/aDji˜xǢ4w 4ޡR;|6qiFyPCBe0B vhQ%jY8G "C{nږv!zLX #1oBZ˩ +&Ld{' 'ƒv Uh5vm:gsm۳{׌'_1Nk%|` 3qbcKAdڲiox{^8T'q $NM5;΁T`^c˕^{][gy mUzBk,R oy-*%^cP/U ض,;f X!5A"e8yKոd4QFWO2uڢjEl@o:CB?, J_*E],z!maYdWcӒ >5Ơx5ơj/-@xqVcan,q@Aw:FȻߞ3]3d2$  %|f}*n. & |6͍Ws1&@IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/PH.png000066400000000000000000000013571404202232700225700ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL&&&&&88888w&r~淼-?:Lހ3LuZ$1?gwC-Z8縆ꤵس?b:xW7s 47%V%"Fh nyc _ [`JF䠠2??"H okD)E QP4"++9mxZF6v,E]"c([1#rGᨩUH>Đ=T-QSD2!k$c&  '?CM,Sk6v[,l0P`@{RMX?z^8p^߉3Jھƺ[4tRNS`` hdIDATx̵Q@wZ\ F~~;H!Cm)(uv0_X),x+bZ\z1߷ZwO>UMx[D{"4א#KtΣ=-B̑ $Le{/(1d@l?+R\gɷ(͆NѿִǮ ijNlm->&fʇ(f sD@WԬ7jgOX0xhe;nDȔ=X|oD{^䚘g!6a'Ee~, ŝ#V"oe-UDa R=")9Oe{9Xٞ!N^~.ԃ9Ki?hY3%cߏ(:HO*Q2" |S(:Pc+GՍWd.Eس`U$N۝( DH*2Y}[צ(@umgC=io[Ih_dcqʨH']9cPE)FC9k@E?du tWSK`ilSNU#V<_Hcq.JStk&ht ,".Q-\cLgh@KM\G~t|xƾo«ۯ_ ŸˠQکĥ^XI*S^vowy;[;bo>O 3Ŝ ï)e;7}&>nIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/PN.png000066400000000000000000000017121404202232700225710ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL$}$}LX+$}$}+f$}+ 2NBY_un9sKv 'N(Yartl=9cgz5Cᔛi@_峷mF-(>Y_IuZe(7CPKK`qNܡkV_k tRNS` ` [7?IDATx^go0IvhUd+RF~>P0>|yΰy9q7168!^ a{wo{͏/=!9|I447~oT E+osF=4 `2)ASu_ i= Tv2*C+.R~[jQ%?BTUG@I_~xȴD69/U"2i㢧mD-6b-&j K+zcP#zUcsg6=Zht݋{Ek~j)MuKOsl"ws Rn{yaq^$؏T=_9-Eؠ ?N4mS^,.?(A0B ²(+0ـ2"'~2p{&DL|zj.!"i^@k+'rM֖l%AIlmnYp'% w̭ c(J|:,Ȥn-HI#s5XVbEn.lb3\jK,C'u\[[~Qa cLfHm@xXTF<"e#¥go"fk:vF9wRHu]=Ӯq|vq"i5,25ΤP4U8qeIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/PR.png000066400000000000000000000010721404202232700225740ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL U  U  UUUU-ja93G{8Go5+hUQ tRNS ``}9IDATx^n0EQ)EJjq%4ΜYJP6apEQ920/SÕέt\)+ئ\) \OP AB ͳQRW!L~B i쪗 hgoȐ෍pPyT2Dd9 )"*Cc$]yZ&sL8" s 5GSH߿C8:+߉7;k /# tRNS` `Ͽ 8yIDATx^ǎ0QOL_VަRaDAjoa`b>݉D=91B E 裓b8D|Dn&wa5EE%̕У658Kz~ * c ӀPRlڬd~УBNν ( =>P6ܒs#\3PR]HZcjQP]rObЅ )1 YlH6'h!9[н!Nb )x@Ǫh\,%IC Dt!Z;*{~?rCҝMHHT\\~(ahK4V~AgQ.#oaS"zaUPH#}ZE-Svw^{w=Gt*zF[+uyM2 e9Dd5/  R*n +ҝ+!IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/PW.png000066400000000000000000000005471404202232700226070ustar00rootroot00000000000000PNG  IHDRHHb3CuJ)Z6PMO'/%[w;ZVF㰚f:>vT-hd[@\ vic1bi"Н dz@6!#Έ279휏`j…<;Y Q5 &߯歳U_9uL}-]"!"&x[]jc5':>vΞbCSƕj;|ˣ&BH$Mݢ:2^O[EH̶Ecth0ı1T"PC!C*1@CY(, DC"5ʡ;>&P'rI=,PWA_A/iS XyDQ?-~1R`Dԏ3`DtccD1-Ft8[k󕇿5\].,/`3=y'-|zǬIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/RO.png000066400000000000000000000003511404202232700225720ustar00rootroot00000000000000PNG  IHDRHHît*PLTEGpL+++&&+&&+&+&@ tRNS Ͽ` `(cIDATHc`ta.8 5ʻ+:4S ̻ ):MX iŠ2&̨QEF**쉪6ʈ^G}Ɓ VIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/RS.png000066400000000000000000000016041404202232700226000ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL6<6<6<6<6<6< @v.㛞/hm0W9NSy5diBGވ32F:o6絶x|2>;c7ހ4ۀ婩ߎO9ڒDK\a1Nͧ8?@݃UCr㚐[ڐ=;?Rf`0 tRNS `Ͽ` )3hIDATxYs@abY>cC (j42Wsdt`LMUOqS8vv}EzoI{IuO4}Wž'E]vUHޚ杌&:7z/IM!'ߎ.VK *FvhI%@EH !B?J-'*>#|-*$"Ef zK;m'.Ɉ C6tm>両ּօX]I[bn&E{+<T^f=ICo3tS Dzj9@ZCS-7W486gO{8>rBB>,˥eY׬>EUՋE-bXCCޡ-CͲЃ5@C5!*rB2Cb+J[Y@7GL U\Ӏ2`.J΁L**ƛ5 R б֭rЁ.ͧK Ղ#n(B b) d;H67 0H6-3uƥ.@80Ý vu.s8v FȰ҂6IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/RU.png000066400000000000000000000003661404202232700226060ustar00rootroot00000000000000PNG  IHDRHHb3Cu*PLTEGpL ( ( ( ( ("@ (" tRNS ` `]ZpIDATxЉ 0AKo7ؤSm:QnsEstsקPQj(H t$H AC)A )H(Ta0U*:˭?wIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/RW.png000066400000000000000000000010631404202232700226030ustar00rootroot00000000000000PNG  IHDRHHb3CuTPLTEGpL `= `= `= `= `= `=T/?¼8}p*m}^bN%` tRNS``Ͽ OIDATxn Eqwv&UU,p{@|.j 0on~@=z5 y)cyx@;Ȉ'Ug s FbfN\l'L4'JDrID^/ %X\5@0"ILVRizv$o=ZDAC4'M"?Z91#8zX^EkGߔ/ 7g,67Kc&rbZ?EH{asu/z7"ݲQ!;m.EK,l㹨aѠy Y)u={W%\pyP;;7:5IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/SA.png000066400000000000000000000011441404202232700225560ustar00rootroot00000000000000PNG  IHDRHHb3CuEPLTEGpLl5l5l5l5l5l5򀶚ٟȳ ~N@huB0[p`Ptp5UtRNS` ϿVyIDATx^j1ѱ=^Lkt{BvuoxǼ8^ҝO|^nsǍOB4UY;,QlS.mޘG/|č0y B:,s8Ʈ Vf`a`?Z% ;нFNS,VJJB7zC":D*Sh! (fnM g%CTWMNpg0x*m -zaY@L fjȉV̑FFC#t̕P -S@cbi񎚪 ̒ₐ]r PcHi"@OOMFb[j 4#5tuCY!*C`F0s32 3~VZگCENOBwO@; DNDܣ :~qPsژuw(c+z!ctIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/SB.png000066400000000000000000000014621404202232700225620ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL![3V{Q![3Q?qXy,![3ս/ڿQ![3 g\p`0rǯ/b1QYP@}&nrŴJq..Ac8`s0(9 B!BE 9Y:3da Lsh2IùJs,r`7e"?"Rk\$+bʲ+VZ1!IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/SD.png000066400000000000000000000010271404202232700225610ustar00rootroot00000000000000PNG  IHDRHHUGIDATx̛Qٶm۶m`hb.8Y`?Ǚtj{f'_'  . |XJ$ ݽ/>wvoyd4(H TD]]Oa4 aqC ' r 'brAON Մtr19@LtrjY ^wr@2un<Jbؿ, d z#&w]~rٓȞ@Lq bbIs1OX̬ĶҨQT~k7۫y_P啝aƙ=)*kRUY!פ9uPbR4 GĤ &eEpm (\=U0`JN* 8Ie)P|4*Pa"%  =/nsDIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/SE.png000066400000000000000000000004001404202232700225540ustar00rootroot00000000000000PNG  IHDRHHb3Cu'PLTEGpLjjjjjjjjjjjU{ tRNS` ߟ@/dE{IDATx;@a!J1."΅{k'.IV 2YXR1FĆ@j 2dhXhe_A^!CBZi ځCz֡8BUWԹ0[}ⰃIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/SG.png000066400000000000000000000007111404202232700225630ustar00rootroot00000000000000PNG  IHDRHHb3CucPLTEGpL)9)9)9)9)9gr)95DN[_k6EDRQ^ylw ] tRNS` Ͽ `:k  IDATx nn Î?M7Ph7e)3_8astΧ~ϖ\O7[}КJ??sTBN0j;qj8V<4slbv蜜:.d15Ջjǂ^4^4o-aN7RD nl56{pdAVKe/(߅HQH!RPI y 'l&wfLR.^OSh6W!?}jxIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/SH.png000066400000000000000000000015631404202232700225720ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL$}+$}$}$}1'6Yd$}pb+ǴnMM_>P4Q'8,AgBQV`w=dh6R'% tRNS``Ͽ tQnWIDATx[O0q;;1* +I$4M!*Vki tz&y5~J2Ëp #ɒxq=z4ZOd&(zߏKArz<"!CBEęꕇlp9#*C<;6}eZ 8B,F@KGay(FgΞ33\ڴpR e"4ALAcYD|!UAU7UAC9t$BShD->,fgTG/9?Cch qGWn M}<_!9F SCHZ(w3D'0ALJC >1C|wd9@/H$ ('{H7 ])]fzc:.clg vJ:pzmcMM&P%IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/SL.png000066400000000000000000000003641404202232700225740ustar00rootroot00000000000000PNG  IHDRHHb3Cu*PLTEGpLrrrr:::::rr:J?} tRNS` `ϿD_nIDATx 0-;g2X4b]sGžpIkiV,!1! r?rȡ$rE)TMBWt:|n48jI\1?ܭYaIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/SM.png000066400000000000000000000015321404202232700225730ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL^^^^䋲td~aac쥱fdjaoYѷ{U]n٤=3qhe\ԣʿ*ZɅ;ܻ͑ĮSeᎪB߶Ξ-ũ ѭUP tRNS@``NK>IDATxn@aZE<mpPPAQΚݛ.vL&rʽ{&qJGWr*j J-ZғҬj*Ys?8U tC=@= Vڪ ]®78fT6M%JKᔇ"-!{ǁ>>p$ y}`~ƎOŖY;d6;s&W7IV~/ "(W(KPT@,OY(kyf,\]ɹvr K3dwP 1u,:E[wn(IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/SR.png000066400000000000000000000006011404202232700225740ustar00rootroot00000000000000PNG  IHDRHHb3CuKPLTEGpL7~?7~?7~?7~?7~?"+]&7~?, ޙ!.* -Q'i%u$ׁ#ۍ":)E(=tRNS` ϿVyIDATx^n! Qm%4vƿ"؜-!rլݘ i&I.r Jn Uh܏hHu"f@,cO-cNOMLm33QH9)Cd袃b25IQMiy+Ğ*LJayz }wTdc;!PG B~SjoOnIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/SS.png000066400000000000000000000011721404202232700226010ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL00G00GGGC0G0 \Ph0nD(hqwf= |J3P ^5=-#IGq8-Z;cA meRQ*X?4u7G'N# hp.b=Yv{JmG tRNS ` `` #RYIDATxr0EmSLKz/WQ@iƛK6F3:#jiY_w:Z@RlmލQцP<>&E\y$~Eq*s~|o1eyem1mFyhsR뉥ͅ@ѿ_%*z@V.} ̲.pg2]Z7QY~[h@|<щQg̺([{Y}Z(>,;,I+%lHiM'j|7 C,0tnY"IU$p#0KCaTY"T@'fixTJU -h8ct3>h""0&,dDE%1C){, 12c? Ȣrǀ)yL$̈́]m?w2IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/SY.png000066400000000000000000000006371404202232700226140ustar00rootroot00000000000000PNG  IHDRHHb3CuWPLTEGpL&&&&&&z=^>^>^>^>^=]EYi;hMO !5>^@@@~*I\Cxk4^u2W2000M&žhh⤤ӆ ptvAo00coSYYi2L6Cx@ JJww```hX0#aUq$RtRNS `K%IDATx^׎0a^m[ aN;_p8q|ti7:p]\CI^_ :g(W:H(A4 F_ =]amt=kѩ/m$!>}ƘF\[#(Jc 7T}cP^62PaJ?@[t,&C{-I84zv9Qs^^c b& 59Aژɉf0@rm>ɋCűmqmT~:mGUhK)%75$By UϪf/m!\B('LT |s&@AV}f猆īB^DCR Tqz%aH~!b>uũd,:'Qfn E-sYN -66=T0n%ż?e+G˥ۈ3+;B:(;f::m. 8UIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/TA.png000066400000000000000000000021441404202232700225600ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL$}$}$}WbB!j$}1Mlpثmrls/AzsӎU\xˣnjtB[9X*p e+/KɺxsAKgź/"絸CPv٣#.=˛)?`'NZ˔ˤ tRNS`` ?f|JIDATx^ׇn0`M{;{ߪ<)M"(¿aGSN5 `\Pw>3GA`q[*^"P׭~PR)$c؄u:]\T%bM(N {41Ƣ1 㒐k:5P> j@tS/@o7Syv$Hӣ@9Y|! Bf?r?D("z}#>d Q"L84^AnK1on뻻*{nk Ǹ:qbEw75 44vM!y+ zrrJW*U7x}=OP("+{}l1$QuV+5Ov_ǜZPθ̡e9bH*Wj̵ Uy3y*v&4!ٔvUJ[BNeI[m5~3!hC(-x۱>>C(y>e ?Y7$@n3 u2@qJfwn [N:3eզH7 &IIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/TD.png000066400000000000000000000003511404202232700225610ustar00rootroot00000000000000PNG  IHDRHHît*PLTEGpL 0 0 0 0&d&d&d&d&d 0 0&dZc tRNS` `ϿD_cIDATHc`twa  B 5w+:] w )KX&^Š62%hQEF**쉪6ʈ^G}" IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/TF.png000066400000000000000000000010011404202232700225540ustar00rootroot00000000000000PNG  IHDRHHb3CuKPLTEGpL######10L`v@Zʟ׏Php ?)9FrFtRNS` ϿVy_IDATx^n@ QHߞkI$hTU!oaHFbsj;HX}ZSn]? g!DR8A5Mw@h=P=> Z^i20|\8clr  !] Iڂe` 8Gm& !ynVCm!d`H3`q 0V 4+B)ҁk2)7H(d\|#J@VË `#!<]dzT}vjycj"r5%-+8z81-Es=b<4k@UZdB9.DS5Dt-]iJrV:?TxڊnPB=uBjϐqhH @uR彀G` GPn(y{ѭI\|r;n% Jxc$dYNAM{=@Y猩BF'# Xtw~x)~ +?7idCgLP'AkH-9883b1\@8V5CLB$W&5|s ONF9(h5) .vLI(z{Yq /X[B1;@5Z@y RR[إKP~RCWIQL ݐk{ޡP{CM"mqmm5EG)IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/TL.png000066400000000000000000000012251404202232700225720ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL$&$$$$V!&.@@@#8 #OOOM!$%&lWv####%a"@6$ӥ#C OAk"{{{lll1*nĚ"y]L]]]$W!$ 111˔A tRNS` ` ϕIDATx^n0 tܣsVn4)@{&MKCC=an Ʌ]Eٌi:զKz>3&! g%As3q0B2L6"bZS_|S;m"u*`lL͘"h|HaAበEeBNZDLEoҊ4V#UDT(5&&MK6pz̟Rk9lQ׳ѩz5->C>GG`\<_Œ]XFIO[3dDy2cF봹=bƮ81zNmVނ\OruNwit_lǖ嶚xʼnF6EwH *)k(-%Q;Cx]$hI l?ZL:C(UnݼeCA@uKuALd<TX 勴<ґ ( &EgHqWjCk ʣfҘ-v\efp !C\ MؖED!Eط7D*.9|`g  \n :CȂOc$-XniertSqjm+@'i$ϵ䉓x8sљ\@lVY81GJ)EhOd2Q=#p /d = [껿C]~HȊݥEz*) -%ȱl(`8&dAȂ7'͠,?qq3#vOΥk4h!*%ICF]ݼ.5`Q:ݜR74|[*Jm@5mϳ&y{{[E]=Wg*܍*aIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/TN.png000066400000000000000000000006471404202232700226030ustar00rootroot00000000000000PNG  IHDRHHb3CuEPLTEGpL"`l 1@N0?pzP]QNEtRNS` ϿVy IDATx^K0 ER̘q4Xn0= om^j aOw#G4vEMK N_MMDygurE4'NgTtAy9fW/g9V(RXA28$UP&*κUX,G9A*Ef=6ńߧDR!"F(6sDX5Fh5jß/i"ڃBښZXci4-ݿV1IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/TO.png000066400000000000000000000004021404202232700225710ustar00rootroot00000000000000PNG  IHDRHHît'PLTEGpL:0/6 tRNS`` }IDATxбA1ES$U::UZ N{vinJ)3EL#~t ~ u-jO6FFVTaPX>7s{z$IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/TR.png000066400000000000000000000006641404202232700226060ustar00rootroot00000000000000PNG  IHDRHHb3CuEPLTEGpL     4?$ BMmuꘝ_h뵸릫QZ|&2銐;tRNS` ϤIDATx^[k0 F&{nkҹ0CO0(> dY}.;~Ռss049 Cۈq]D|'ED9/|?&\YWDjvjCW/O&@͓'SYUE\>"-e)rᬊ;'*PK0RDp%`%7B5I˶MUlmolxj8"ei" aԘYV*4?`W(IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/TT.png000066400000000000000000000010201404202232700225730ustar00rootroot00000000000000PNG  IHDRHHb3CuiPLTEGpL&&&&888&峹&"""3:L===✜dq⛣fffހ淼///-?䩰 tRNS` ` qKIDATx^G@CQJrf+8xC rjQ7wV!Dhݫj YROjoB*$ҥ˓J:O*皕))BF% EigH K2M0j.BtL:I iK iiI2ă-d[S7"dKo&m4(JWI?JmmH2Ļ#IP7iVl#%B ~F8EI f @j8ARL)'N0E Cj8|ɇp%ٖ $@<hOJp`:Tf$$x0B\YUOsm[GEIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/TV.png000066400000000000000000000017621404202232700226120ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL[[[[[++$}LX$}[eYzz+M,¹B̽7 pdo!$}15CCPwh|(7-J䬱湽]h߅ Qa L]~bDx%".iNLHXRf@~'@IAXR3UQqoPI)MIo #0Rh#Hj`BARF4 Ѧ8OAFdR<iҷ@+dM&?a6ynVpӘs"kIeD]-# A`gi<ۅͤktIOPK&=GPWjo*`%N)z_-l@p i~nP6 2@9)PWj00tI \bhF I1 73(IAtA X,SGs+3(Qq.pKJ+.U +ND)QHa;؎(1DV;$va_e_ L`;*84\(8M@FPrh#@R74=rk;:,M0~M:<@#|[_6ŃIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/TW.png000066400000000000000000000006361404202232700226120ustar00rootroot00000000000000PNG  IHDRHHb3CuNPLTEGpL``@@ʿ叏PP % tRNS Ͽ`` 3ūIDATx^n TY_Զ&&DV"d7&l:R SR@vF2`y8>Ϟ%`QȀCNQB74D&k1ҝ(.D*8j! ѳ*Ґ4G I (!ҰACJBHj#Rhk 6UiA jPdq s `p,t}wPdL c]g3g|0|ʚ?ǝpZ-Ekwi7h jhhmiD沚錼D*jM2"OLz^(C#7!2섴+]BКU3HԚxJ+;$i ^XDC57!2Z.54'E_}g }Ԛ(G@қ]V`-e&B&Kb zşDOmAKo!z.kYC˄&f3[ꬡ 75`-y&H詗`hYCVti)M5֛`-tYU*M1[jZ >>`7Z6Sg-Md4AǜB%s:mԤ&H+!4AZ8<x>Zj4Aǜ ZcΡ}w˞=n`(IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/UA.png000066400000000000000000000003571404202232700225650ustar00rootroot00000000000000PNG  IHDRHHb3Cu'PLTEGpL[[[[[[F tRNS`Ͽ` >lIDATx 0CQȓ?oը@~",{恬LSm;íOROH)UXd"DyI!BJS 9#;FxOvRB? 6كuIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/UG.png000066400000000000000000000010731404202232700225670ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL111lll`` !@@@OOO./.###J//tFF]]]KMKHANFpp뚙00TQڵ+(PP>;{{{B75ʱ`2h tRNS ``jb7"IDATx^ǎ@E6`qsNY6՚w:*Q3YΎ,l (e Dk!Kdkб7dEcs jyP( ;)Ķ3T(ۊT36IצyzhxOPGeњޢP;o@8jU" q~X+ρ b?DS 􅻖 ?5]g<gLDHIQy=m&/ K,IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/UM.png000066400000000000000000000005401404202232700225730ustar00rootroot00000000000000PNG  IHDRHHb3CuHPLTEGpL#4#4<;n#4#4#4#4<;n<;n<;n<;n<;n#4mlHGwyxߍ7tRNS` ﯿ``_[IDATx^;0 E HLmUytu<˘MQF^ %p·JCܷ*+pD+i ?G+i+кmkb ';Ov(Ύ#vG#Q?PsHb%ߑv4h b} ąq7Z(_IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/UN.png000066400000000000000000000014251404202232700225770ustar00rootroot00000000000000PNG  IHDRHHb3CuEPLTEGpLKKKKKKm⃴bx䏻VaubtRNS` UxIDATxG@ 0K3:@׏*պXS9:#Q"IAY`!_.73l/[5hQ9pD) )`@#7泏SK!Vlaٞ3:P~i6KиN =J!090eA%3CCHG#rφfG9K }ǂP+w='h+Yf T+$,L?)oO؂EC|;܆CJP5W-zǖC)A:;rۨi6B#PXیYK B%u织iߝ@r~o@P\t5A_. Q_t-~VѷߥK?Y|n#IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/VC.png000066400000000000000000000006671404202232700225740ustar00rootroot00000000000000PNG  IHDRHHb3CuEPLTEGpLrrr```rr``r`-)_D?NOI[~;项ftRNS`` `IDATx9@a%2pΦB^uފ}w5mvɻ4gfx$'i6sv\_B݇hO\:ƀ`C IKj$5l)ANr2P# '% 'Vz@oMAw:I''r'QI I,$NRB8I<$9I֧ 'B^NB$CſnI~HDBj ^vBT5egVW{=A7@IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/VE.png000066400000000000000000000007041404202232700225660ustar00rootroot00000000000000PNG  IHDRHHb3CuTPLTEGpL$}2`vPh ?p@[ƿ++++++%ItRNS` ` boaIDATxЅ0Eя8t=M nA9K&N"ǒxsLJC (| R> ^|vTJ8@R L@2}@QV@UjwArfAc':MvRJZ/ko"hMz8Sr'*k%+!d*rJT\dWGC̓,Cj=G8= 34m蝿gZqg&:7wIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/VG.png000066400000000000000000000020041404202232700225630ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL$}$}+$}$}Yd+n+a($}E[+O}2G u/t(m88L ?Zh|ᕗ.G~h4M1E\8ъ]x)w;:B=boDvqk_7' yF[x;<7nCD">ZnmM>x Qp=97|`>\sw,xs,)] r:U~rwcCۦG'Q[$w)FD9/P9j*>uX#j 7}PVtڇze%DƕttB[*{U(z` qDyѠN9$lK hoP+l:5 ǸՏر?~yyY]] ED+Ǩ]Qք>꣑ &Ѥ) c<.=.yfX eGC 5g!H1ٿ.!Ras"+(ϩ!ZclYjWiqufiU8 Pe0ȰYS R%&ަl=<ٮaM"&idr-%k-4MH``55mQ+ހ|\:M0!&RKdw?v=eYBMhMu]%c{A> O WYS(@$+E4YDQvkX;ܕO{ߋf1+ QeS"Y@HBL03`6( YPKm{}*7LS`zR=Ҳ,8C\IN|%;>p;ξcDSe/j Pwb`zdC_VxrE(Gsx%O2+ A3@2U| )am 1צfm\mP~cIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/VN.png000066400000000000000000000006351404202232700226020ustar00rootroot00000000000000PNG  IHDRHHb3CuEPLTEGpL%%%%%%N @\3 w i JtRNS` UIDATxƒ a4O%ߴ{BYN$)BYA٦3hu 1LN*rCJtB)ĬV)NV Y4BVYXÊ(C6wG75;pC㰯&-δ;"# !;q HpFUl Lg3Ϟ/%#C=}XH0љ .ԜG,aW;BjYj?迕$KO{6SSIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/VU.png000066400000000000000000000014301404202232700226030ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL4CCC444C81C42@C4q1+NC# à]N@7kZѫze22}, H'@D=  X,,$g"s$u70.}:m4e1*O8[ <#&(LtRNS ` ``ϐWIDATx_W@GG hmYw$P۪ +ɩ/웇C.l~;v_Fó#hpxy>j}wgT&Spd!4cyBMLHoa$lk~X$6l"=aeThB&yR߳X}>^ E>zh42͚Q*9 DǣyB(7ƧǣћQNZ}<-Xl(z}·ݑуD_I1^dmIhDF^j" y,r%'"TκiY`Q.̶nvj0fUYy*/9~>yڭa$_rO$XJjvM\zM_@*~(%7%qi[?G?UЯr\%LJU*TeMRTk%QbMU @Q"8nBx1 &<9oWI9ه^qPy8F3W(ShhIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/WF.png000066400000000000000000000005651404202232700225750ustar00rootroot00000000000000PNG  IHDRHHb3CuBPLTEGpL)9)9)9)9)9#####)9#6EyDRlwZM tRNS` `fq]IDATx^ѻ0 aB& S3!O=7|W*\{9 弳J)Y%G4dgD$BQ8PA$H6ܤ $(R 8ʁ*`!ͼ3d嬪 @ eUtIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/YT.png000066400000000000000000000022401404202232700226050ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL䦛jV辷^H㚍ᎀ沩F-.:߂rvdR:ቇ䃃MMZZ$$??י՝]F31|zzᕡAtRNS` UIDATxb`+th! PXB~wwX?Cfi3om nEE o-+l+rrw},ԛCaK:cB$s@Q&6 (;*ZZE􏁼Eiǝ#gߚ/TV(wTy(1q׼*^b$wr][b ED1Y)gE<# }Urɳ3 /LR Q:QH8=? x09rtT1E@ytp.<"b0gE\!{X5ѸbD 0 (DzfӐH@0I]Z#a)%hhm>g#!T+S"WVVmhڔ-M5l[Hs1wáģuZ&)khFn[(|E5%YZ$ /)ۭYzZk6Ekjvn d, mZ1&6j 򢪎U"4n,-2*jZB(Xsd%PV9,B9|͍ "'5,%YbӬoJǏZ]a F(^̴Ԓ9.IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/flags/ZA.png000066400000000000000000000012451404202232700225670ustar00rootroot00000000000000PNG  IHDRHHb3CuPLTEGpL8080#zM#zMzMzMzM#80#80#80zM#80ϫ/0BeӘ@2Wa.D[.yw-k\-[ͼ<==d{F`!5]>[uOlM-]~.*rUd?W |oX3h8Q<-jWZթߝDI+c2$;hiX'O Lňg)byb e>qt&ZD@uy7h(L~=BdL=cat#?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_pbcgvnithqeutjz]alQIk{`7kwoxZInterUIY     >  UO UO  XjA"i $+29@GNU\cjqx  '.5<CJQX_fmt{#*18?FMT[bipw~ &-4;BIPW^elsz ")07>ELSZahov} %,3:AHOV]dkry !(/6=DKRY`gnu|$+29@GNU\cjqx  '.5<CJQX_fmt{     # * 1 8 ? F M T [ b i p w ~     & - 4 ; B I P W ^ e l s z    " ) 0 7 > E L S Z a h o v }     % , 3 : A H O V ] d k r y    ! ( / 6 = D K R Y ` g n u | $+29@GNU\cjqx  '.5<CJQX_fmt{#*18?FMT[bipw~ &-4;BIPW^elsz ")07>ELSZahov} %,3:AHOV]dkry !(/6=DKRY`gnu|$+29@GNU\cjqx  '.5<CJQX_fmt{#*18?FMT[bipw~ &-4;BIPW^elsz ")07>ELSZahov} %,3:AHOV]dkry !(/6=DKRY`gnu|$+29@GNU\cjqx  '.5<CJQX_fmt{#*18?FMT[bipw~ &-4;BIPW^elsz    " ) 0 7 > E L S Z a h o v } !! !!!!%!,!3!:!A!H!O!V!]!d!k!r!y!!!!!!!!!!!!!!!!!!!"" """!"("/"6"="D"K"R"Y"`"g"n"u"|""""""""""""""""""######$#+#2#9#@#G#N#U#\#c#j#q#x###################$$ $$$ $'$.$5$<$C$J$Q$X$_$f$m$t${$$$$$$$$$$$$$$$$$$%%%%%%#%*%1%8%?%F%M%T%[%b%i%p%w%~%%%%%%%%%%%%%%%%%%&& &&&&&&-&4&;&B&I&P&W&^&e&l&s&z&&&&&&&&&&&&&&&&&&&'' '''"')'0'7'>'E'L'S'Z'a'h'o'v'}''''''''''''''''''(( ((((%(,(3(:(A(H(O(V(](d(k(r(y((((((((((((((((((()) )))!)()/)6)=)D)K)R)Y)`)g)n)u)|))))))))))))))))))******$*+*2*9*@*G*N*U*\*c*j*q*x*******************++ +++ +'+.+5+<+C+J+Q+X+_+f+m+t+{++++++++++++++++++,,,,,,#,*,1,8,?,F,M,T,[,b,i,p,w,~,,,,,,,,,,,,,,,,,,-- ----&---4-;-B-I-P-W-^-e-l-s-z-------------------.. ...".).0.7.>.E.L.S.Z.a.h.o.v.}..................// ////%/,/3/:/A/H/O/V/]/d/k/r/y///////////////////00 000!0(0/060=0D0K0R0Y0`0g0n0u0|000000000000000000111111$1+12191@1G1N1U1\1c1j1q1x111111111111111111122 222 2'2.252<2C2J2Q2X2_2f2m2t2{222222222222222222333333#3*31383?3F3M3T3[3b3i3p3w3~33333333333333333344 4444&4-444;4B4I4P4W4^4e4l4s4z444444444444444444455 555"5)50575>5E5L5S5Z5a5h5o5v5}55555555555555555566 6666%6,636:6A6H6O6V6]6d6k6r6y666666666666666666677 777!7(7/767=7D7K7R7Y7`7g7n7u7|777777777777777777888888$8+82898@8G8N8U8\8c8j8q8x888888888888888899 999%9.979@9I9R9[9d9m9v9999999999999::::*:6:B:N:Z:f:r:~:::::::::::::;; ;;!;-;6;B;N;Z;d;n;x;;;;;;;;;;;<<<"<.<:?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~                           ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~                            ! " # $ % & ' ( ) * + , - . / 0 1 2 3 4 5 6 7 8 9 : ; < = > ? @ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z [ \ ] ^ _ ` a b c d e f g h i j k l m n o p q r s t u v w x y z { | } ~  "4F%/9BWdov~ />T`ov}   * 4 @ K S ] g n x    * 3 B T \ g o x fnzuB,<KUdy!!!4!M!j#k######$$"$0$>$L$_$r$$$$$$% %"%:%W%a%z%%%%%%( (!(7(N(e(((((())#)1)?)N)b,%,6,K,`,u,,,,,,-----?-V-m------...'.:.J.^.o.......//"/8/G/^/l///////0 0!000A000000001 1!141<1O1l1}11111111222)2;2o2222223333344#4B4]5555666-6<6N6a6s6666666777)818D8U8h8y88888899#929A9R9d9z999999:w:<< =>=>@CEEFGHKKMQOPQTV WXXYBZZ/[]^l^u^^^`b[cveehjjln nyopp)p4rsNssstt v wyKyY{d|~+B\q#6Gg =Nr J_q +=Umk:]t 7Jes $9Sg})%2QlZX:3'KLȨq0AV֊ݢ/E!3cAAfzO,,7m B   @`Af&4;#2v}!!.!G$T$u&(,,/)/`//1b33455 56777778)85889Q:;;<<4>> >*>`????@@A A"A;APAdA|AAB`BBBBBBBBDD%D8DLD^DqD|DDDDDDDDEEEEEEFFF!F/G`GjGGH)H3H;HCHWHkH}HIIIIIJKAKIKWKeKL]LeLnLMMMMNNNNNNNNNOO OO,P`PQrQQQQQQRcRyRRRT#UlWxWWWWXYZ*Z3Z<ZLZVZ_Z]!]@]I]S]c]r^` `E`VabRbbdceXegghh8hhhi:i]iiiijijtj~l[lm mm#m>mUm_mommmmmmnnn&nKnRnYnan}nnnnnnoooo'o1o;qDqNq[qdq|qqqqqrrttttvvxx(x2yqyzz||}}|fq Dz .t0h /:E]g$3CYf$,8LU]hps} '4LZcoy(W D&=X¹ 1Cf#=5+d͚:&tָ -C\sA7j@VCZpx;Vp $ '<Qe6t+RfJThw5if3Eaz}~FXl  1 @ Y g ~       & = F M b s       * J b z        - C X     Rcu(4@Lcnx7Od~k%R"#%'(")v+.,`,,,,-h-./1b22:346789:;|<=[=>W@@@ACCCCDD5DKDhDDDDDDEE(E@EVElEEEEEEFF.F=FTFcFzFFFFFFGGG&G6GJG`GoG~GGGGGHHHH.H>HNH^HnHHHHHHHIII(I7IJIZInIIIIIIIJJ"JJJnJJJJJKK)K>KSKbKwKKKKKKLLL.LHLdLLLLLLMMM*M=MOMbM{MMMMMMNN2NSNfNzNNNNNNNOOO#O4ODOTOdOzOOOOOOOOPPP5PIP_PvQ=RRTTTTUU.UNUnUUUUV V,VIVgVVVVWW!WAWaWWWWWWWXX%XDX[X{XXXXXYY)Y9YKY`YxYYYYYZZ7ZWZtZZZZ[["[>[Y[v[[[[[\\"\6\H\[\m\\\\\\\]]]+]=]N]`]s]]]]_abcccccdd:dZdzdddde e,eLele~eeeeef ff1fIf^fvffffffgg3gAgQgiggggggh h'hCh]hyhhhhhii/iMijiiiiij j(jAjZj{jjjjkkk8kRklkkkkklll2lOlmlllllmmm8mQmkmmmmmmnnn%n9nOndnwnnnnnooDojooopp"pJpspppqq/qIqkqqqqrr+rFrkrrrrss;sZssssttBt\twtttuu$uFu]usuuuuuvv,vCvZvqv}vvvvvvvw w%w5wEwUwewvwwwwwwxx&xGxYxlxxxxxxxyy1yMyiyzyyyyyyzz)z5zSzizzzzzz{{ {,{8{;{?{B{E{H{K{O{Q{T{X{\{]{k{{{| ||$|6|B|N|a|q|~|||||||}}@}K}\}e}s}}}%1E`l~amy]8XyIT.?I,/*->Ugr#-7HSdoc s |UR=  Wk„BȫH_ё@}דَdKߑ 1Xhs(2Eaiu $L iJT^Ju 3>ju?lOY|FRbm    $1Q8CT Hly)W&A6>|U+, !!!!!" ""0"C"V"i"|""""""###+#>#X#t#####$$$+$7$O$_$o$$$$%%%&''','='P'k''') ))))* **.*>*O*h*y---------.. .. .-.4.F.U.k.v...........///*/7/B/S/b/l/u//////////0 0 0/0?0H0R01111111111222 2)272A2R2[2q2222222222333%354c5555555566626>6J6\7888888899929E9T9_9j9v999999999:::5:8:G:O:e;Y;j;z;;;;;;;< < <+ > >>>0>D>Z>p>>>>>>>? ?!?1?I?`?p??????? T T T T EM! " mX# T# `8@\\d`A8`AƋodƋG‹o\Ë{\8Gr *cUU5cUU5KѿVUUVUU_hUUتت*6*ê*UUê*UUL!UUUUĀUUʪ/YlUIUUTIUUT*UV* UUUUb⪪XȪNȪNU?,0UU,0UU*UUy{UU⪪-Ϊ-ΪUUUU9UU9䪫UU.UU]UU]UU~UU4]m*UUUUҀQ*kUU UU_UUAUV|UVwUUUUwUUUUUVSUUv_w誫UUUUUVUUUUȪG%תUUUUU*pUUȪOO̪"UUU,UU,ꪫŋGUUYUU"UKEE]*$t UU$ $ % & & $UUUU4545UUaUUΪaUUΪUU_UU`!UU`!UUQ2B%B&1UU$UDWWs*xE*|UU|UU*ǀUU̪Q*BUU00UUwFccR4UUA'A'UUU*U*UUUUU*UUUU,,9FUUU@UU@UUڀ*[UU{UVTkUUDUUUU\,ު7UUު7UUUU;ˋUU >UUËY=UUԪ;VUU:UUUU:UU98* *)UU(UUUU(UU!UV'UU'UU-U *Ջ;U窫-UU-UUUͪWEUUhԪhԪрUUQe8# a' ad( ) 8* I+ Mp, U{- q@`l(llo. p/ 0 jh1 o2 883 X4 ro5 o6 p7 8 o9 p: `;  < p= U{- t- I+ L- >  ? ?  @ A " XB ;C ;D E F G 4H hI J pK VL 6M <N O uP eQ R -S T PU +V W X Y +ld=UU=UU yUVUUUUUUd*QUUQUUdUU =b=bdEUUllEUUbbUUUUUUqUU2GlYUUUuV>UU>UUUU*Tl"E檫UUUUUUU>>UU\UUPPUUUUO\UUdUU>dUU>p*UUp*ફdUUUUdUUUUOjUUNeUU*zUUUU*U=GnZ [ X\ $] 8^ ;_ ;` a #b Hc a4d m| 4aD`e m,|P0|ef m@g )0gh hi vlj |k 9kl vm |n *ko s0p eq vr s BX0t lu v lw v alx Xy [lz 2X0{ Rk| h} v,k~ kh vk ))UUȪ Ȫ UUUUU 7UUv7UUv[b@b@ UUUUO*:UUj:UUj,ڪUUUU*VUUUUxwoUUUkժkժUUUUĪjĪj<UUh"lU6KK%UB@ڪUUUU$'UXXUUFj4j4UU!2!2UUBUU˪BUU˪RUb%l  M & =  m = i@ a04   a` d EM|-ffҀUUҀffffҀUUҀff---9v. }*2*`UUY`UUY9m*wڀUUT]T]UU=WUUUUEVl4UU̪4UU̪"*ƪUUĪn'VUU'VUU0VmUU UUvwUU׀UUUUUUUU*|loUU~UVUUUUGEEobj=UUUUUU*6#**FUUeFUUe^*<* p_UU)UU*2UV;UU;UUUBmxu|(bPUʱ0.;UUUU;UUUU=*ˋNj8U5r5rUUUUϪUUϪ *|KUU׊J*ꪫHUUꪫHUUڪA*ʪʪUU.*="UU="UUUU*#UU,Ъ,ЪUUUUŪ+Ū+UUUUn(mxtUUcUUʪʪUUꪫcꪫcUUwc;pX *UUU6UU6$V*/UUUUJL4.>@UUĪUUUU]*iUURMUUMUU>/UU??Q*b;UUUUܪ0[2.*UU*UU+1UU1UU U1UV1UURUFUUݪݪc/UU%UUUU%UUVUUx[[[[sNjU0))AUDU+R+RU5$UU*\UUتUUت۪UUb{b{݀olYӀ#dd4ˋ*0UU)UUUU)UU!UV&UU&UU!VUUxo+UU*&UU&UUUU*Q2  < < L1UU*֪UU֪UUUU*;3UU ҪҪUUS8S-UU-UUB ۋ=UU쪫*UU*UUUU3h$UUUU ( U55`U282U U( oSUUSUU)$mUUUUUVફ UUm}(UU((*UUDUU^U??UUU *᪫UUUUUUUU*/w!UUUU Njj5UUUUG 2U*UUUUU*%-U;R;RTU͋7U UU--$UOUU誫w٪UU٪UU*ڪUUʪۋ#UUUUۋmHp d m8Mp, < < #Ӌ2{UUkUUk[K?wUUӪӪUUuh{:IUU@9r2UUr2UUU%VUU:qUU cX*;:UU;:UU*pddLH*UUUU*IWꪫUUUU䪫UU䪫ΪUUSm m  \     I l ZnUUUǪUUǪUUW*W*q*UU**LUUdUUdUU*UUM- m   m0"U U(&s*-*TUUZUUTUUZUU**t*""ժtUUU[UU[-U(&**UUUUUUUU**"UCUU'6'6:UUUًۋ;UU䪫&UU&UUUU;9U*٪UU٪UUU*=;OUc5c5wCUmp  %     %   K%    P*UUƪUUƪUVUUUbUUb**OUU3ʪUUIΪUUΪUUUUUVUU#UUU//CUUKUUUUf*誫uUUb*UUXUU*XUU*=5P5PUUqUUUUUUUViUUiUUժUVUUUUUUUUUVUU-UU99UMUOUUDUV9UU9UU1UU.UV:#UUͳ8+$*>*UUMUUUUMUU g ;D E T0g ;D E T ;D E  ;D E  ;D E   ;D E P @    hI <x VL xg VL xD VL l VL   uP  Pg uP  uP  uP ,  J pK 8| PU  +V W Pg +V W  +V W  +V W ` +V W H  p4 +D7  s 8^ g 8^ D 8^ l 8^   #b hD |  v"*OUDUU=DUU=UVUgUUVUު?UU?UUU+ªUUUTUUT*UUZݪZݪUUtHyUU`UVݪGUUUUGUUUU#4UUUUUUxpt hYUUYUUEUV1UU1UU`րsU882U !UU8OOb*tmUUqWNWND<1*1*UUUUU2Ӫ2ӪUUͪUUͪUUU)0gh hi g )0gh hi  )0gh hi ( )0gh hi T )0gh hi <L  )0gh hi ` @  k r `i 9kl  *ko s0p g *ko s0p  *ko s0p 4 *ko s0p `  lu g lu D lu l lu 8  "0G(8QĪ-UUUU(UU(UVUUUU#<3UUUU0*㪫㪫***'UU⪫(@aUUUUL*7,7,**;wUUફccZ!UUQQUU UU$lUUMUUMUVUUUUUUUU䪫nqUUUUUV6UU6UUM4dd UUUUuUd=d=NUUYUU *UUpaU77"UU{UUUK1K1\@mOmO|U*Zݫ0UU@@ܤaUUiUUUUV㪫AUUUUAUUUU/D5UUUUUUUVުUUުUUUUNIҪIҪUUUU&\ H{ Rk| h} g Rk| h}  Rk| h} $ R  | h} Rk| h} H  $ X  R7 4 _ & pg & pD & l &   =   vl  =  P  ;D E  )0gh hi , ;D E  )0gh hi h ;D E  )0gh hi t hI D 9kl  hI  9kl  hI v 9kl v hI  9kl d J pK  h Om |n D J pK 8| vm |n  VL p *ko s0p  VL  *ko s0p  VL v *ko s0p Dv VL  *ko s0p , VL  *ko s0p 4 <N $ vr s H <N  vr s , <N v vr s v <N  vr s 7 O p BX0t  WP8DDDD8d8((80BX0t  uP D M @u uP   lu p uP   lu  uP P  [u v uP v lu P Q %lu v w v eQ l lw l R  alx L  -S D Xy | -S d Xy  -S  %Xy ( -S hv Xy hv - YPPHPƋpL 4p8Ap8Dpދ7pPU 0 2X0{  PU H 2X0{  PU  D 2X0{ D `4;uUU_⪫_⪫UUUK}UUUUUU`*UUUU*#GUUGUUewLH2X0 +V W T Rk| h} l( +V W h Rk| h} Xd +V W  d< Rk| h}  d< GpL0p0Hpp|Hb*ՀxUU6xUU6UVUUfUUUUfUU*L2`%UM*KfUUKfUUiUVUUUUU*HDpD*!UUCUUUUCUUUVUUxUUx*UU4UUU!7x7xM]cBcBsU!UUDk| h} ho s0p Z [ D %l D Z [  %l  Z [ D %l  X\ D  P X\ \l   X\   L X\ \  l $] L M  $]  u \ $]  M|dUA)UU)UU4UV?UU?UUA*B$UU*UUUUUU*[hUUUUꪫqUU*ll檫UUUU*1UB$L\|\||8^ L &\ $ 8^ p & p 8^  & < 8^ x @ & l @ 8^ t< d< & < d< 8^  &  ;` l  l #b l = H , #b   Hc  i@ p Hc PHv i@ Hv Hc `  i@  mhD)UU1UU)UU1UU<ۋ`kUUުUUުUUUUk(*UUUUUUUUժ*6vlj |k  8xUUddI..UU\UUfꪫ[UUUUJUUJV9UULUU:62UU!UU2UU!UU(-UV9UU9UUHUU[*ҪRUUҪRUUFAUUUUUVqUU&cE;E;h+|*U(*%%' U5UUG 4H   vx`xUUoUժժ-UU;UUj;UUjb L7UU 7UU UUUUU ȪvȪv>[UU@UU@ UU~'UŪjŪjUUڪlUUlUUUVUUwHLk lptpUU8*tpUUtpUU:UU*FFŪUUUiUUiW4Upp @tt`xUUoUժժ-UU;UUj;UUjb L7UU 7UU UUUUU ȪvȪv>[UU@UU@ UU~'UŪjŪjUUڪlUUlUUUVUUwT doUU*UUUUUUyUVUUUUlld=UU=UU yUVUUUUUUd*QUUvVVUU=UU=UUUU*檫 ^&UUO8@8@DUU!UU!UUUUUZp*ફdUUUUdUUUUOjUUNNjUUPPUUUUUUUફUU;!UU;!UUUU@UU@٪Ow^UU 3*UUUU`sUUVsUUV~UVUUGhUU6*UU*UU5UUˋ * UUUU UUUU *\yU窫窫Us5UHժHժ*UUU;baRUUUUQUUdUU =b=bdEUUllEUUbbUUUUUU~UVsUUsUU`LUUcc3wUUU wUU٪UVUUUUUUUUZ;ު;ުUUUUUUU>>UU\UUPPUUUUO\UUdUU>dUU>p*UUZUUUUUުުDUUZ8UU8UU&UUUVUUz\UU6*UU*UU5UUˋ * UUUU UUUU *\yU窫窫UsUUԪԪUUUUo9P{(UUeUUuUUV*UUUUUUUUUVUVUUUUUUUU*v*0fwUUGGi$qUUc*U*UUU*UU9UV4UUHUU4UUHUU VQ UUZu7*UUUUЪUUЪ*UU/ UU5UUFB**TUUTUUo*4RUU=ɪ=ɪ'UUEJ pK 8| o|*U(*%%' U5UU$ d)*UURUUUURUUn UUhUU*UUUU rQrQ>(U9UU&cE;E;h+tK L0|Htp=U PpUFFnU =UptxtUUUQ#Q#ժ.UU9UU9>*BSUEEe"U"tvxooUUUkժkժUUUUĪjĪj<UU ))UUȪ Ȫ UUUUU 7UUv7UUv[b@b@ UUUUO*:UUj:UUj,ڪUUUU*VUUUUHLn m8I*KUUUKUUK*JU;Օ*UUUU*o&U\UU=\UU=FMUUe*mUU|VnUUVnUUWUVUU@UUUU@UU* * U((UU˪˪UuUUo#*UUUU;UUUVKUUKUUW*bUUs!⪫%UU%UU"jફફ*t UUhUU⪫UU⪫UUҪĪª^ª^UU+[cUUk Kƪ=UUUU檫^ UTUMM*>UU99J*ZJU9\9\*UUUUUU */FpL0p0HppoUUdUUqJUUJUU%UU<U$xUUSUUxUUSUUZ*)XUU?z?zUUxЪvЪvjUUd@UUUNNbUU U UUwUVUU[UU[UU3*UU*UU@E *ZUUZUU΀* t("M=UUM=UUlN!UOCClU *]U*IUUIUU$U*-qUU*UURUUR9?,,UUUUjSU*}UU}UUUr*NUU*V8UUUU8UUUUL*`cUUUUŀOOmUU UUAALpUUUUpUUUU~** T UUSUUSUUK/UU/UU6*<(UUI(UUI*WU)ڀ*@UU@UU* UU9:9:bYUU $Y*IUU8UUIUU8UUl*$t64;UUUU֪⪫֪⪫UUUUK}UUUUUU`*UUUU*.t#GUUGUU'e p Hhp4$,DQUU*UUUUUU*UUlq۪UUUUժe*UUUUUUUUת*6,`;*UUUUҪUUҪ*UUUU*ߪUUߪUUU*}wlUUUU k(UKUUQKUUQ%uU.K<l&*EUULUUbLUUbj*UUUU*"*}DUU}DUUb`G|G|#UppUUXXUUͪUUͪ*UUUUU>>\UUĪĪUUUUUUUU::\UUcUU>cUU>nUVUUyUU({d==G*+UUyUUXUU6*UU*UU5UUˋ * UUUU UUUU *\yU窫窫UsHUUĪlQUU.?VUU.UUUU.UU *~QUUe*UUUUbbUEUUlpgYU۪۪UwʪwʪUU_#UUƪƪUU9UU9UUP)UU.VUU4UUUU4UU*@fLfL*^UUprt<&UUUUYYaiq*uUUuUUn*ggn UUuuUU U*3UU3UUUϱT3l0&*gLUULUU`$toUUW*ހHHlUUU----=UMӪMӪV*U^z.UUmUU\mUU\V&UUUUddUUu_<_<*UUUUUUUUتՀ UUUUUUUU*)UVk=UUk=UU{Z UU[&+^+^UU/UUbUU\\UUgwmP@4*UUTUUTUUtUUԪ( !UUUU!UUUU kd{yUUwwuUUs;UU*֪2UU֪2UUUUD*uP X /,\L3UU7UU0V-UU-UU,U*oL9**UUUUUUUU**;3s0$HwUU6*UU*UU5UUˋ * UUUU UUUU *\yU窫窫UsUUUUUUUUL\Xy X D'Ro=UUـ/Z!Z!UUwUUUU⪫⪫UUUUd * UUUU UUUU *UUUVUUUUUV*UUUU'|Kċ UU UU* d d$UU檫UUUU/UU*6UUUUp[m窫UUUUUUUUyWͤ'UU)8VUJUUתJUUתVVUcUUU!!9.-UU;UU6 UUcĪȪĪȪUUUUV *UUUUUUUUUV(UUUU*T*mUUtڪUUUUiꪫ*êUUêUU**UU* *dUUdUUk(rrT*mUUtaUU*UV(UUUU(UUUU8ccUUwbuUUaVNUUNUU=*AUUR\wL`;UUUU֪⪫֪⪫UUUUK}UUUUUU`*UUUU*#GUUGUUe2X0 +V  t Op (lW Vt @} hh)ʪ-UUU(V,$UU,$UU**eUU]UUoe??UUppUS6 d$UUPHqUUHqUUc~~$UUUU,{UU*^"UU^"UUIUU(**%UUmXҋ<d|2UUeUU~eUU~?UU<LUU*8UU8UU+x骫骫UUUwUU*᪫骫᪫骫UU쪪T磌T磌U/PrUUUUEpl̪(iUUUV݀UUFUUFUV0UUUU窫SIUU0bFbF}UU݀,YUSSDU<X(-UUU[UU[ݪU: UUR*PUUPUU'BUV84UU84UUO*fًBꪫ7UUUUhoy*UUUU*eKTUU*:UU:UUKN1UUb1UUbUVmUUxx窪mUUΪΪ?MUUU9UU9|UVU_UU&cE;E;h+|*U(*%%' U5UUY vP''UUUU'UUUU FUUUU\檫Ҫ*UULUUL**UUUUoUժժ-UU;UUj;UUjb L7UU 7UU UUUUU ȪvȪv>[UU@UU@ UU~'UŪjŪjUUڪlUUlUUUVUUoP (ělC [F*\UU\UU kzXêUUgUUUUgUUQ3x OOxt*kiGG 1%hUEE"U @UUgUUV\UVUUUU>UUUU*VUUUUUUUUUU4]CCwH誫MUUMUUYUVeUUeUUxȪGUUVUUUU;ԪKUUlKUUl^QUUKV *FUUUUFUUUU%UV-2UU-2UU*?VMUU>;UU/, , UUqUU㪫UUUUUU'G'GUUi~~UU''[UUsUUUUsUU㪫UUu,,;UU9*%UUlp@{5UUUUrUUr٪XUUV oUUxUVoUUoUUY}UU.UUvUU)ullwpUUUUUUUUՋ8U۪۪U1.׀U::U$*:HUU:HUU׀q*.LiUU''@YY{rUU$'*#UU,UU ,UU .*UUw\)UUUUUUUUI_C(ت>ت*̀UUUUUUUU*AUU+UVUUUUk*ߪUUvdUUU66/*OUU UUm\p4@HU|+m+mUbWMѪ`UU`UU㪫qJeuUʀFFU 4 iUU9iUU9bDoU//=UUֺNURUUɀܪUUܪUU`oUU磌UU磌UVUUU'UUUU媫Nj.!n`UUUU""?-UU-UU-UUӋ]UUߪOUUUUUV-UUUU{L<jh1  RoP*P:UUfUU:UUfUU*tUuUǪgǪgQ;;*UUUIuuUUUUaު싋p,|o*6UU*@UU >UUWUUUUMUVUUUU5Ȁ*3*UUUUUUUUUVUUUتUUت*UU7UU**@UU'N'NUaUU:р..~+:WʀTzTzՀmwP\xGm QUπ˪d˪dU)WUU̪̪`UU(UUUU(UUUV6VUUEUU7*UUUUEUUUV[UU[UUp*UUeC40UUCUU0UUCUU*MR*AUUUUUUUV'UUUUUUUV*UUUUUV!UU'UU'*5U\``dUUUV!UU!UU)UV1UU1UU1V1UU1UU)ફ!i!iUӪUU7~-UU=UU*EUV)MUU)MUUraPUU PUU (**J骫UUUUUUUVUUUUUVUU*llU*YUU 8.UU5UU5UUBUVOUUZJ*窫9UU9UU*UVUUUU U+ouUU*UUUUV*UUªUUªYtUUqx m,  mx   M! " J pK 4p  m |n  L _S  @HQ S w v %Xy Dw v wPU Q PU <w v X0{ w v ;D E $ )0gh hi  uP  D lu  +V W D Rk| h}  8^  &  8^  H  &    8^ l  H| & l  < 8^ | p  & x ,  8^    & ptg {x  *  s0 ;D E ( H  )0gh hi    ;D E (  )0gh hi      @  k r `i , p\N̪UUUUUV5UU5UU骫UUUUUU>>\UUĪĪUUUUUUUU::\UUcUU>cUU>nUVUUyUU({d==G*+UUyUU檫UUt*=dUU=dUUUUNUV8UU8UU}UU*$QUUe*UUUUbbUEUUll&*EUULUUbLUUbj*UUUUJ3*UUUbUUv<UU8L4UU68ppƪUUUUWvr s T <N $D vr s HX R L alx  +V W $ Rk| h}  +V W $ d < Rk| h}  P 4  T  H xD lw  J pK v>vUĪk,:UUBUUVUUUUTUU *^mUUUURR)UUUU0xwU"*l)l)UU%UUŪŪU'~ߪUUUUUUUU>UVȪUUȪUUUU ^]UUs*7UU 7UU L/JJ߀b;UU;UU-%*UU*UU#*UU,0VXUU,UŀϪϪU*UkUUbUU!HCUUHCUU$UUWۀX*BBUUBBUU!*UU}Eo|& |#UUUU#UUUU*!*zUUoUUo *ha]UUU⪫᪫⪫᪫UUU[Y*UU#UUUU#UU*3UUUUUU`+UU`+UU=ۋU?UU+(+(@UUUl'UvUUUUUUɪZCZCUUUUEEDɪUUUUUUUUX7X7UU !UCUUCUUFrUUrUU*Ur-r-EUUCUUCUU!u*UUcXSUUXSUUU2UUU&D6FUUFUUYlnU[UUϪϪ6UU$$*UUlu@Uફ+UU+UUU5;UUUժԪժԪǪmUUUUU7$sUU߀VJVJ+UUԪ*UUUUUUUU*$k$UU=UUUU=UUԪb+UUdVAUUVAUUsUU $(SUU* UDDƪ#UUUUUU*H*]UUM*6UU6UUA,*`'*UU^UU^JKϪ8Ϫ8U*UUUU5UU?5UU?FUVPWUUaΪ**˪˪U* !UUUBBZUUFUUUUUU*UUU--ZUUEUUBB!UUu*UU UdU˪S˪SBUU1UUV;*5UULUU5UULUUavvUdUϪϪJUUUU*'`iAUɪɪU'U`X`XRܪDUUDUU*7p"sU,UӀ2H2Hހ"k$!UU3B3B_eUUeUUBB!UU$ы0*UU\UU\ *ˀQ?t  ۀaUUBJBJ%UUhpH|pHmh܀_UDMDM&UP`p\ȋۋ4UUUUΪUUΪ UUK;D E v )0gh hi Lv VL  *ko s0p  +V W XH H  Rk| h} t   +V W TH d  Rk| h}  { +V W ,v Rk| h} Hv +V W TH  Rk| h} t  #b p =  0 X,0T\,UĪΪΪUUUUlbUU!HCUUHCUU$UUWۀX*BBUUBBUU!*UU}Eo}*  U#UU)UUUUUUUUڪ[]UUU⪫᪫⪫᪫UUU[Y*UUUU*'3X0%*gJUUJUU]$pmUU*>UU>UUUUVUU,0T\,UĪΪΪUUUUlbUU!HCUUHCUU$UUWۀX*BBUUBBUU!*UU䪫 X*UU_UUUU_UU/dUU*UUUUUU*g=wxh*  U#UU)UUUUUUUUڪ[]UUU⪫᪫⪫᪫UUU[Y*UUUU*' $p|\|,0T\,UĪΪΪUUUUlc!KUUCUUKUUCUU%W*X*UUBUUUUBUU*!*UU}Eo>\5*  U#UU򪫭UUUUڪ[]{Uk᪫k᪫cU[Y*UUUU*'lw  hUU^^I44UUU*FUUFUU*_UUƪ[5UUU(U5UUUVkUUKUUKF2ΪΪUU{iUUo$U111XUUIXUUIf*jZO*DUUDUU+V1UU9UU)+1UUQ;UVӪEUUUUEUUUUPzhVUUVUU--U$+UU3檫;檫;תKȪ[Ȫ[sUUSSUU5ફ?UUUU>+)UU)UU^窫7UUUU7UUUU'UVUUUU+L *XUUUUPUUP)UV@8UU08UU0IUUZQUUCUUUUUVUUBkUUUUUVʪUUʪUU쪫UUSSbqq~lXUU;׀㪫㪫O*UYUUi*VUUVUUA$骫UUUUUUUVUUUUUVUU*llU*YUU~-xΪ5UUŀ(VUUUUUUUU*Uk2K2KG2\\{iUUo$U//DUU1IIjjUU2deUU`  U*99J*ZKUU@UVUU5UUު5UUު+UUWvުȪ`UUUhUUh;ƪ1UUƪ1UUժAUV䪫QUU䪫QUUUUX+UUppaRΪRΪ*UUUUUU**V$UUV$UU*.*UUZ 8.UU5UU5UUBUVOUUJU窫9UU9UU*UUUVUUUU +;$ xE4bTx,boKT0dxXUUcUUUU~UVsUUsUU`LUUcc3wUUU wUU٪UVUUUUUUUUZ;ު;ުUUUUG UUUU;UUUU2VЪ*UUQ*UUQG=s[rC`C`KUU6 x0UUUUDQUUdUU =b=bdEUUl*&UUMLcUULcUUgUVUU}9BZUUUUU窫*UUUUO\UUdUU>dUU>p*UUUU9PcªUUuUUV*UUUUUUUUUVUVUUUUUUUU*v*0kUUK*aUU(T|sMUURqUUc*U*UUU*UU9UV4UUHUU4UUHUU VQ UUZu7*UUUUЪUUЪ*UU/UUUЪUU53UUЪ"UVUUUUGo__UU0UUkUU˪^B**TUUTUUo*4$UU"UU-S  xTgꋋpx/pNcUUmUU*4B4B!UU,x6e6eH˪ZUUZUUUVUU! UUAAg**ުO,ܪCUUI߀ǀJ\J\UU>UUN*gUU誫0UU0UU"YʪqxpP`p\쪫UUUU1U[)ƪ)ƪ(*ƪ&ƪ&ƪ+[0UUUU0UUUU<*쪫Ӌ͋9*1UUUU1UUUU'*/4suUU᪫᪫UUeԪUUUh&h&Uڪ9UUڪ9UU`9UVUU9UUUU9UUVUU&UU&VUUUUm,C#tUU`U/UVGUUGUU9V4UUzΪΪUU''",UU*2Z2ZEUUXXUU MUU::OUUfO*8UU8UU'>> UU*UU47u>u>تLUUZUUZ#mTXNU??Uߋ݀FF-+`UU`UUફ![*UUj*9*>UU>UU*iUxNͪUUUaUUao0UPUU.骫UUUUUVΪUUΪUUUU)(F G 4H , 08f$UUHUUHUU`8xƪxƪUUUU$$UUx9UUx9UU`HlHl$UUUUUU檫VUUUUUUUUHS٪٪U*&UU&UUHUUIUUI檫Z*j$̋|x,̋XV x@@xpF]L-p\Hfp'p\HH$]L5L*PcªUUuUUV*UUUUUUUUUV-UUUU*u*b*{UUK*0aUUUU)T}MUUqS~0#GUUGUU]xm;ϪOUUcUUcUVwUUMƪUUUUUvUUU#ŪlŪl*QUU5UUOUUJ*бп7͵͵٠'UU%UV#UUUU1]*۪UUAUUUUAUUUVUUViUU.eQ 0 lw t Lv _hxsY*TUUUUUVȪUUȪUUUU ^]UUs*7UU 7UU L/JJ߀b;UU;UU-%*UU*UU#*UU$&UUMUUMUU^UU Dd` zUUl*̪]̪]IUUvttt[ %l  #P\ڋ88(88ڋ8??sfqs=#7L,Lċ,L7L+Lo0S _UUUǪ誫Ǫ誫UUU5OdUUUUU#U֪V|AgXd{d),$UUMUUMUUUUߪڪeڪeUUUU{ުdUUUU2*ɪUV*@UUUU@UUUU8V*1UUMUUN OOIUU$4UU4UU5HV]UUtqS^UUKΪKΪUUUUUʪ7ʪ7UUMcUU U* :UUUU:UUUUKV ]UUGUU UUFV FUUFUU@*UUUU.UU+*""UUG誫U*UUFUUUUFUUL5ghM*UUUU*9OUЀߪhߪhUU媫VUUVUUU*ª檫UUbbժUUUUUVUUUUUUUU**lȬŬ0/UU@UU$@UU$NVUU]UUk m H*{*UUkUUkIT==UUU*UUڪڪ*ʪЪwUUԪԪ˪ªk᪫UUUU㪫~ow\UUUUUUUUUUU窫窫 UUU9|H/UUUU:UV̪EUUgEUUgQy}UUj*"*DUUDUU^UVxUUxUU*0#+r3r3תUUUUʪUUʪ*UU/;*V,UUV,UU*9*UU *UU9DUUTZHU^^O@@ *UUvlxUUoUժժ-UU;UUj;UUjb L7UU 7UU UUUUU ȪvȪv>[UU@UU@ UU~'UŪjŪjUUڪlUUlUUUVUUwUU6*UU*UU5UUˋ * UUUU UUUU *\yU窫窫UsUUUUUUUU44 ml 9kiUU_*$UU$UUDUU322UUO߀RUUJ@J@ UUUUUѪUUѪ*7uՀQYUUB**TUUTUUo*4RUU=ɪ=ɪ'UUEݪȪȪDUUUUuUUVUUUUUUUV.UUUUUUUU*f.UU6 ʪʪЪ֪ͪЪ֪ЪUUeoUU8$U&&**/UU-UU;UUMFUV[QUUiQUUiZz7ߺߺOKUU#*$UUt$UUt*ۀYIUUɀ``UU/UU9v*UUUU*\UU*UUUUUU*}KʪUUUUUUUUꪫ6wU"*l)l)UU%UUŪŪU'~ߪUUUUUUUU>UVȪUUȪUUUU ^]UUs*7UU 7UU L/JJ߀b;UU;UU-%*UU*UU#*UU$&UUMUUMUU^UU @ kb;UU;UU-%*UU*UU#*UUxۡ4UUUU ˋ UUUUUUUU `yUU*檫UU檫UUUU*u*hUUEUUE*&wU"*l)l)UU%UUŪŪU'~ߪUUUUUUUU>UVȪUUȪUUUU ^]UUs*7UU 7UU L/JJ߀h mXoUUz*U==OUU7lUU7lUUU|* 8ફ*UUrUUUUrUU:;;"UUUmUU*U00UUUUժժU  wUU UV=UU<UU<"2UUBUUU_UUPV܀BUUDBUUD)VUUUx*  s0 8'U}ooª۪ͪUUͪUU*\ުΪ<5*/U>UU%>UU%L*UfU*nCQCQ/AUU1^O3 ***UUUU*UUUUBJJvU߀n*cUUrUU>rUUUUNjǘ UUUUUVUUUU"UUժ,k,km0 uLoUUS*UU&&3UUˋGUUUU9V,UU,UU*8ꪫUUUUUUUUUVSUUUUUU?lVNUUNUU'*UU*lUUlUU6UU*"֪Հ}UU}UUUUתتfتfUUU?ꪫQUUUU>l+ت+تՀUUUUUUUU**aUU*UVUUUUUUUUUV*UUfUUfVUUJ(+*VUUVUUsUU3(UU:6UUg<*UUUUy*sAUU%*䪫JUU䪫JUUUU`*y SUUUVMUU7UUUU7UUUVoUUQUUrU**쪫tEUU'U**RUUUfiUUTUVU?UUӪ?UUӪ*K̪TUUTUUU쪫L-oSU & &ŀUUUU:Uuu*䪫*lUUlUU6UU*"֪Հ}UU}UUUUתتfتfUUU?ꪫQUUUU>l+ت+تՀUUUUUUUU**aUU*UVUUUUUUUUUV*UUfUUfVUU80LoUUS*UU&&3UUˋGUU*9VUU,UUUU,UUV*UU`UUUUU*y*TD%lUUUUr*.oUUYVDUUDUU"*ŋӋHUU=2UU2UUUU*Llw t vnUURUU>>UUkUU6*UU*UU5UUˋ * UUUU UUUU *\yU窫窫UsUUUUUUڪUUs"*UU)UU)Ҫ%UUPP'~UUUUU>UUȪ Ȫ UUvUb4MbUUUUbUUUUo*⪫OU-UU$UU"UUUU쪫cUUcUUʪ֪UUpUUpUVUU3U ǪUUǪUU^UUUU*wUUP9JUU*dUUdUUUU*8s vr s p|uy**UUUUUUUU**UU*DDSbbKtRUJ)BUU)BUU4FUUFUUMVUUU[ZZ(-*ϪϪEUUUU* vUUUUȪUUȪUV>UUUUUUUU᪫~t}UU*VUU>UUUUVUUTdX*UU_UUUU_UU/dUU*UUUUUU*g=w6-UUUU-UUUU6ˋ * UUUU UUUU *\yU窫窫UsUUUUUUUU2X0%*gJUUJUU]$pmUU*>UU>UUUUVUU;ꪫUUUU⪫UU⪫ʪUUK}UUUUUU\*UUUU*^#MUUGUUMUUGUU&etX*UU_UUUU_UU/dUU*UUUUUU*g=w6-UUUU-UUUU6ˋ * UUUU UUUU *\yU窫窫UsUUUUUUUUlu v L mx3*UU&UUª&UUªUU?k?kEU{׋UUUUUUUULUU檫UUsKUUڪ!UUڪ!UUUU8m@p\\p@pddy @ p[UUʪUUUUUUUU%?%UUT%UUT1ժUUUUC*IUU, mX\&UUMUUMUU^UU*UUUU*\UU*UUUUUU*}KʪUUUUUUUUꪫ6${\Tjd==)&ր/8C8CgCUU ĪUUĪUUͪ UV֪)UU@@S_kk{wUUU33QGUU;UU;UUh*uUUr*UUWUUUUWUUFUV25UU25UUU ,H܋[TUߪUUߪUUUUVVUUVUUU磌ªBUUU5UU5㪫FWUU|UUUĪJĪJUU߀7BUUM0M0l܋qUUKUU+zVWUUUUWUUUUpUV*UU^UUNN?*'UUOUU94U*CUUCUUT*eUUUUGUU-GUU-3?Ux[x@TUUUߪQߪQUUҪVUUVUUU磌ªBUUU5UU5㪫FWUU|UUUU9ĪĪUUUU7BUUM0M0l܋qUUKUU+UzVWUUWUUp**^UUNN?*'UUOUU9*UUUUTUU*dUUGUU-GUU-3?U[lTKUU*B UU9 UU9*-* 9*UU=UUYUU*8UU8UUUU|cUUR*;UU;UUS ߋ窫UUUU:X;ꪫUUUU⪫UU⪫ʪUUK}UUUUUU\*UUUU*^#MUUGUUMUUGUU&eUU*UU*UvUU\UU\PUت{e*?UUU-UU-U'UU*UUUUUU*k9wxb0%*gJUUJUU]$pmUU*>UU>UUUUVUUTdX*UU_UUUU_UU/dUU*UUUUUU*g=wx\;ꪫUUUU⪫UU⪫ʪUUK}UUUUUU\*UUUU*^#MUUGUUMUUGUU&e&X0%*gJUUJUU]$pmUU*>UU>UUUUVUU&UUMUUMUU^UU*UUUU*\UU*UUUUUU*}KʪUUUUUUUUꪫ6tX*UU_UUUU_UU/dUU*UUUUUU*g=wxmD0(Rk| h YL  l@DpD,l#*UU UUUU UU*9uUU*ު&UU&UU=TTqUUkqUUNUUeUUeUUxUު ۋNMUU UUMUU UUZfUUVUUUUɀĪUUVUU1UUUU1UUUVAUVUUQUUUUQUU*X^UUQUUUUQUU,UVA;UU;UUJUVYUU<72UU2UU46xKUUf窫UUUU*::RUU)UU)*UU9 *UUUUUUUU**EE8ǪUUUUUUUꪫdp(ll(pUU**8UU8*FUU*FUUUMUVTUUTUU SR*aUUUaUUaRY*PPU@00fUUUUJpUU*䪫UUUUJ,UU/,UU/*%?UxxUUUUժXժX*7"UUUU㪫u::㪫UUFUUF݀7ȀUUUUm?LUULUUUL*׋U++IUUhUU6hUU6VUUUUdlUUUUV+UU+UU>UU4a*ҪqUUꪫa-*QUUEQUUE>*W*hhs~~s`h`hUW*EEUU-*ꪫUҪ::4UUUUUUUU*UVUU*i,UU_UU,UU_UUC*"1U@ӪӪUj*uUUUUC*J,UU,UU*UUkUUӪ,Ӫ,U1i%`UU*UUU⪫*骫骫UUU;\UU%UU*\IUU/UUIUU/UU2UV=UU|xm UUUUUUUULBUUìì.-*%UU9UU1@ioĪ׀\VUUVUU9sꪫUUUUꪫs`UU*UUU⪫*骫骫UUU;\UU%UUU*]UUS*/UU/UU2*=UU<%UU&JEJE]UUh*UUUUUUUU*LUUUUUUUUjUU$%l gUU$UVIUUDUUIUUDUU\"*UUVUUUU*UU\sUUo kkڪUUUU8UUЪЪͪUU䪫?{|x%*UUUU*\UU*UUUUUU*}KʪUUUUUUUUꪫ6pgUU$UVIUUDUUIUUDUU\"*UUVUUUU*UU\sUUo kkڪUUUU8UUЪЪͪUU䪫?{|x\&UUMUUMUU^UUXl51UU1UU8ˋ * UUUU UUUU *dyUU媫媫UUwUUVUUUUફUUફ̪]dUUdUU3JUUdUU>UUN*gUU誫0UU0UU"YʪhUUmUU*4B4B!UU,x6e6eH˪ZUUZUUUVUU! UUAAg**ުO,ܪCUUI߀ǀJ\J\*Vb48.<_UU۪ުUUުUUUUSa* UUUU*""UUUU3UU몫 >eUUUUeUVeUUr*UUWUUUUWUUFUV25UU25UUU ,HUUܪ΀nŪUUŪUU*UUĪUSSUUܪUUܪUUUU%*#UUUU ӋU``>ՀamzUUe*OFOF;UUW*ggo*vsz*[pUU[pUUZUVUUDUUUUDUU"*,*誫UUUUUUNTTU:UUٯAP*9UUUU9UUUUUVUUUU UU*7UUUUV誫UU誫UUתIƪ˪ƪ˪;UUUm m@OVCߪ0UUʪ0UUʪ!VUUUU UU=UUUU誫誫תUUƪVƪV;UU'<UQ?Q?jJUUU'*UUUUDJWUUϪWUUϪfUVUuUU,}UU"bbIUUztUUmڪfUUڪfUUUUV??UU/*UUm#UUU3UU3تuPUUuPUURVUUU UUR*PUUPUU'BUV84UU84UUO*fO*UUUU *5*ZUUUQUUQUVUU0UUUU0UUV*UUiUUUV݀UUFUUFUV0UUUU窫zUIUUUUbb}UUU,iUUa*UU..KUUKUU܏V=܀JVJV3+V W ,D- 1l t 8 -oUU* & &:UUUU*UUUURVUUQUUrUV*UUUUUUUUɪ*"֪ՀUUUUת'UUf'UUf9UꪫUUlUUتUUتmՀ磌UU磌UUU*aUUUUV5UU5UUI*]f]fj*v80LUUUT&T&3UUˋGUUU9V=,UU=,UUQ**dUUvUMM&Uy*TDڀl@UU@UU*.*UŋӋH=?UU2UU?UU2UUU*L8%UU%UU2ˋ **UUUUUUUU **LUU UUҪ]Z?Ԫ,UU0&*;;UU UvUUUUȪUUȪUV>UUUUUUUU᪫~t}UU*VUVȪUUȪUUUU ^]UUs*7UU 7UU L/JJ߀h mt8zUUe*OFOF;UUW*ggo*vsz*[pUU[pUUZUVUUDUUUUDUU"*,*誫UUUUUUNTTU:UUٯAP*9UUUU9UUUUUVUUUU UU*7UUUUV誫UU誫UUתIƪ˪ƪ˪;UUU4p\\m  m |n  O$``l\T*DUUdDUUd3YNN*UUGUU*ܪUUܪUUUUϪKfKfUU>7UU>7UUҀA}xUUUU*>F>FMUUYllbiUUs :**UUpUUUUpUUf*>*^<{"*UU)UU)Ҫ%UUPP'~ߪUUUUUUUU>UVȪUUȪUUUU ^]UUs*7UU 7UU L/JJ߀bOUUǬǬ.UU% *UU *UU#*UUa1BUUBUUS*$mUUVJ!J!%UU@B*rUUUU*ɀ"UU*4UU4UUYFUUUU*c*mUUm |n  -p 4`2"*UUUUQ*B*jUUjUU5* U^*DUUDUUsU7*R!XX-0UU+UU,+UU,GUUIUU*2UU2UUUU*QUUUUUN*ҪҪ8*UU*UUUU* UUKUUXQUU*̪UU̪UUUU*'cg*kUUkUU{7*n֪KUUUU0U-aIaIUY>iUUDUu-檫-檫UUoUUUUUVUUM4`5UUUU5UUUU^ت"UUU  UU03u_UU_UUM誫;sU窫窫Uyp*UUUUUUUU*0zUU*UXUXU,UU{U6CUU..=UUً*UUUU*pm*UUUUU*Uo0*UUUUUUUUӪ*6,w**UUUUU쪫Q׀tUUtUU6oUUUNVHUUUUHUUUUD*$˷5UU[>UUG䪫G䪫Q*UUZiUUZZ6UU7UUE7UUEPUUUMª?UUª?UUUUUU*CUUCUUUUcϪϪ΀ĪUU쪫-UUU/UV1UU1UU0UUUTSS/UUEUU??Q*bIUU>*ll'UUcZZUUʪUUZUYQYQUU0UU0UU*nU@UUUU檫USUUUUUU"Kc⪫d*eefUgcUUڪڪUU80\|\|0(*>UUUU>UUUUN*۪GUUPU@VM:UUM:UUQ**T *UUvUUv *ހ]Ҫ*UUUhUUhUV䪫UUUUUUUU**OUU*UUbbUફUUq 0 L QUπ˪d˪dU)ЪMUU UUUU UUVVUU&UUUU&UU8VKUU4= UUzWUU䪫UU䪫UUheC40UUCUU0UUCUU*M\_-EUU-EUU5HUUUU窫,ŋU0UU+,+,HUUUHU*3UU3UUU*QUUUUUN*ҪҪ8*UU*UUUU* UU몫UUUUUUUV]UU]UUtUVUU/.KKX>eoVߪ-UUUU-UUUUͪKTpP$ދ\mlD>:Dh=D6lD>:Dh=D6m(d P ddPd @ U UU UU Uˋ5UUUU*⪫*⪫UUUU;Z/UmU__~UU2 `UUUUH.H.6UU?UU@0٪UUBUUBۀ!UUUUફ^UUeGUUGUUUU#sU窫窫UyG @ U UU UU Uˋ5UUUU*⪫*⪫UUUU;Z/UmU__~UU2 `UUUUH.H.6UU?UU(&UUMUUMUU^UU*UUUU*\UU*UUUUUU*}KʪUUUUUUUUꪫ60٪UUBUUBۀ!UUUUફ^UUeGUUGUUUU#sU窫窫Uy` mP`UUU  UUUUUUU|gUUުUUުUUUUg5*mUUOUUO*=+UU)UUUV!*UUUU' Ë,쪫!UUUU!UUUU+ P(UUUUUUUVUUUU䪫UU䪫ت٪kΪ3< ', m m 3UU.))"U+*7UU0T{UU**UUvUUvVUUq窫UUUUU骫Uum IUU,:YUU+UUUU9⪫ዯ"* UU UU#*UUUUުcyUU| | UUUUܪUVUUUUwUUwgԪq窫UUUUU骫UumYUULUV?UU?UU0UV,!UU7UU!UU7UU;ˋAUU{<k7k7]+UO;zrlы2**UUUUUUUU**a_UfݪmݪmU|=Ti ),H  !  m@L JUUUEUUE!CU0;GM_UU_UUu3| m@`3UUUUG0_UUCU誫E誫EUUJU' 'cުUUUU򪫭 UU$UU!UUUU=*UUU˪UU˪*U9;UUU7Ϊ7ΪBUUUًm4$p,UUU#UU#4UՋUU-ª*UUUPUUPUVUUUU٪UU٪ǀU/r BUU9UU9UU|UUbUU[*ڪSڪSSCUUUUUUUUUUUU֪UUm$֪^UUMUUUUMUU&UUXUUXSڪUUڪUUUUVUUkUU9UU'9UU'rUU/?**UU&UUUU&UUt0U<*=UUP*BUUBUU!*Ջ4*UUUUUUUU*Um m|0P|,rm',xP0xm0xP,xr'M, M, '` m m8 m@g ' ml m3g m8L mLHoH mL ',D 'cު UUUU" UU#*UUUU!UU*=*qUUWUUW*I;9U*7UU7UUBU*ًmP mDTTDm m m v  @ m0 m M d< M m<\]\a [`[\Z[Z_ Y]]_mf|p#⪫IUUWe=U*/UU/UUBU*CU++>UUAUUUUAUU7 -(e**UUUUUUUU*#* U#*UUUU U*=*qUUWUUW*I;;U7UU7UUBU檫ً TDXXD;  d7U㪫UU㪫UUU몫IUUU磌磌UUU}l** UUUU UUUU**a*(U@UUQ@UUQ *uU.(d. **@UUUU@UUUUa**UUUUUUl}U磌UU磌UUUGUUUU䪫*䪫*UU=UUv ,v v Pt @ \UU*VUUUUUUUU**iW04*"UU"UU*ۋ8_PׅUUUUફ]]v*aUUaUUX*O0h*6UU6UU#F*UU9UUԪ"Ԫ"UUUUW  d|<|0+UUªªGUU7UU0UV )UU)UU!i!)UUx)UUx0UV7UU=UU=UUNWUUjફjફ؀UU]MҀnUUnUU)WUUnફnફҀUUM]؀jUUjUU)8t`|X|t{ !  UUXUUDDUU"UUݪUUUUUUUUڪp.UUUU(UU(2UUӋϋ*UUUU*Ut l D\f @hf f   Tp|H$H( T ( < 0((x)UUUU'ɋ-UUફફUUW)UUUU-ɋ'UUફફUUWoNS=UUS=UUC3ȪϪ֪x֪xUUqiqUU֪֪Ϫ Ȫ5UURªRªUU+[\]` [`[\ [^Y^ [^[^L%UU%UU2UV?UU$#}"UU}"UU}}}** UU UU*83EUWݪWݪqUMg݀jjlnnswzUUzUUm@pp@g ;    " @# $ (+1U+UUת+UUת7UϋU*UUUU U* UUUUҪUU$IUUd<X    <Td|t/3UUUUUV*UUUUUVUU5U߀ŪJŪJU%L #UU**UUUU* UU<7!UUUU*UUH*:UU=UU:UU=UU*RHaU*媫UU媫UUU*eUUUVUUUUa*rUUrUU*e[U ۪UU۪UUUUUVfUUfUU* UU7D߀QJQJn%H *#UUUUU *U%UU~~*UUUU*UU~UU~&*ߋG*;UU>UU;UU>UUR*D D pk~UUqꪫqꪫlUUgΪUUgUUgUU۪UVUUUUUUUUUVUU7DUQϪQϪnUAH *UUUUUU * UU1UU(*{{{{{0*=UUߋG*;UU.UU;UU.UU>*Lk䪫rUUrUUeΪUUgg۪UUUUUVUU7D窫QUUQUUn?H UUUUUUUU 1UU(*UU𪫪𪫬𪪮𪫮0*UU=UUߋG;UU;UUȋwx<UU>2UUvUU(juU쪫UUUUUUUVUUUUUUCp"!UU7*#UVK%UUK%UUdUUdUUhVmUUmUUhV~dUUqdUUqUUKڪKڪ7*ܪ"ުt00& ~' `݋@UU.!UU.!UU"UU +wo骫nUUnUU؀YKBUUUUj䪫YUUHUUUUHUU$*-*ުUUUUUUUU*o!*?UUUU?UUUUY*HduUU5UU:ŋ)*{UUkUUk *c[< lHHX@qUxxUU U*UUUUUUUU ުUUVUUUU쪫UU쪫*UUo`#Ҭ\j]IGUUIGUU4#!UHHހU {!.EEVh(( ) `IU=3UU=3UU U;@mUU檫v檫vUUaQ**UU,UUUU,UU*7*U6-UU-UU+Uŋ$**UUUUUUUU **m@⪫@UUUU'**UUUUUUUU**%oUU VUUUU&UV4UUUU4UUUUC*R`I3UU9UUI0HY0UUUU+?USSoU{BBKۀ(UրUUn*n*9HŨ)QUU9UVh!UUEܪUUƪ۪0 HHUU;UUJ1UU"**UUUU *UPHPR\\9UUUV媫UUUUo pHUU2ՋUUPHPUUUU@4UU UUN UUUUU UӪOPLNsUUS*\\9QϪUUtUUt᪫uc֪?UU̪*۪UU۪UU*q誫ު l{UUwsUUUV*UUUUUUUUpꪫફtHEUU2"MUU tLLL(=€^^׋U U$*~UU~UU*UUꪫ%UUJT(xX4\ ll| l l| p|,,m@\p\JUUd*ϋV*᪫FUUUUFUUUU#*/ƪɪn̪n̪aUUTkTkU{}4 m5 6 u4 p\UUEVUUUUUUUU^UVફUUફUUUU %UUUJJfUeg@UU2U骫qqUU檫UUfUUԪUUԪફӪcҪ$KUU?UU<*CVHUUHUUD*ˋ?UUߪ;*UUUU2,U"LGUU#UUتتUU;UUDfWUUӨ)UUGUU5F5F@*JaUUhD2X0 mtlƀx$$eVT.C$$Qx:UuUUkuUUkUTTUUJ*rUUrUU9UU*pb%UKUUoKUUo_7U _*UUUU$a*bGb*^UU UU *ŀ :UU UU ٪bqd/  2v!UUUU%UVUV)UU UUWd*UUUUUU*UUc U||UUU|iUV)UUcUUUફફUUUdJU3UU㪫3UU㪫.U!!!,0 --UU:UUҪ:UUҪCVUUMUUl ",UU"^^/UUUUUUUVUUUUꪫ*mmUUUɪɪU5{Pm cW UUK K KUUKb[U*UUUUU**UUEESUɪjUUUU**UU0UU0**uUUv3UUUU3UUUUGKҪҪjܪyUU檫{y5*UUUUUUUU*gWUUUUުUUުݪUU_W4 "$U%UU-%UU- UV1UUUUUUUV3*UU1UUBn%UUn%UUUUUVɪUUɪUUU?0ªŀUU!UUUU!UUҪh6h6BN(,UYYU>XpX0UUUUH9UU?UU2?UU2eUU Ë5UUU221UUU}Rk| h} Hpx$<(UUĪĪMUU鋻UUptUUtUUucUUڪڪUU> *UUk+UUppaRΪRΪ*UUUU/*UU1UUUU1UUժAUVpQUUpQUUX- p8(UU5UU5UUNUU󋷋*UU) ) +UUKP}U*骫UU骫UUU*sꪫUUUUw磌UU磌UUU):D2l3 rUUMUUrUUMUURV3UUt3UUt|(N N 9\UU$C$CU!U8UQͪQͪnU(&UUUUǪJ*gWUUgWUUy[jQ H;4QQnp2qUU|*1UU 1UU *nUUnUUUU\Ls*UUUU* Z*AUUUUAUUUU2UVUUUUUUUUx*e8e8U*UUUUUfl9 : qd/ (  2l3   Rk| h} ;- 2l3 @- : <- $,5>+(@$x`yH #-v7a7aETSG. UU WUUUUO*u@UUU:nXUB%B%0UU/UUĩĚ9U:RwE*c7c7UUUUUU*]UUyVрUU.UU.UVUU\UUUU\UU*D*h'8 qUU[UUqUU[UUlfU>u}UUu}UU:UdUUUs˪˪CUUUU[p_UU==UUફUU4UU4ԀUU+ƪƪUUpYUU*KUV$UU=UU$UU=UU/.UVUUUU?ϋ$IUU.98UU)8UU)BUL׋4UUUUUUUUڪcm*UUjUUj*=UUVUUUU UU *EUU0UUUUUVVUUUUUUUUફVkUU{6a}*7XUU7XUU,*$a3737_ ,w UU6'6'cUUHcUUH ӀunnUQQ/T2 \UUD,TfUUGUUk_*ƪƪ*U~*UUfҪ3UUUUsUVPUUUUPUUUU(*ڀ$٪ڀUUUUUUUUUViUUiUUEUV3!UUmUUVtUU]UU]UUUUĪԪ)Ԫ)⪪&# UU;UUF UVrUU@T^WUUGUVr7UUeUUpTF'UUaUUT*MMU7*'UUs*[ UU[ UU*UUѪUUѪ.U=8=8 *UUڪ/.)88jրUUUUl}U磌磌UAŀ``AP8 \.րv88)sU᪫᪫Uul*UUUU*Ջ:ff- ]UUUI㪫㪫U)*[UU[UUUUĪĪUUUUM;AUUGGQ3[[jY*yoUU:IEU*UUUU[**eUUUUn;UU;UUFUUP00UU|VnUUVnUUWUVUU@UUUU@UU* * U((UU˪˪UuUUUU*C8UU*8UU*NUUu*TUUUUwOUUU=䪫=䪫UUUCLUUVUUUUUV7UUEUUUUEUU*KUVQUU UTUMM*>UU99J*ZZJ*9\9\*UUUU U*/o*UUUUUVUUUUaUUaVvUU9kl lw v +V  t mpUUUV*UU&UUUU&UUV2UVUU>UUUU>UU*CVIUUp՛Dʥ(UU2U6&6&IUUUU8p8iUUUVUUUUUUVUUUU窫USUIUUbb}UUU,8pmpp8,}UU *b@UUb@UUIUUVUVlUUlUUx*sx*[lUU[lUUVUVUU@UUUU@UU *,8p8I쪫7UUUU7UUUU*LLUUApUU*媫媫UUͪT٪T٪*UUU|  vl  hI T xohT`\7 y$8 t  UU}UU#V*:UU&:UU&QUUUU**UU$&UyUUMyUUM]UUUU*PPPUU*UUUUUU M Mu&UaUU]UUxVUUPUUP8;Ū&Ū&ۀUUUZUUMU̫̻ˮˮWUUnuUU_VUUJUUJUU66UUUU ~TfӀVU22|UB,'UUUOUUOժjUwhI - UU 3wUcc`LUUsUUsUU~UVUUUUUUbbEUUlldEUU=b=b dUUQUUvUULUU=c=cUUwU檫 UU&UUUV8UU8UUDUUZުުUUUUUZp*UUdUU>dUU>O\UUUUUUPPUU\UU>>UUUUUUU;ު;ުZUUUUUUUU٪UVwUUYՀhh*)UU""*UU*UUUU*YYՀhhhhՀYVL xg VL   pJ&UUsUUUUUU**UUUUUUUU*tpUU*6Umm=UUUUUooP*7U 몫 UU) 0 l  UUUU*|BTTUU=UUTUU=UU`lPK**EUUEUU9X-UU-UUU/ *xUM6UU6UU*uUU`!*UUUUUUUUVUUlUUl*V@TJ!5 \U*vUUvUUUU*.UUU55YUULBcBc%w*UUUU[I[I*UUUUܪUUܪUUUU*:;UU:;UUUUѪhѪhxUUUUX\ uP uP ,  eQ r*MM6nUUUUUUUU|dpUU8*tpUUtpUU:UU*FFŪUUUiUUiW4UpHgpUUUUt*磌磌UoUUoUU݀րb誫b誫UUUȪSpx \\pUU8*tpUUtpUU:UU*FFŪUUUiUUiW4Up4 pyWUUUUZ*bgUUoSS#,@@vêUUU]UU]Y.Uh*UUUUUUUUw檫HR D * P Pg o< \ L ;D E    F G 4H  0 \$@_X*^㪫㪫ݪתUUתUU*ŀҪ֪@2*UUUU*tVL ;= o2 * *   > ? T O +V W 1 X Y hI $] o< L ,ݪ UU UUUUUU!44!sUUCUUCUU\UU G Gi,,"UU D D\_UUCUUCUU!!44UUyUUyUUUU^ ,gcpdcB{*5UUUU*FH-U[kUU[kUUU5BBUʀ[ [ -UqFH*pUUUUUU{*UUBga hx@ Cdd Hp\$dd@x$pUU8*tpUUtpUU:UU*FFŪUUUiUUiW4Up A p p(P A p +<B P jps6V W DdLL zl*3UU]3UU]JUVIUUaUUaUUuUVUUtp#gCCCCg'$*HUUDHUUDq*܀.)0gh hi >L@ekЪUUUUU=q*UUUUUVUUUU-UUUUN8?*k~UU&~UU&*BbUUwVfUUfUUOYUttUUoSS?++ UUUU/Ҫ/ҪCUUW+wZ/UUUUNhNhwUU7UU4^MUUMUUEUV=UU=UU.-UU 3UUgUUUUgUUת@<)*OOl*n]PUU/PUU/(*<SUU*v*UU$UU$̀t1l t 8 QC LLpL($dWUUpVUUUU3UU3䪫UU䪫UUݪZ֪Ԫ GUU'UU_  UU*ko s0p  D E RXF RXF 0 ulG >H  I *X Rk| h} *l$tHv,k~ kh 9kl }$J = t4몫UU{UUUU{UUUV`UUUU UU;xeUUUVUU hUUEUUEUVUUUUUU몫rUU4oqUUUVUUUU<4PUUPUUbVUUuUUTUU;UUUUQUU`uUUbVPUU?UUPUU?UU<nn~*5UUUUUUUUUVVUUUUUUUU*V7UUUU᪫qUUqUUtUVw UUUU UUU UUUUUGVV:UUUU:UUUU..U! Uc)KUU7979EU*UUUUVUUUUUUتUU[UVUUUU7**UU/UU/UV?*UUNUUN*]UUm t$pK  xL lL$$S px$$$ Lth,p@ eUU*R,UUR,UU>;**UUS UUs[UU[UUvUU-H,pM XhN ,pM ,pu EXhN ,pM ih8O wUU% ObUUUUbUUUUpV*UUyhUUhUUQUV\UU:UUv:UUv*4UŪwUUŪwUU\uUUUU*(R(R;UNNۀsUU@} 1tt ZUUUUUUЪrUUU2B2BGU\ફ $0pHUUUU*9IUUJUU:4*4*HUUUU,*ko s0p g *ko s0p `  BX0&*gLUULUU`$toUUW*ހHH;ꪫUUUU⪫UU⪫ʪUUK}UUUUUU\*UUUU*^#MUUGUUMUUGUU&etUU[&+^+^UU/UUbUU\\UUgw(L88L\QC 8 m UU*/whUU,~RUU=ɪ=ɪ'UUEݪȪȪDUUUUuUUVUUUUUUUV.UUUUUUUU*fbUUu*88PUU-UUUU{UU*qUUc*U*UUU*UU9UV4UUHUU4UUHUU VQ UUZu7*UUUUЪUUЪ*UU/(QUUQUUYUU lu v lu 8  lw v qUUX??.*!<ċ eUU*R,UUR,UU>;**UUS UUs[UU[UUvUU-H,o<UU@UV|*UUUUUUUUUVLUUݪUUݪ[UªSTM ldtd eUU*R,UUR,UU>;**UUS UUvUUH,t,LU*;UU;UUU*9=*UUZUUZ*-,BX0t  ulG @D RXF dg =  l *t$x|TpUUUUЪ4U*MUU UUMUU UU?1*͹I;*UHUUߪHUUߪR*Uj`UEUU?UU3T3TUjUU UUUUUU㪫55UUR8R8VUU NUUNUU㪫-=UUU88UVUUlt>/*U^UU^UUv*U"OGUUGUU9˳K5[CUUkCUUkK{ߋ({**cUUTUUcUUTUU1*>)*UU\UU\*EAU˪˪UH)*UU\UU\*=KĪUUUU䪫HA p  *pp  eUU*R,UUR,UU>;**UUS UUs[UU[UUvUU-H,pp|M ;$*%U~MMU^UU4^UU4gVUUqUUZUUUUUުުDUUZ8UU8UU&UUUVUU檫 UUwU=c=cUULUUv]UUmVU~UU~UUVUUuUUuϪ[{AUU8 UUZ-U*pUUpUUiUUUU:UU~UVsUUsUU`LUUcc3wUUU wUU٪UVUUUUUUUUZ;ު;ުUUUUUUU+7+7QUUUUUUVUU2  UU*/whUU,~RUU=ɪ=ɪ'UUEݪȪȪDUUUU**UUUUUUUUUVUVUUUUUUUUVVUUuUU@ UU{UU%VUUUUNaUUȪaUUȪUUqUUc*U*UUU*UU9UV4UUHUU4UUHUU VQ UUZu7*UUUUЪUUЪ*UU/(QUUQUUYUUXd,aWk;m01 / $P X9lËplaWk*, ы5 1 / $P 4* UeAAaUIl׭.bUU@V@V UU*têUU]UUUU]UUY.h|xahZUUUUUUaUUtQ Xp.UUgU8585VUUUߋ䗟ϋU**8UUUU8UUUU**.pphU]UU]UUwUB%Ћ%HsUU΀..UUeU(pFm'* UeAAaUIl׭.bUU@V@V UU*têUU]UUUU]UUY.h|xpEhYUU4P Q HUUUU}UV[UUpp.UUgU8585VUUUߋ䗟ϋU**8UUUU8UUUU**.pphU]UU]UUwUB%Ћ$vFmmp4_Uફ=UU=UUU3+=LOLOm&XpX"lUUKêKê%UU#ުUUUUUUUU S"6R6RՀUUQQ.UU8UU֪8UU֪9ު:檫HUUݪݪmUUUU!UU!*)1UU*$UUUUUU2*UUы~p*b.UUb.UUM8W8WUUiUvUUc*OO1:UU%UU(Q9UT㪫`UUQUUUUQUUUV>UUUUUVUU; m(p4Qwcc[SMU*ɪUUɪUUU*XpXU۪۪UAK㪫UUUU֪UU֪UU'O"6R6RՀUUUUUV%UUQ%UUQ.UU֪֪8ު9UU檫HUUݪݪmUUUU!UU!*)1UUUU$UU3UUUUӋrf*Y#UUY#UUF3B3BUP^UU)X<R @UU+UUNjYUUUL˪˪UUH [2 l9 +V  t Rk| h YL R fS R   fS 4\  V W  Sk| h} ` +|weUUUUwUUUHjUUHjUUހQ*\\!Q*jUUjUU\vUUvUUUUUs|svUUYUUvUUYUU\w!U\\ހUHHUUwYUUYUUweUU|vUUUV*UUUCUUCVZUUUUU*UUUUU//A*ZURCRC]*Uhr|rhy]*RUURUUA**//UwaUUaUU*wUUUUV*UUUUUUUUUV*UUy8x _UUUUVVUUvUUUUvUU*T*b`/UUUU_v_v*4UU'vUUgUU+*DUUZUU0o0oUU*&窫UUUUZ3EUU3EUU+UVUUUUS"'UUFUЪeЪeUUp{UU{UU/UUf/UUfIGUU=PUUڀǪǪ&UUU;UU; 611UU*檫UU檫UUUUUVWUUWUUI*;yUUUѪѪUUZZuUU UBFTUUUUTUUUU_*j[QUUQUU@9MUU@UUUUPePe[UUx6U3ii4Ufd*0UUUUz*6|ۋ*UUUU*qWJWJI߀;-UUN8N8VUUUUªUUªUUUU㪫+=* UUUU*cUbU3?3?@UUًUUUUQQUͪUUͪUUΪϪϪUUUU%UsŪ[Ū[U?#[P #U" UUU䪫䪫䪪䪫䪫\UUUUjL$U!UU!UUUVUU$UUUU$UU $* |iUUzުzުUUmeˋ4㪫)UUUU)UUUU( UU*֪UU֪UUUU*K=*UUUU*H)V\V\=KUUU֪7֪7UUVU (VUU)UU8)UU84UUˋp"\.-.-р!P(R.r..uр"YUUPUUG G 9+UUAUU*8UZUUUUOU磌X"v*.^UU^UU/*R(PU!U"H<AIUUQQUYUUYUUZ[[XUUU%U*ŪUUŪUUU*%WP #U" U5UU8UU;䪫;䪫D䪪M䪫M䪫\*UUjL$U  $UU$UU UU$* |i*UUUUުUUު*UUme NUUNUU㪫-=UUU88UVUU TpUUUUЪ4UUU ?*1UUAUU.;UZUUUUR*磌[UUj`UEUU?UU3T3TUjUU *UUUUUUUU*㪫55UUR8R8VUUDD\00)V\V\=KUU֪֪UUH>/*U^UU^UUv*U"O* UU UU90@(UUπCUUߪCUUߪKUߋ({**cUUTUUcUUTUU1*>)UѪ\Ѫ\UEA*UUUU*H\@@\00\<*U77ȪJ٪]٪]Uh*rqUU*gUV&UU]UU&UU]UU7UVJUVHUU7UUHUU7UUVVeUU*r*تNUUUUNUUUU'*UU᪫ UUdUUd5M66|UUUU,,zUUfAA1&UUUUߪoUU^kUU*yV6UUUU6UUUUVaUUaUUr*UVUUmDT(UU**T4UUT4UUcBUVrPUUrPUUR* R*UUPUUUUPUU(UVBUV7UU4UU7UU4UUHUV*YUU=UU7*UU⪫⪫&dUUUUUUUU*VƪrZUYQYQF30UU30UUUU*uUU*݀*F*FA0XXS *UU(UU(UU;*_UU]pUU |@(T$'H |(+D L$D$ (@Lj\* MUUMUUDUV;UU;UU8 5UU"UUUU ܪ[sPUJUUŪ0Ū0UUU%UX[UU[UUZxYꪫYꪫUxQUUQUUIAH74T<<T;4/DgݪUUUUUUhڪUUڪUU_UUUUªUUC -*GUUGUUi*1WP#U( U͋7-UUUU-UUUU(x#ꪫ#ꪫ#UxUUUU)UU.LpP U U#**UUUUUUUU **e0U>UU˪0˪0UUU11U窫˪UU˪UUU?LUU#*UUUUUUUU *ߪg0U>UU˪0˪0UUU13E窫WUUWUUq?X U U#**UUUUUUUU **e0U>UU˪0˪0UUU11窫XUUXUU?LUU#*UUUUUUUU *ߪg0UU>UUʪ0ʪ0UUUU33E*WUUWUUq*A UUUUUU"UU"UUUUUUUU ߪg,q>UUW0W0EUU31U*˪UU˪UUU*A@` UUUU! #*UUUU䪫UU䪫 *UUg0U=*˪0UU˪0UUU*13EUWϪWϪqUAh*UUUU*#*UUUU䪫UU䪫 *UUg0U=*˪0UU˪0UUU*13EUWϪWϪqUA **UUUUUUUU#**"UUUU䪫UU䪫 UUg,q=*W0UUW0UUE*31UU˪Ϫ˪ϪUUAp\<@p+\;**UUS UUs[UU[UUvUU-H,ppM Tݥ(wL eKUU7J*XUUXUU`*x窪mUUΪΪ?MUUU9UU9|UVU_UUH pL kUUWUUƪLHv׀FUU:FUU:"VUUUUU*zVzVU*ڪUUv,kqUUf4F4,->D+RUl UUUU ȪvȪv>[UU@UU@ UU~'UŪjŪjUUڪlUUlUUUVUUoxUUoUժժ-UU;UUj;UUjb84,'8UUUUN"*6UUUU*B@%UUJJmUU$S*BUUXBUUX1F44^UUz $p8\pM|`$` 0  QC   0 t U mH4TZP*UUEڪEڪ6̪''U+*UUUUU*䪫d&U;;>-UUXUU-UUXUU`h*qUUUUbUUbMUU wUUT<\;= ,K  D K o2 ' E t > dK 6lG K ,<,UU>UVUUUU{UU{媫k*ZH#U]UGoGoUUUUUOUUKUUUUCUUUV>UUOUUުU檪[[`UUUUUêêU{UUbUX*MM'UU=UU5U::5UU0UUԪ0UUԪ&UU==0#U˪"˪"**UU``UUUV UUUU7UUD*OUU`UUfH*hUUUUUVIUU6UUUU6UUV*UUUUVUUUͪUUͪUVUUUU*UUDUUUUUUUVUU7H5UUH5UUVGdddUUnQUU IUVAUUAUUUU3%%(Up+UURUURUVUUUUUU몫UfUUu88OUUUUCC gxUUUU<檫UUUUN*SUUk=êA*UUNUUUUNUUꪫVV_UULj*WUU&UU&UU0V;UU;UU0Vۀ&UUB&UUB*ULUU;nn*UUUUWhI <x 9kl  $] K }$J hK #b }4 #b  }4 c a $dK m dDK pxdh@dpL(L$t@ K xL hK pp9*!UU:UUUU:UU*a@@h5UUfj,j,(ˀUUpUUK* UVBUU UUBUU UUB*͟yUU쪫UUUU* (pŀ#UUUU6UU6*׋aUU>+UU|UUVVtUU{ApA-UU((' & UUتU*bbՀU_@vêUUU]UU]Y.Uh*UUUUUUUUw檫yWUUUUZ*bgUUoSS#,@BX0t DV dW X p DV dW K X p  uP ;=   D   C8$HWgUU#gUU#3qUUJYUUڪUUUUUVUU}UUpΪUUCUU ꪫUUNP*PUUUU~̋X*laUUUVzJUUiJUUi:UVU*UUê*UUê*U=UUUVUUUÙUU檫XgUUUU[UVOUU8OUU8>*FTT\UUra*YVUUYVUUUU77##UUUU9ċ000? CT H TKT (;UUUU֪⪫֪⪫UUUUK}UUUUUU`*UUUU*#GUUGUUe$$*X$t;*UUUUҪUUҪ*UUUU*ߪUUߪUUU*}wlUUUU m(UOUUQOUUQ'uU.|(twO 4T X dT @ K  xL hK T T I T uP ;D E  )0gh hi h ;D E   )0gh hi <L      k r `i VL  *ko s0p  <Y ` *  s0 <Y ` D  *  s0 0x  ;= ,   D   o2    E t  H H *   RXF p *   RXF   +V W H  Rk| h} H  +V  t Rk| h YL +V  t E  Rk| h YL u$  +<B |  ih8O X  o< p  =  0 o< |0  =  P  o<  d< = t d< @ l   xL    0 K QC tK A p p(P @  XhN ,pM ,pu D   0  8Z QC   Z a 4hZ m 8UUUUUVVUUUUUUUU*e UUUUĪy[y[媫۪UUUUUUUUܪguUkkuUwtUUUUUU]UU>)UU>)UU/6 UU UU*ՍۍIUpCpC*F*UUH||a D m L| H @K [ @\ 5D|] <^ _ [@` a @\ b c d e Ef &g h @i j [@` k l )m \\,n m l )4m 9o p Uq m $ 7*nUUnUU*zLUUu}__OHUU?.?.5UU+UU誫?UU?UUêUU)UU)骫UUfLfUU''<LUUUULUUUUTV窫]UUh7Laav,{,wcGcGKi3(,lAUUAUU? ?UUUU,w@g a UUU㪫UU㪫'U&*UUUU #*~#UqqdUWWUU䪫UU䪫UUUU۪_r s Pc UUUU⪫UU⪫'UU'UUUUUUUU "UU#U䪫䪫UUUWWUU*䪫UU䪫UUUU*acUU⪫⪫UU'*UUUUUUUU #*+UUUU䪫䪫UUUUWWd*qUUqUU~*aX' *UU UUUU UU*"UUUUꪫUUUUꪫꪫUUoUUoUUUUUVꪫUUꪫUUUUꪫw UUUUUUUUUUUU *UUꪫꪫUUUooUU*ꪫꪫUU檫cUU=骫=骫KUUQUUߪߪUU UU UUUUUUUU UUUU UU *UUUꪫ ꪫ UUUooUUUUꪫꪫUUUU UU UUVUUUUUU U U**UUUU* M]UѪѪUycs`F%#D`,ċ3\ld3\ċ|tH`|,,8|H``,PDp\, ppċ@lDT,ċpp@*UUUU*5UUjҪItHD2"UUMUU UUUU<`WUUnتnت΀UUECUU⪫'UU⪫'UUUU-p%UUJUUJUUeUUݪՋD* UU>UU>UU1*)UU<eUU"UUJDJD%UUXUUtWUUnتnت΀UUEE΀n'UUn'UU-`S2UU֪>UU>UUDUUՋ(<t܋g7DDߋ[*UU~UU~*u}DUUUUUUzzUUmOUUUUЪΪЪΪUUUUO_7]B7UU7UUIՋ*AUUUU*90\Jt`,/XO(U'U&&UU&UU&UU(UV*UU*UU.UU2Ë-UU U*U*0a**UUUUUUUU**ok*UUUUUUo㪫㪫UUUU̪UUUUAҪ qU *磌UU磌UUU *{yffˀGGɪ*UUUUUUUU몫*MKʪUUUU8ꪫ͋qU몫몫UçË*UUUUUU *UUkAɀ#UUh#UUh2ً͋:*UU&UU,&UU,*4UUNjUUʪʪUUK UUU媫媫UUUoªoUUS%UU UUUU UU gg{_WUUªˋUU/*$$UU$$UU0UU*Njɋ2U誫'UU'UUU3UUwcKcKΪUUUUફUUફƪ窪S(UUOUU!VN.UUN.UUAUUͿUUp*UeUUJJ*%UUGUU|ƪmƪm*րݪVݪVU€EUUN UU UUV3UUUU3UUUU5*ËxUUc֪;ck sUUsUU@77UUUU7UUUUI窫F-UU3UU5UU̪IUVUU]UUI*UU8UU,8UU,*DUULUUc֪;ci oUUoUU}`׋>*UUUU%"*+siU*ߪUUߪUUU*aUUUVBUUUUUUY*ʪHUUʪHUUUU$*/UU⪫ªUUU:=UU%/UUUӪUUӪ㪫UI@ *UUUU!*MUU&a7Y!wPcPcSCE*Ȁ.UUb.UUb>*ًˋ6UU--!'*UU.ʪ(U5UUl5UUl@UVKUUUUફUUok*UUUUUUUUV*UU;cUU:uUU5UU:\ŋ({kk c[`#Ҭ\j]IGUUIGUU4#)mUUOUUU/UUUӪUUӪ㪫UI@ *UUUU!*MUU&a7Y!wPcPcSCE*Ȁ.UUb.UUb>*ً͋!UU(UUS)V*6UUUU6UUUU>**ы(( \⪫oUUUUUUުUVpUUUUUUUUfܪ;cUU:XsCoDSSC3/UUUӪUUӪ㪫UI@ *UUUU!*MUU&a7Y!wPcPcSCE*Ȁ.UUb.UUb>*ً6- U$$UUUU{UUUU𪪏\D' sbMUUA55U8*?UU@mUU檫v檫vUUaQ**UU,UUUU,UU*7*U6-UU-UU+Uŋ$**UUUUUUUU **m@⪫@UUUU'**UUUUUUUU**%oUUUUG*VaUUUUr1UUUUUUyuUU*UUUUUU*k+ً:U 'UU'UUU#UU誫誫UU o` ,UUhΪΪIUU̪UUUUUKP|UmUmԪaUUUUUU磌LCK*Q UUW UUW+֪UUUU=ϋBUUUU[٪UUUUUUUUЪߪKg\TA'UUUU#UVUU(UU(/UUIUU:*XXA_UѪzѪzUim@DʪUUUUgU٪UU٪UUU<MƀVVRߋI?UU?UUċ׋3VV-]܀ r UUr UU5UU&MUU$UUUUUUm<*8UUUU*17UUUUتUUت㪫UUGC*ʪ8UUUU8UUUU=*檫{{-*UUU磌UU磌 *Uqlt UUUUUU UU ** UUUU UUUU ** s쪫UUUU𪫌{A**UUUUUUUU**1t؋( ph_UUHUV$UU1UUHUU.UU۪H{ UUUUUVUU}UUU80誫UUUhh* UUUU'UU>UU%%UmUU(vUUxeeͪUUUUUUUUUVGUUp%UU8V$LUULUUccg* jiUUfVdUUrdUUrXlff8ۀgpk~UUqꪫqꪫફUUUUUUժgg۪UUUUUVUU7D窫QUUQUUn?H **UUUUUUUU * *1UU(*{{{{{0*=UU݋G***Lk~䪫qUUqUUlgΪUUgg۪UUUUUVUU7D窫QUUQUUn?H *UUUUUU * UU1UU(*{{!*{#UU{#UU{0UV=UUSUUGUV;UU;UUȋ4 @0<| |<0|=(t,P0t(d2;D E   )0gh hi <  F G 4H ;v vlj |k hv F G 4H Gv vlj |k h v F G 4H $ vlj |k po⪫UUUUU⪫Uo"܀UUDUUDUVUUUxd|KPUUUPKK'UU=UU88PUUߋF*9UUUU9UUUU2*m,l\UU"UV/UUUU/UUUU13u3u,UU*UUUU*- ܀UUDDUUIŪUUUUUUӪUUe;psUUUUɋUUv*g.UUg.UUQUUYYn*DUUgUUgUUbUU9`\/UHUU<<|UUU*]UU.UU"ml5 6 ;D E v )0gh hi 8v ;D E | )0gh hi p  ;D E   )0gh hi $  ;D E x  )0gh hi tx  ;D E \ T )0gh hi ` T ;D E  D )0gh hi d( D ;D E  Dv )0gh hi ( Dv ;D E (  )0gh hi $  ;D E ( h )0gh hi $ h ;D E 8  )0gh hi @  ;D E 0 ( )0gh hi L ( ;D E  4v )0gh hi h @v VL  v *ko s0p Dv VL Pd *ko s0p  VL L *ko s0p x VL T  *ko s0p H  VL T  *ko s0p   VL p T *ko s0p  T VL Dl D *ko s0p  D VL l Dv *ko s0p 4 Dv uP w lu td uP v lu v v +V W , v Rk| h} <v +V W  Rk| h}  +V W d  Rk| h}    +V W (  Rk| h} t  +V W  T Rk| h} \ T +V W  D Rk| h} $ D +V W  Dv Rk| h} $ Dv Op (lW  Vt @}  Op (lW Pg Vt @} g Op (lW  Vt @}  Op (lW ` Vt @}  Op (lW , v Vt @} <v 8^ v &  v 8^ Hd & d O|  @D  O| hg @D g O|  @D   O|  @D  O| tv @D pv #b hg =  g #b  v = `tv #b @d =  $ #b L =   S S m ttttpDDmAUUm8UU8UU-R""UUUU#*UUUUUU*UUª*Ǫ)UUǪ)UU]UUPUUP*cvHz/UUUU`v_xDk$UUUªUUªUUUUsswU$?/*UUUU'UU44BUÀUUUU_*d:UUiUUtiUUt4UUXyUUsn[c[cH5DUU5DUUUU0*mAUUm8UU8UU-R""UUUU#*UUUUUU*UUª*Ǫ)UUǪ)UU]UUPUUP*cv6KKZ+\$3Ltt?/*UUUU'UU44BUÀUUUU_*d:UUiUUtiUUt4UUXyUUsn[c[cH5DUU5DUUUU0*Ћoܪ*UժժU[UUUVUUUժUUժǪUUaDaDvUUUUVUUUU8UUǪFaFaNvVeUU[*8KЋvnp 8ȋ>XO UUUUUUDժDժUUUUժDժDUUUU*UV*UUUU*UUUU8ǪaaN*vUUU `UUUUJ>J>6UUSUUȋu k m M k m 0 k m y  k m 0 P k m   k m 0 0d k m   k m 5 l0 ;D E D ;D E p ;D E (  ;D E (p P ;D E   ;D E  p 0d ;D E 4$  ;D E @$ l0 u4  u4 | u4   u4 | P u4   u4 | 0d L $ L x L L  L L P oL   L h 0d 2X0  2X0 T@ 2X0   2X0 C@ P 2X0 <  2X0 @ 0d 2X0 (   2X0   l0 DO  0O h ;O <  pO < P O   O X 0d PO   \O  l0 qd/ D qd/  qd/ TD  qd/  P qd/ GD  qd/  0d qd/ *FUUUIU=%=%/UU>UU>UUDJJUUDު>UUު>UUUUM%M%UUUUVnVnUـު[UUUV&UUUUNUU*MUUMUUUUYުުUUUU$ %   & +UU!UU!UU/YUUUUJUU*VEUU44*& !UU[+ـ5n5n>*Fы>* *6UUUU6UUUU-*#*!UU_*U媫媫;UUUBUUUIU=%=%/UU>UU>UUDJJUUDު>UUު>UUUUM%M%UUUIŀUX媫X媫ՀUi_ܪU#*TUUTUUU *UVnVnUـު[UUUV&UUUUNUU*MUUMUUUUYުުUUUU$ % p   &       Tt >   Tt Tt t  t  H >t  H H mɤM,P0mp mPp XT\T\DHT\T\DHT\T\DHT\T\d# laU㪫㪫U(**UUUUUUUU *#*U媫媫UUWd|qmqm~f_, X, =! " , ! " D!4,UUUUU9*-8JUUiwUUgUU*UURUURA40UU40UUU*)ꪫ3UU3UUDUVUUUUUUUU*UUN?00*UU#224 UU6ŋ6U3r3r**U Ϊ ΪUUNUUUUyVgUUgUUQ*;SUUUU쪫UVyUU3" ml \f x, xDp*UUUUЪUUЪy*UUOSF*AUUUUUUUV0UUxUU$xUU$aUUJJdU~ݪ~ݪUU\/UU/UUUU*?,# HpmxT,,+,P~68/8<8:3/48|<@/8:z|8<>@Z  m ddYD% a0TpXXpapX XpA " A " oA "  ! " o! " (A " m44 Hmx`{UdUU7dUU7LUU33Unz*mUUUUU?L8UU8UU*mUU\$UUꪫ7UU7UUUUOЪЪUUUU##UU/UU/UUAOUUUUaUUꪫn(<L$L$((naUUUU**A/UUMUU/UUMUUZUUZЪMUUЪMUUUU7*7*UUUU<$L$) mP`3UUUUG0_jCUuEuEJUd_ Ufmm(U|Ë}&UooaUSU**UUUUUUUU**],  UUUUUUrϪ]O]OJUUU-UkkGUppHu _ _U/K,EUUPCU;;nUU"UUUU"UUUV*UUm) {) m||a*UUUU*(*UUUUU *#UU#U媫媫UUUWd*qUUqUU~*al, We**UUUUUUUU**MKL UMMUU *$$U *HH UA+7UUCتCتKUUSgD2UU33?U U׋=UU=V*>UUUU>UUUU?UVV@UUUU@UUUUB*VUUUU*ӋVUUJJUV;-UU;-UUUU4sUUUqUU9**UU.UUUU.UU*:* Ef  ``&g  j [@` u8 4k PUUUUUV4UUBUUUUBUUJRdR*J*UUAUUA)UV4UU&UU4UU&UU;*BĪUU-UU>Ǫ*UUTUUUUTUUy**dUU>p*UUZUUUUUުުDUUZ8UU8UU&UUUVUU檫 UUwU=c=cUULUUvQUUdUU =b=bdEUUllEUUbbUUUUUU~UVsUUsUU`LUUcc3wUUU UUPUBB"ԀUU}UUEUUE*-UU9UU--GUߋ*UUUU*6M  Hx*2UU:UUUU:UU<*HHHC#UU7UU#UU7UU66UU9UU666w6w-ߪ$UU$UUUUP4ͪUUUUbUUbs1UUHUUs+[+[UUFȪ1Ȫ1UUUUiHnHtHy7UUcӪUUUUԪUUeUUeUVtUU'x/0)e1UU*(*UUUUUUT㪫T㪫UUUU٪`٪`ԀUU_{(UHU!=!=+/5!5!8UUNjJ=UUUUڪڪNUU=XW9UUCUU9UUCUUP!*,UU.UU쪫.UU쪫'UVUU UU] UU]*L;}UU*gUUQUUQS<''*U]**UUUUUUUUـܪ쪫C8dd\d|d\d004d00d\d|dE5||l|5|\ȟja I|wʪU.:Ux*UUg@UA)UU)UU4UV?UU?UUA*B$UU*UUUUUU*r(UU2۪yggTUUUUªHfUUUUQz,3UUeUUUU磌G/*UUUU*ihK{%UUnUUn ҀMKUUʪ֪UU֪UUUUꪫ37L*a.UUa.UUj:*sd;UU[UUԪUUUUUVUUUU*ll檫UUUU*1UBf7UUd7UUd&UVYUUNUUN *UUUUUUVꪫUUꪫUU٪UVȪUUȪUU<*UU{Z [ < ; $HBRLGLJLJLGLRLBp~ƋpF I ‹I F pƋ~:yG.H.Gt@6h5DwEwD$ߋUUԪԪUU/EUU]UUEUUEUU/xUUeԪeԪNUU7vm |n  f ,>*XUUXUUvUU,*P**JUUUUJUUUUE**QdCU4UU$$UUU!2PnnUUGU]_C\UUުiiުUUGC\_GUUUUUUVUUU!!1U'dQY**UUUUUUUU**3yUU,*%XUU%XUUD*c>\q !UU!UU ؋AЋpЋd.[M}dp&vpp&`#i&`#a#h&g#'Xo)pUUpUUVsUUUU*XXeU?? qUUVUUqUUVUUT+*\CCUUC|誫ߪ{UU֪pUU͛͛ UU|UU᪫᪫*ܪ*UUUUU*U &UULUULUUdUUݪb۝=UU=UU.UV.UUUU8W@UUK٪K٪^*Up7  UUUU0U/UUUUwVeUUmUU*|VUUUUUUUUUVUUHUUHހUUUJ,J,*UU9UU |8 }Ha*UUUUUUUU֪*SaU*窫UU窫UUU#* UU!%UUUyUUUUtHd&*J$UU$UU"C  eՀ\\΀W]*pUUpUUـ*Yi**UUUUUUUU**Wly,*+UUUU+UUUU4*ɋeUII$UMÃAUU{J{JoaUUc ?SUUD*U eVUUVUU OVIUUNUI㪫D㪫DUU7J*J*UUU#6vIaIaUUȪફUUફUUUU; UUUUUUG.^&LDOHu*KUUSUUUUSUU*MӋU9*%(UU%(UU/U*ŋË.U%UUת%UUתUAΪ*ˀUUUUUUUU*VkUUkUUUUUVȪUUa[U"E"ErU"UN@ppxLUU#*FUUFUUIUU[*ppbUUUUap4H`U=UU㪫=UU㪫U YUUΪѪdOUUOUUUUL4pUU{UUUUUUw쪫 p~fʪUUUU.*iiUUP7UU9UU!UUBBjBj\vUUUUvUUUUVɀUU쪫p|oUUoUUW UUpxkUUUU_*ҪCC>*UU)UUUUR+D*DUUpaUURUUUUUUrrAUURCUURCUU]UU*UVUUpbU|*kUUUU;,\9]WW]9\,⋋\,,~`7`KTdpDpcUUڪUVՀUUUUUUUU*]0 99,bTR*vUUvUU;*D%hE^E^22MMl=ً .*UU*UUUU*UU UV+*-UUpt]]N?_UUժ誫UU誫UUUUתmcUppm%UUUU')))U0*vUUUUPUUPp*(UUNiUU1Z1ZJKc<c@fp%*.UU.UU,*8U!*yBUUyBUU^UUeUVUUUU!ҪPUv*ͪͪUUժUUժ-UU@X@Xp@X@X8 pp^LL&U==dU! UUUUફH&$`6GUnU22UӀpnUUUUUUUUȀ/ફAફA{VUUUۨP\HPH=UU@* UUUUV(UU(UU/U4*$$UULUUX1UU*4UUUUV5*UU2UUUU2UUUVUUUUU"p.l窫UUUU?kުUUUUyUVpUUpUUUʪpH@H ^(]**UUUUUUUU*#* U!U(**UUUUUUUU **a LoSSUmURR*tUUxw UUUUUVUUUUUU*_UHUU)*9RUUxRUUxiJ_`UUojUUUUZUV-UUx,}UUb7UUb7UUIUUIeUUnnseUU[[IUU7UUUU7UU,`xpTp88XpXTph$jUUEUUEUU"UUUUUUUUUU$<(̪:U,,Utf*EUU$UU$**piUUUUܪܪUUUUip(QUU*UU9VhuUUUUL*aUUuċp@phUU-<-<SUUUUUU窫 UqUU+\+\:*HUUHp#"#UU"UU!UU$- - - t- - - - Mx4Ѫ UUUUzUV5UUQUUQUU-UV UU UUx;UUU[⪫[⪫TUUUUU ftUU??Gɀjj*UUUUUUP[UUU *UFU65FUU5FUUUZVoUUxUUU*ͪ^ͪ^Uߪ3UU3UUUUUUU6wUU*UUUUUUGGU UU᪫᪫U>UP`eUUJ*ϺϺUUUU*UUUUު[[ G3T- - - tDX44XHtHX48TDH s|%  "UEESU`_bneneҀxMKЪUUU/UU/6U*7UUUU.*ɋɋ.*UUUUڪUUڪ*UU[`UUSUUFFUU#UU ;F*QUUQUUUUYફફUUUUUUUUUUY:UU:UU*QUUp H |% TU;-UU;-UUUBU=Uɪ)ɪ)U"*W b UUmm| *UUUU% *Ë'UU誫誫 UUoX*B*UU6UUUU6UU**/UUUUҪҪUUUU;9UUUUIߪumUUȪqs|s|kc[Ԁ*dUUdUU!*XAU*?UU?UUbU*L g \  0I lUU!UU!UU/YUUUUJUU*VVU=UU%=UU% >UU >UUUUDJJ磌DUU>UUUU>UU\ª%ª%AUUUUU*MUUMUUUUYުުUUUU% _UUnتnتҀUUMMU᪫᪫U7*8UU1UUUU1UU.*ɋɋ-UUڪڪUU[`iSUUGFGF-#UU UU*QUUQUUӪͪUULUUL?#UUUU,UUYUUUUE*QUU "UEESUHXUU7""5UUӋы4U#Z#ZUȀMMȀUUZUUZʪCCʪUUUU7% $ U#*,D`i䪫UUUUUUUU߀ت]*UUUUU*6U`U*3UU3UUDU* "UEESU`_UUnتnتҀUUMM*UUUU*7*8UU1UUUU1UU.*ɋɋ.*UUUUڪUUڪ*UU[`UUSUUFFUU#UU ;UU*ƪUUƪUU_ͪUULUUL?#UUUU,UUYUUUUE*QUUo \ TM m@\UU"UU֪D֪DUU`UUEUU^MUUfMUUf:lrrkUUgiCC/&UUUUUUUU8{UU}Ԫ]]UU̪_UU XUUxP.i]UUUU]UUUUǪTª-UUaUUZjj̀EOUU&تUUتUUUU*7/(w,$@&s*+*TUUVUUTUUVUU**r*""ժr*UUVUUUUVUU+*(&*UUUUU*U""U U(\9ÀUd1d1AUUU@&1UU&1UU;UUۋۋ;UUUU&Ϊ&ΪUUUU;9U*٪UU٪UUU*=H`xoUU~*u4UUu4UUgHVY]UUY]UUmUV2UU}UU2UU}UU*&UUUVͪ|UUͪ|UUEUU]UU]UVHUUUUUx*qUUWUUW**1UU1UUUUUU檫y*n&UUxUU22FUUgU*u˪u˪}UU@UUVUU+UUUU+UU52UUssU㪫UUUU Un檫XyUUUU*MUUR *7*UU,UUUU,UU***UUUӪUUӪ *UIUU*UUCUUCUUԪʪUU5UUV>UUGUUG*M*R *7*UU,UUUU,UU**UӪӪUIUUUUV쪫UU쪫UUUUaʪa5UUUU>쪫G쪫GUUM*R͕7*,UU,UU**UUUӪUUӪ *UIUU*VUUUUUUUUVUUʪ% ` T L %  d H p % ` T H p % ` T H p GH%  t H p -%  uP P lP cP lP lP 'P _ ;_ _ DP _ hP HP _ DP lP lP P a a a pP a pP lP -S hI J pK T m\u v %lu v u v lu v u v u v lu v   m  <u v  du v u v  du v u v u v Wlu v h m  W d u v  d u v u v Xy a\kl m |n [lz P&*LUULUUiJUUJUU%*UXXfUAAUUzLUUzLUUd&*N(NdUzzUUArUUArUUfW*XX$UW*IrUUIrUUUUUU*UPb*UUbUU~bUU~1*SUUlt*DUUUUb*Kfcsl^UUUUʪCp|5*jUUyUUjUUyUU*u>ـUU ٢٢DUU *:)UU\LD8PX[38UU*&*LUULUUiUUII$UUXXfUAAUUzLUUzLUUd&*N<TUUnn7UU/XZT*rUUrUU%9*HP\UU*rUUrUU9UUE*~ƀGYn PWUh=E=EU0;(UU<$f=UU=UU=UU=UURiUUફSUVUU=UUUU=UU%UVrUUP:)UUUrr*UU_]*UUUUUUUUaVªUU'|P&*LUULUUiJUUJUU%*UXXfUAAUUzLUUzLUUd&*NNdUzzUUArUUArUUfW*XX%*W*JUUrUUJUUrUUiUP@R*nUUnUU7*/XrqUY_Y_UUI33#UUUT\UU*rUUrUU9UUE*~ƀGYnTPH%UTcUZ?X7*/nUUnUU*RR@nYCƀ~9UUssU?UZLTVh=UUE=UUE0;(@*jUU?=?=USU*SUUU=UU=*U"@(0U==WUP$*UUUUUUUUફ*!_!**>UUUU>UUUUj***PGUU䪫䪫UU U',U ɋr9UUW#_[UUe䪫o䪫oUU}G  td,(0(  xt{,(0({ (0({蛋,(0({,(p00|l  l$00l l$00|llp00||l l|UU⪫UUUUUU;YYrUUkv*UU4UU4UUK4KKwk wU'UUcNcNLUUUUUUUVUUUU*ZUUUU_UU_UUq/@5UKKQUWupˡ?*>UU>UUB*ыRMUUU̪̪=UUUU1-1- UUW*-*UU UUUU UU**=pYUUH*ziid__*ڪUUUU/**UUUUUUUUUVUUTUUTVUUUUVUUUUUVUUUU*SUZ UJUUDD7UU:UU:UUK*SGuvDUUMӀVUUߪVUUߪ[U`eS8UHUUHHUUH8T*__UfmUUUU_UUUVZUUUUUVUUxywwG+UUUUUU-$+b+bUUUUUUUVUUUU,UU,w*UUUUUUUU+U*GvvDêƪƪUU]#UU#UUUÙǪǪ UUĀUUUU%VUUUUUU8UU8UUU*PpUU.*3x8UUx8UUU;*UUd*[U޼BBUU11]UUfUU<:*UU7쪫7쪫2UUUi \,;DPp\hhdd$ \pdLUU.ZZIΪ8Ϊ8UUUUU.1UU1UUBUVSUUΪSUUΪUUfݸݸ1*UUUUU*Uu*UUUU*sUE*΀RUU^RUU^`*eUU\*1UU1UUBUVSUUSUUfeUU\*ZZI88UUUUҪҪUUUSSoqtUUyꪫyꪫzUU{uyU몫UU몫UUUsSF29-UU9-UU,OɀnZnZUUdϪnϪnʀUUG€TT̀]m媫UUUU몫yUUU Uééɚϋ:50UUn0UUn&UUϪϪʀUU:50UU0UU&6Nj͋UU>UV*UUUUJ;?UU,UU?UU,UUOUV_UU_UU Urdr*_UUUU_UUUUOUVk?UUӪ?UUӪĀUUUUzUVcUUUUUUV8wUU$UUwUU$UU-*Vt  WddWdUUV UUUUU3UU*#Uڪ"p2CH^UUUU^UUUUnUVUUUU*쪫UU|$ڪUU**ff3ફUUUUUUUUV*UU@UU*UU&&n2^UU>^UU>HCUU2  WXdXdWXd W* UUUUUUv!((U)UU)UU* UU( UU(!(U UU(**UUUUUUUU**a*?UU3UUUU3UU*3**UUUUUUUU**)k)U*5UU5UUSU*ًDUUU:1:1UU9UeUUހުnުnUU_4J*=UU8UU=UU8UUI*UUI*ª8UUª8UUUU*3x\ X ,U*#UU#UUU*QQ**UUUUUUUU**U}$ }$ `  }$ 8l  H| }$  }$ P }$ g }$ D }$ l }$ HL }$ Tp }$ @ }$  }$ |v }$  }$ T\  }$  }$ d }$ | v Z$ ; F$ 8 $   #$  P $   ?$ ( 0d o$ K  {$ ? l0 }$ @ }$ Tp P$ h $ xP }$ }$ `  lu H  [u lu  v lw t  4 l 4p8Ap8DpދªªUU UUUU[`UUUUU*UoWݪUUUUUU4UUJpl  l @ p[UUʪUUUUUUUU%?%UUT%UUT1ժUUUUC*IUU,|ªªUU UUUU[`UUUUU*UoWݪUUUUUU4UUJ< l v l |D l  %l ( l k v l k v   l H( l  El E*UUUU*\UU*UUUUUU*}KʪUUUUUUUUꪫ6p$IUUIUU\UUVUUUUU*UUUUUqqUUUU/UUUUЪЪUUUUo?{|x\&UUMUUMUU^UUEl  El $IUUIUU\UUVUUUUU*UUUUUqqUUUU/UUUUЪЪUUUUo?{|xEl D El  El  El \  El { El v El  v El  v d  El ( x˿KUUbHUUU;Ъ;ЪUUU'UUVwUUwUU誫*UUoUUo誫Uwݪwݪ*ƪUUN UU UU(UUUVUUUU*hh(UUڀ c cUUUUʪUUUUȪcȪcw UUUUU*mUU+do9UUUK誫K誫TUUU2*$bUUIUUbUUIUU1*RUEUUߪߪc0*UU(UUUU(UUUV$VUU!UUUU!UUc"Vߪ$UUߪ$UUU*V1UU( #UUUUUVUU UU UVUU-UUUU-UU 9ыpUU4U8!8!U5UUVtUU]UU]UU\\UULo. p  M, H 88  8 {  8  Ro. p/ R0 RT1 Ro2 R83 X4 Ro5 Ro6 p7 R8 Ro9 p: `; R < p= Ro. p  R R R8 RX { RX  a' ad( ad aD e a0 a  XB ;C Mp, pppp p U H D U- - U+ U{- U{- t- U+ L- D>  D@ p tt p  ? ?  * p p p X    d  d T) 'PheU媫媫U *UUUU *ssg[[g}sosoicm>'a9TUU)UUUV!*UUUU' Ë-UU쪫"UU"UUUU+ P(UUUUUUVUUjUUj*Ukƪ_ [@` 9o a @\ 4p xd e [ @\ i ),@ Uq  { L , pP 1{  |  9{ 5{ L e` { H p 5 L   L , p 1    9 5 L e`  H p 5 L m m m3  m$mUU-UU!-UU!5UVUU=UU=UU6Ҫ檫4UU"݀)ݪ0ݪ0UUtMUUtMUUcUVyUU UUUU٨@"3UU"3UU)!UUC<[檫UUUUªIªIʪҪUUҪUUUUUVmUUm$骫|UUUUhUUhUVTUU@UU@ɪ,UU[?<"UU"ת"UUY"UUYUUUU UVUUstUUtUUUUUVݪUUݪUU݀VUUUU4UU6UU.*<<4W,lUU,lUUUUVUU  L , p 1    9 5 L e`  H p 5P L m m m  m$\eUUz-UU-UU5UVUU=UU=UU6[UU4UU݀,*ݪ4UUݪ4UUUUtStSjUU UUUUߨE"7UU"7UU,UV!UU!UUC<[UUUU[ªªʪUUҪҪUUzeUUmt骫UUUUUUUVZUUEUUUUEUUɪ[?<"ު"Ӫ"UUȪ"UUȪUU77 % s~tUUtUUUUGݪ˪ݪ˪݀UUUg46UU<EUU<EUU4Z,,UUUU D L ,T p| 1D    9D 5D L e`T D H p 5 L 2  m m  $ G $  p G p   g 7mT Ts  $ D~    < D 0d (|o8苣\\]lu   k m ރ mg(UU٪$UU$UUUUӪ[eUt骫t骫׀UQgߪUUUU骫{[D}$-UU~-UU~8ϋM?UU1UU?UU1UUHB*^DUU^DUU;*%' P \   lx  `   0 g {x    m,U $ {  4 $ X H   ,  m8lZ ;D E 0 VL  +V W $ )0gh hi { *ko s0p , Rk| h}  'lE*zUUzUU*]UUUUhUUܲٹ٢p@ y @ w mX m  m P m  m` 0d m   m  l0 m, m0D 'pK ]m m8 ' ' m     p    H   0 r m    \ m h m  { p| } m( T m (   H|    @ }  9     d  m  D m  mX<  |z mt- UT m kT % " 8 6R s c)C6s!"l"""%5%N%W&'&P&^&&'()K)*+*f-K-^...//</]/n///12346,7579D9:Wf>>?@AgB|CwDEBFGHIwIIIJJL8LAMOjPQSS.S6TQTTTUrWQW]X+XYKYaYZZ[S\]g]^__`&`2`:`habbc c\fTffgEhThi^jhk'kllm,mmo1o=oIop,pq-qrt"u|vwxy{X}T}]}e}~~*CO#JVp/XB} \AX^'epRmu{.6WH+u/Qd=c uky9Y!Qdlpƀ@ǜ?ʁ4͝4bлpZӑ֝7؀ڱ>OL޲޾߂ 8nzd{f}@ O >{ |p3/;>vcg9  v  0 S v  4 t   JH /n|Uu+xsE  @| ---ffҀUUҀffffҀUUҀff- pTp UUUUY;UUUU;UUUU*VUUUH*U%%,UU>UUUU>UU*DJJ|Dm>UUm>UUӪUU%UU%UVUUU*UUUUUUUUҪYllUU xx *7UUUU2*ыӋZZȀMMȀnZnZ΀ECUU⪫⪫UU7 U*A,UUA,UU]U=*FdKQW W ֪UUUUUUUU5UU5UUUU)UU dF*=*UU,UUUU,UU** *UUUӪUUӪ*UFd3 )UUhUU\\UU5aUUaUU5UU\UU\֪hUUW W QK3dF]UUAӪAӪ UU T{L<``+<LTL+<``< tttt 1*UUU}E}EǪUUUUH-UUUULLUQVV*QUU UU p **)UUh"h"ՀUUY[a*gUUgUUy*YYՀhh* v*[|UUUU|UUUU>*NUUUUQUU\UUttQUUGUUGUUUUUU>*|UUH|UUH*UUv kUU*8$8$րUU )USUUSUUsHU((s*SUUnUUSUUnUU)2*UU֪2UU$UU$Cj d*몫ફ몫ફUUhdUUdUUπUVQUUQUUUUdNU=UU=UU1V&UU&UU**h  pL`uUU[VjUBUUWBUUW.OGG IKUUiUU媫_UUUUUVDUUUU2UU2UU  UU檫UUUUUVȪUUȪUUUU t]U^UU#voUU]*UU%UUUU*rRrRUUU̪̪UU8x vUUUU22R*EUU>UUB UFqUUZUVCUULCUUL!U٪UUUUUUUU(ߪ8p8UUwUUSS)UUUՀ*6H6HUހ|UUVUU9UUUU9UUҀbUU VUUUWUUUUMUVUUUU5Ȁ*3*UUUUUUUUUVUUUتUUت*UU7UU**@E>\*UUrqUU[ફEUUEUU#*#UU!gUUUU uUUUU᪫MMǪUUǪUUUUVUU*U:UU:UUaŪaŪkUuUU`UU00GBUU*WUUUU*F2*UUUU*Mu6|U#U@zbUUĪĪBUUUUv*aaUUbUHHUU7UU7UU77 UUUL*UUԪԪ4R&UUUU&UUUU*?U?תUUתUUɪRԪԪUUU77*UUUUUV7UUUUEUUE*MUUUVNFF8UUεεKߋ $hlhl$ l 4$hl{hl UUUUGUV9UU9UU1*##/U&U$5UUD1UUN1UUN^n*\UUQUUUUQUU4.4.UUUUꪫUUUUDUVUUUUUUUU*UUN?00*UU#224 UU6P**BUUUUBUUUU!**)wcƪcƪΪѪUUܪIcǀԀ\\\\UU檫檫UUUU #UU*LUULUU!#)E)EZ U(UUl$l$UU,ϋG UU UUFUVBUUUUBUUUU5VUV)UUUUSLsتUUUUUUUUL}}U\UUMMUUUު'SOUUUUOUUUUrUVzUVUUUUUUUUUUUV*UUUUUV檫uUUUUuUUUUa*G9+UUUUx;j!j!V%OUUUUફ*ફ*UU1UUh}Հ%DD ''UU*VUUUUUUUULAѪѪUU UUUV'UU'UUNUUNNUUNa*߀X*.UU.UU+4*ƪUU+UUi+UUi1窫UUUU3UU.T*UU>UU2>UU2.*BUީ[* UUcUU UUcUU`UU*UVUUUUUUUUt,g,g RUU!![UD [UUm>O>OUU<:UŪ:Ū:U׀UUF*̪.UU̪.UUfL>LUU1UUUUUUUUߪ 檫 UUwU=c=cUULUUvQUUdUU =b=bdEUUllEUUbbUUUUUU~UVsUUsUU`LUUcc3wUUU wUU٪UVUUUUUUUUZ;ު;ުUUUUUUU>>UU\UUPPUUUUO\UUdUU>dUU>p*UUZUUUUUުުDUUZ8UU8UU&UUUVUU d)*UURUUUURUUn UUhUU*UUUU rQrQ>(U9UU( H yUUqUUyUUqUU<<*:UUUoUUoC*U pL0p0Hpp, ( p Hhp4 l&*EUULUUbLUUbj*UUUU*"*}DUU}DUUb`G|G|#UppUUXXUUͪUUͪ*UUUUU>>\UUĪĪUUUUUUUU::\UUcUU>cUU>nUVUUyUU({d==G*+UUyUU檫UUt*=dUU=dUUUUNUV8UU8UU}UU*$QUUe*UUUUbbUEUUl (($$  UUŪHŪH*ހUU44UUEVUU??DŀWUUbWUUbcFU4Uiiii4U*Ī ,\L0؋,s pL$ |t|L\sL wLH ld=UU=UU yUVUUUUUUd*QUUQUUdUU =b=bdEUUllEUUbbUUUUUU*UUUUUUyUVUUUUl NjUUPPUUUUUUUફUUU>>UU\UUPPUUUUO\UUdUU>dUU>p*UUp*ફdUUUUdUUUUOjUUN TUU*:UU:UUKN1UUb1UUbUVmUUxx窪mUUΪΪ?MUUU9UU9|UVU_UUH Hv׀FUU:FUU:"VUUUUUUUUVEUUEUU֪[UU Dd` zUUl*̪]̪]IUUvt pgCC'UDDU܀. gUU*\UVêQUUêQUU@*UUUU}UUV*UUUUUUUU7UVUUUUUVUU.UU]UU]UU~UU4]Cw誫UUUUUVUUUUȪGUUuV_UU_UUUUԪll6UU* *UUUUQ%UVUU2UUUU2UUV?VUUMUU<FUU#UU#WpUUVUUnUU4nUU49YUU9YUUUnUUتت*6*ê*UUê*UUL!UUUUĀUUʪdUU UUV*UUUUʪ^^;*IUU)UUQ*R@UUR@UUmUU *hoUUUSUUUS0UU $p UU檫VUUUUUUUUHS٪٪U*&UU&UUHUUIUUI檫Z*jLf$UUHUUHUU`8xƪxƪUUUU$$UUx9UUx9UU`HlHl$UUUUL Ds X`X@|wHH|w@ ((؛{ xLxPsP ppH|p XxX XX p , YUUK UU1 UU%UU%UU#!UUdwaUUcHcHU6UUêêU *UΪUUvUUvUUH˪H˪ʀ*ct;ø41UU1UUXUUpmUUQUV*5UUUU5UUUUe*pUUpUUUUŪŪ*UU*UUUUUQફQફѪUUUTUUT3'UUU..UUOUUfOUUfXUVaUU -UN)N)=U݋*/ UU UU*UU9* U=UUUUUU!***UU++&UU! ! U UlKNjQjQUUЪUUUU* xUUoUժժ-UU;UUj;UUjb L7UU 7UU UUUUU ȪvȪv>[UU@UU@ UU~'UŪjŪjUUڪlUUlUUUVUUw @%UUJJmUU$S*BUUXBUUX1F44*UUU2UU2YUU˪˪UUU"*6UUUU*B qUUc*U*UUU*UU9UV4UUHUU4UUHUU VQ UUZu7*UUUUЪUUЪ*UU/ UU5UUFB**TUUTUUo*4RUU=ɪ=ɪ'UUEݪȪȪDUUUUuUUV*UUUUUUUUUVUVUUUUUUUU*v*0fwUUGGi$ xooUUUkժkժUUUUĪjĪj<UU ))UUȪ Ȫ UUUUU 7UUv7UUv[b@b@ UUUUO*:UUj:UUj,ڪUUUU*VUUUU @ڪUUUU$'UXXUUFj4j4UU!2!2UUBUU˪BUU˪RUb"lU6KK%UB 0#GUUGUU]xm;ϪOUUcUUcUVwUUuUUV+UUWUUUUWUUр}8UUxxeeXNUK;K;D+*==< UU;uUUV*UUUUUUUUUV-UUUU*u*bbUv*99RUU/UUUU*( UUOUUJ*бп7͵͵٠*UUUU* 0*9"UU'UU"UU'UU8*ً`y䪫UU UUUU UUЪQUUUUI⪫I⪫UU_֪UU֪UUUU3h\8 vnUURUU>>UUk xs"*UU)UU)Ҫ%UUPP'~UUUUU>UUȪ Ȫ UUvUb4MbUUUUbUUUUo*⪫OU-UU$UU"UUUU쪫cUUcUUʪ֪UUpUUpUVUU3U ǪUUǪUU^UUUU*wUUP9JUU*dUUdUUUU*8 (UU2eUUeUUUU4^UUUUUU1BUU2BUU2S*UU$mUUˀJ"J"%UUx:>*zUUUUU*U" &*gLUULUU`$toUUW*ހHHTdUU[&+^+^UU/UUbUU\\UUgw  'UUUU%%jUUjUU؀]]؀UUjફjફUU__UUફફ'UU =UUUުժުժUUUC_p(nUU)UHSHS$UUpU"p 9-lM$4@o  TKUU*B UU9 UU9*-* 9*UU=UUYUU*8UU8UUUU|cUUR*;UU;UUS ߋ窫UUUU:UU*UU*UvUU\UU\PUت{e*?UUU-UU-U'UU*UUUUUU*k9wx %*gJUUJUU]$pmUU*>UU>UUUUVUUTdX*UU_UUUU_UU/dUU*UUUUUU*g=wx yhUUhUUQUV\UU:UUv:UUv*4UŪwUUŪwUU\uUUuUUjIIUUŪŪUtUU4aUU*uUV:UUUU:UUUUQUVhUUIhUUIyj UUGGͪUUUU磌_UUffUU_ X X2UUG66VUUiUUiUUV䪫UUUU2UUUV UU UUUUUVUUUU磌UU3UU3ͪDGUGUUUp  L7UU 7UU UUUUU ȪvȪv>[UU@UU@ UU~'UŪjŪjUUڪlUUlUUUVUUoxUUoUժժ-UU;UUj;UUjb "*6UUUU*B@%UUJJmUU$S*BUUXBUUX1F44*UUU2UU2YUU˪˪UUU `gUU$UVIUUDUUIUUDUU\"*UUVUUUU*UU\sUUo kkڪUUUU8UUЪЪͪUU䪫?{|x UUmUU*4B4B!UU,x6e6eH˪ZUUZUUUVUU! UUAAg**ުO,ܪCUUI߀ǀJ\J\UU>UUN*gUU誫0UU0UU"Yʪ |\|TUA)UU)UU4UV?UU?UUA*B$UU*UUUUUU*[hUUUUꪫqUU*ll檫UUUU*1UB +*%%,U#UU UU'UUm0UUIUU@рUUUU*ϋϋ@UU=UU=UU1%CUU%CUUUnlUU{ÀުUUުUUҪ*ƪܪ߀UUUUUUUU؀Ϊ*UU?UU.ǪǪUUGGNUUNUU΀Zeey2UU &**UU UUUU UU*%*U&*᪫UU᪫UUU*]aUUߪ᪫ߪ᪫UU]a*U UUߪ UUߪ%*U &UUxUU22FUUgU*u˪u˪}UUUU~*u4UUu4UUgHVY]UUY]UUmUV2UU}UU2UU}UU*&UUUVͪ|UUͪ|UUEUU]UU]UVHUUUUUx*qUUWUUW**1UU1UUUUUU檫y*n yUUpgg:*ZJUUMJUUM<a+UUa+UUh*nhUUUaԪaԪUUÀUUUU9UUV)UU)UUU*UUUUU*UA=A=*UUUUԪUUԪ*UUUUVUU+UUUU+UU5UU>*UU{UU$܀DDfUU)t/UU*)*!!1AUU5UU**UU檫檫*UUUU[⪫*UU媫g媫g*UUª 0ԋ U77JUt]]JUU7 MUU UUǦ****UU6UU͋ـHUU>4>4UU *DUUDUU܀*-xUUUUUU) UU*U~qqUh__zhiqiq\~O<͋3U*%UU%UUU*]]zUi媫i媫\UOQ* UUUUU*!Ux'%UU<JQJQeUUn UҀffffҀUUҀff----ffҀU T  {T #UU~~*UUUUUUUU*~~H*:UU=UU:UU=UU*RHaU*媫UU媫UUU*e[* UUUUUUUUgUV۪UU۪UUԀ UU5U߀ŪJŪJU% K*UU UU̪ UU̪+UUmm=|ϋU AUUAUU"P*:UU5UUUU5UU`*ɪɪUGG€{TkTkUUUUߪʪߪʪUUUK -*(UU(UU*ËËUתתUY[yԪgUUgUU]쪫SS]UUg&g&y+UU s䋋pL0p0Hpp  x( L ,,||||,,||||,,||||,,|| c$UUͪUUUU*UUUUUUyUVUUUUl:UU)ЪЪo[UU'n,\pbZUU1UUPQUUdUU =b=bdEUUl[UUUUkV.|UU.|UU@R,u NjUUPPUUUUUUUફtUU&UU!LUU(UVUUUUUUUUUVUU UUD*R*n_n_lUUUO\UUdUU>dUU>p*UUyUUڪhUU T UUddLUUҿbqbWWUUUUn < t "lUjKIKI%U0*UUUUUU*UU" 0#GUUGUU]xm;[OIcIc;w-%UUUUWmUꪫUUxxeeXNUK;K;D+*==< UU;UUUV*UUUUUUUUUVVUUUUmHS=9292UUUUzUUvUUvUUH˪H˪ʀ*ct;-UU4@1UU@1UUYUUUUjUUkUUUݪݪ\UUUUUU*UU?CUUUU磌UU磌K*ƪફƪફ]UUUTUUTU'UUU..UUOUUfOUUfXUVaUU ϦϦ9UU!.(UU.(UU#U(((!UUJ*VbUUUUbUUUUyV*UU UU.UVSUUZSUUZ-UU~*UUUU* t )UU""UU*)UUUU"UU"֪UU[YՀhUUhUU֪[YՀhh* (iUUK kyhUUhUUQUV\UU:UUv:UUv*xzmibUUibUUUUT*ª7,'(UU xuUUjIIUUŪŪUtUU4 *UUUU/=UU7 UU<WUUUUUUGUUUU磌UU3UU3ͪDGUGUUUp ફ3UU說@@UUCFfUU_ X X2UUG66VUUiUUZ骫CUUUU UUoUժժ-UU;UUj;UUjb L7UU 7UU UUUUU ȪvȪv>[UU@UU@ UU~'UŪjŪjUUڪlUUlUUUVUUw *UUU2UU2YUU˪˪UUU"*6UUUU*B@%UUJJmUU$S*BUUXBUUX1F44 WUUU᪫ت᪫تUUUEE΀n'UUn'UU-p%*JUUUUJUUUUd*ݪeUU"UUJDJD%UUXUU )U##U {D}UU誫誫UUmc䪫UUUU6/UU1UU/UU1UU5.ǷOUUȪBȪBUUA5U5^5^CU݋ T{  T ?U6窫-窫-U4i;\c'*ZUUUgUUg*_U 8ő5*0UU0UU4*0⪫檫UUUU]UU]UVXUUSUUS*SS 0*c %*gJUUJUU]$pmUU*>UU>UUUUVUU;ꪫUUUU⪫UU⪫ʪUUK}UUUUUU\*UUUU*^#MUUGUUMUUGUU&etX*UU_UUUU_UU/dUU*UUUUUU*g=wx d< P< pUU8*tpUUtpUU:UU*FFŪUUUiUUiW4Uppp,  k܀KUUDKUUD%)UU*VUUUUUUUUΪUVJUUJUUU*  k܀KUUDKUUD%)UU*VUUUUUUUUΪUVJUUJUUU* pUUUUN>UUUU㪫UU5UU5UUUUiZiZzeUU %UUgJJ]UU$V*i>UUG>UUG*- XUU_UU_UUUU/0UU*UUUUUU*g=wx UU**N$N$UU*UUUU*媫UUU+7+7QUUUUUUVUU2VUUP*UVIUUIUUфBUUBUUA*쪫CUUwD*||HUULUULUUS*Z$ UUUU{AAUUUUUUUUUUVUUUUUUUUUUU1gUUUU&*}MMUU44gUUqUUtj*UU_Ȫ_ȪN*ZZsUU  IUUUUUUUUUVUVUUUUUUD*6%UU6%UUUuUUjIIUUŪŪUtUU4aUU*uUV:UUUU:UUUUQUVhUUIhUUIyjyhUUhUUQUV\UU:UUv:UUv*lUUMWUU!UU8$$UUaUUr q|h d {UUkIkIOUU3쪫UU檫VUUUUUUUUHS٪٪U*&UU&UUHUUIUUI檫Z*jLf$UUHUUHUU`8xƪxƪUUUU$$UUx9UUx9UU`HlHl$UUUU uU<*dUUdUU# MUUUU֪֪*檫骫檫骫aUUUu +*1UU>1UUQUU׋@UU5ת5ת,**"ª"ªLUUUUUUUVUUUU UUUU*??GUUXUUЪXUUЪ`VUUiUU,}UU"bbIUUzsz[[UUUU",UϪϪUUJYUUYUUUU*'JU??UfU9UU˪9UU˪(UVUUUU U7S7UUUUUcƪ˪ƪ˪UUU#3?*K6UUK6UUgB* LUUUT&T&3UUˋGUU9V?UU,UU?UU,UUTUV*iUUgUUꪫ>UU>UU'S쪫UUUUlVNUUNUUrUU'*PQUUrUV*UUUUUUUUɪ*"֪ՀUUUUת'UUf'UUf9UꪫUUlUUتUUتmՀ磌UU磌UUU*aUUUUV5UU5UUI*]f]fj*vJ(+*VUUVUUAUUsw3תUUUUUUUUꪫ.EEhŋӋH=?UU2UU?UU2UUU*L ([تUUUUUUUU"*$UUUU#*Ë)UU磌UUUU UU٪_ UUUUY9۪۪*OWUU *UU۪C۪CYƀUU`UU` N$UU$UU1V8?UU?UUMMv?UUa?UUa1Vǀ$UUD$UUD 5 0zUU*UXUXU,UU{U6CUU..=UUً*UUUU*pm*UUUUU*Uo0*UUUUUUUUӪ*643u_UU_UUM誫;sU窫窫Uyp*UUUUUUUU* oUU]*UUUU=*'*.UU.UU!25UU5UU UU1>{7k0k0UU&UUʪʪUUKUUB骫UUUUUVUUUU *&UU&UU6*ŋɋ1U٪٪UE ` ^LL!)=UzͪiͪiUds_\` ]UUU媫UU媫۪UWcDP9UU9UUۋ U U*UUUUUU* UUiglqUUqUU~䪫kkoss `͢6ϋUUUUUUUU<ܪ_ UUUUMɪUUjUUjߪԀyU8X LddddLdPl\lH`pPp` uMUUaުuުuUUI[<UU*UU݋CUUU4+4+'UU8UPll  tLAlJlJۀaO#OX ًBU71UU71UUU@ۋ*C*UU4UUUU4UU**= UUUUU U۪_c*iUUoUUo*}e &|,P|0 dd HħH dDTTD dHDTDHdHDTD TT ʪÙUUUUUV(UUUU(UUUU5*BUUUUUUUU_hUUUUUUUUUV UUUU磌%UU11>*J+ u*@UUUU* *㪫UUUUUUUU*/C**2UUUU2UUUUD**uD_**UUUUUUUU**icUi٪o٪oU}MI*UUUUUU*'UU{T_=-*UBUUɪBUUɪ`*U;UU;UUKUU8UUҪ0Ҫ0UU"UU3@#W-UU͋*&UU&UU.* ͋/UUUફફUUUo ttptptp ̝J8(<< GϚH4,P P0 H,0P P,H4| EU/UUժ/UUժ9Uϋˋ8*1UU*UU1UU*UU8*ыUU7Ϊ+UUΪ+UUUUKGUUUUЪԪЪԪUUUUG  tt iirtrtmi tt d|<|d @`@ /+UUUU+UUUU1ϋ܀DD,NjUUUU媫gkUUnn΀UUEl ;*tUUtUU߀*aI0#>U$ڪpFUUU\L\LnUU̪UUUU*UU*U''3UU\UU\UUF*B*EUU UU{UUUU𪪏LoDSSC3/UUUӪUUӪ㪫UI@ *UUUU!*MUU&֪UUW!wPcPcSCE*Ȁ.UUb.UUb>*ً6- U$$UU UUꪫ*ݪݪ*Uު;cUU: MҀUUn*n*9H:-ɋɋ-``ŀA{CƪnUUnUUҀꪫM /H䪫QUUnA3_:uUUXHVUUUU.Y.Y?UUrۋ4,U *UUUU*UU  LHw 1U+UUת+UUת7UϋUUUUUU UU UUUUw磌UUUU a*UUUU*'UUUUU #UUU#U䪫䪫UUUWWd*qUUqUU~*a 84h l3%UU:9*AAĪUUUU骫uUU8sUU8sUUNUU[rUUUUriGGNUUUU88UUqUU骫UU UU Ī/A>A>:U3ڪlp@hUUqLUULUU&UUVªUU*UUuUU0*DD0Հ66UUjUUjUUUUSV=UUV&UUXUULrLrhUUUU5{@ |(*UU>UUĪ>UUĪN*UU鋻(UU* UU UUUU*t*UUUUUUUU*wa*UUUU* ,$p $ ZӀ2UU2UUUȪty- 7UUUU\UU\ӪUUd*zU>UU>UUUU|e UU |?UU?UUUW*>UU>UU**UU @l32gUU'gUU'Y8c*SUU,UUSUU,UUDUV5UUK5UUK'UVVUUUUUU dUhYUU\UV|_UU|_UUUUY窫窫Uo?9UU3UU3UV3UU3UU3V9UU?UU'ff̪DUUUUUUUU媫ʪ̀ UUUUUUV*UUUUUU*OD   p( 7**2UUUU2UUUUD**iwUUcGOGOɪUUUUUUUU몫UVUUov*UU4UU4UU4cKcKxkUUvU{UULU0lU^U٪٪U*UU׋ӋFEUUUUEUUUU6{MĀRRC8UUUUUUѪUU UU*NUUNUU*9/9/J*UZN{DUU*UUUUBB*UUo/UU䪫>䪫>ժUUƪѪƪѪUUUV*UU.UUUU.UUUV=UUUU*T s/UUª8UjjOUU\nU]UU=]UU=T2k2kUU{*0UU*̪xUU̪xUUUU^UV+DUU+DUU"*yUUUUUUȪUUȪAUUɪɪUzaUU (IUU *KUUUUٵA95UU95UUK]UUKUUʪʪ+UUUU3UU3*+#oUU}oo`ªQѪQѪUUU+RRUU<媫GUU媫GUU*C `C*3*UUwUUw誫UU N*.UnUUMnUUMO`U00UU*{kkgmc'UUUUUUUUUUUUUV*UUOUUOت UU6- U-UU-UUBUUUU誫` .UU$ $ UU UU2UU2᪫DaVaVǪUU7UU"ȪȪUUu:00:㪫UUFUUF݀7ȀUUU5UU5mGUUUaUj %UU %UU -Y7U۪ ۪ Uy*! UUUUUUUU*??8*UUUUUUU*dp(lUUتMUU'UU(ڪV*UFUU+FUU+8V;U+UU+UUUVVUUUU`UU` eUU**UU UU *eY |D<UUU  UUU!UUtk⪫ UUUU⪫k5Kta]a]cJe7\\_UͪͪUIk⪫UUUUUU⪫UUk{p U U*UUUU*i@< R!UU!UU0UU#6#6U;UUXXk?uYl*cUUcUUR*A  h, ,l  | @ r'NOUUNOUUUUUU֪ gp6U^UUvvUЪUUЪUU䪫O @v;**UUS UUs[UU[UUvUU-H UUuԪԪ.SUUUUUVUUpUU=UV@UUCUUC\PĪ]Ī]E誫UUUUOǀUVV`(j2UUj2UU6*|%UUDWUUUUWUUUUc* vU#UUaFaFJUU]UUXX]UUUUFUUF*#UU 'UUϪϪUUUT=T=UU|5UUU0#+#+,#*555 5UU !?<  psl3UU#UUUU#UU&ËxG!UUTTUҪ~ PUUUUU*8UUU UUUl쪫UUUpp*UUUUUK媫ªUUªUUUUc{< T?tUUtUUi;\c)UP*RUURUUUU* PUUUdd2UUoUZ0ުUU%UU%X$lhUUpp8UUL|U%%4@U mUU&UMMkUǀUUH"UUe*D~UUD~UUbUUUVUUUU*UUjdUU UK *9UU(KdUUުUV媪UU쪫UU쪫>UUCUUk8qUUqUUUUVUU0ȪmOO*o*UUMUUMz&Uddz*۪UUUU#êxêx^UUDGV%UUe6e6xDUU f,U*YxUUYxUU{*<*UUz*UU,a6_ *(*QUUUUQUUUUyUVVUUUUoUU!U;;QU*UUUUzUU*0#GUUGUU]xm;ϪOUUcUUcUVwUUuUUV+UUWUUUUWUUр}8UUxxeeXNUK;K;D+*==< UU;UU*UU*Ȫ*Ȫ*UUUUUUUU٪V{{UUUU*ԪԪUUEUX UU?z?zUUxЪvЪvjUUd@UUUNNbUU U UUwUVUU[UU[UU3*UU*UU@E *ZUUZUU΀*UUdUUqJUUJUU%UU<U$xUUSUUxUUSUUZ*)X ;Mf_f_ҀuUuUkkuUwtUUUUUU jUU,UUHXHX$UUrUU  Nj1UU&&UUUUUV UU\X<{򪫣yUUUUUVUUUUYª*рUU.UU.*~:8(UUP-P-fUU\ 5"^^USCUUCUU!AUU!!^H^Hހ5 C*UU.UU&.UU&*#UU \<bJU``W"NUUNUUU *ʪ/*UUbUUb*i-T_* U&UU&UU2* Uɋˋ{kk_SgUU٪٪UU=UUUUMwMwUUlڪaڪaUUŪUU)!UUBTBTQUU U*UUUU*U%*'UU'UU2U*ɓUU UUUUUV UU UU U*^UU^UU€*= e*.PUUPUU(*::תUUUU.UÙ٪v٪vmUUUU㪫UU㪫**ꪫ`\4UUUU檫oo窫'UUUU'UUUU2UV=UU 7J]]tTAUUAUUA BUU-UU-UUUU'%UUҪFҪFUU݀3 Nj1UU&&UUUUUV UU4XT\򪫣yUUUUUVUUUUYª*рUU.UU.*~:8(UUP-P-fUU\ ]UIUU5IUU5/?UT{]UU*تUUتUUUU*O7*X4UUX4UUUIV_UUdvU`UUժժȪ6UU""*UU"UUUU22**UUUUUUUUUUv*, U;KM_,_,u9 H9,,Kaۀ>llEL ׋E*>UU>UU11>UU>UUE*׋E*f>UUf>UUUU1U??UffUUUUffU??U1UU>UUf>UUfE*׋ U7##4UыӋ5UU"Z"ZUUȀMMUȀݪZݪZUECUggy7 UU7""5UUӋы4*"UUZ"UUZ*ȀMMUUȀުZުZUUCCʪUUUU7 @@ , 0\\_UU3n3n 7UU,, Ȫ3UU3UU_d\Z+dxǀ. 1(XXo"$pQUUQUUF(UUUU$$qW;W;Ec3 |X|HDH @4З Qүү71UU(UU>(UU>*GUUۋNf=UUf=UUX0UVJ#UUJ#UU7[*UUU磌UU磌*Uqq؋D?Tk"UU( UU( UU(UUۋB誫5UUUU5UUUUĪCE*ƀUU^UU^*=OU*ϪUUϪUUU**`UUUUVUUUU5UUUVBUUBUUJ*R ^UUGUU$GUU$7?UU?UUGۋMUUxE*eeXKKUUEUUUª᪫ª᪫UUUiUj7UU7UUL%͋ll׀Yd{UUUU2ŀ. . рZjj*1UU1UUªL۪L۪UQUU =**UU0UUUU0UU*9*͋81UU1UUAً׋?UU3UUЪ3UUЪUUGG媫UUUUЪUUЪUU? pHDD ]UUSUII(*UU3*UU3*;*BpCUU9U9J"U?͙5U UU)2)2U:UU͋UN*>UU>UUU*UUUUUU-IUUŪ(UU(UU6UUߪ}=ܪVUUVUU?)-UU*ZUUZUUuUU*$ 7H*Y$UUY$UUr/*ŋ1'UU'UUAًًAUUتتUUOQrUY۪Y۪HU7 GUU*Ԫ"UUԪ"UUUU,**,**UU"UU*UU"UU8**ы͋7*UU,UUު,UUު*UUSUUUӪݪӪݪUUI +UUUUڪڪȪͪتتUUDUUUNN[hhGUUN׋C*;UU UU;UU UU-)*U!Ǫ?Ǫ?Ue)GUccqd).OUUHOUUHfހ2} .9hUUUUuVΪVUUΪVUUL=UVUU$UUUU$UU<* ً@UU3Ϫ3ϪUUIKUUVΪVΪUU=?UU̪/UU̪/UUUU9UU94/UU4/UU@UU׋ D@D @@ =*UUUUU*U&(UUUUD?)NUU]p]ptkUU.8g*rUUrUU8*- L9xUU0.t]UU]UUNx?D|UuU*(*uUUUUUUUU*{= ߥʿËNjvaaTG` _LL )AU˪h˪hUрsQH\` 0UU **UU*UUUU9D*PUUU3UU3*U)ȪUUȪUU`UVUUUUUUUUa֪L*UUUU(* Nj*UUll UU׀Y{$UUVUUV۪%=U*7UU7UUJU* ꪫ誫**۪磌۪磌*Uت[UU *檫UU檫UUUU*ZR-UUUU ۋGʫʫ1U$$UUfhCUUCUU2!UUUUU᪫᪫**⪫plT,GP[9UUB9UU/U%ݪ%ݪ*UUUUSV]UUgUUg*yWUUa UUުުyUU&xUUUU'UVKUUOKUUO\m yUUbUV#KUUFKUUF%\UUkUUiWVGDUUGDUUUU"VUUUUUUɪ~_䪫_䪫UU۪誫ҪUUQUU*:*:>UUR:cUU;\UU]UU֪E6E6UUUUrWUU#UU#*qUUJ?UU**vUVRUUUURUUUUhb~ ƪ\UUUU-9UU9UUUU.UV$#UU$#UU9UU9UUUU$ܪ$ܪUUѪƪƪUUUUiUUi\zƪ g*8UU8UU(M*DUU9*֪-֪-UU!EUU;UUUU/***1UUUUXڪGA6UUA6UUUU*UUUAɪAɪڪUUUUUUO*΀dd.UU:쪫ꪫȪހ֪UU֪UUUUVUU)(UǪǪgU OZ *eUUeUUx$*%NjNjnnڀ]]xUe媫e媫ZUO UUV UUUU*")UUU!!!+*44+*~!q!qUUU֪a*݀UUpUUpVUU xxPЋP싋 t,,,, td{,,,,{ ,,({,,,,{؋,, l>#UU%UU#UU%UU,*UUUU*aK@UUU7⪫7⪫=UUUϋϋ@UU=UU=UU1CUUCUUUUnl;UUUUު֪ު֪UUUUQg߀Unn؀UYC.UUUUUUUUGGNUUNUUΪZUUUUV2UUUU l, lʋtl, l dd 4d} @H *U*8UU8UUsccccs8UU8UU*U**UUǪǪ=sUUcUUcUV|UU8UU|UU8UUs****s*U|UUǪ|UUǪUV=UUUUUUUU=UVǪ|UUǪ|UUUs****s*8UU|UU8UU|UUUVcUUcUUs=ǪǪUU* UUUMUVEUU)UUEUU)UU7UV7)UUE)UUEM*TUUUUUMUV֪EUU֪EUUȪ7UV)UU)UUUUVUUUU֪UU֪ȀȪ֪֪UUUUV)UUUU)UUUU7ȀE֪E֪M*UUT UUHȪ7UUȪ7UUUU΋NN7UU7UU7UU7UUNNUU7UUȪ7UUȪHUUUUUUHȪȪȪȪHUUUU SUUKVUUDUU(DUU(66(D(DUUKRSUU몫KVUUDUUUUDUUɀ6((*UUUUUV몫UUUUUUUUUVɀUUUU몫*UUUUUV(UU(UU6UVDUUDUUK몫R DDDD DD TT T    t,,,, t d{,,,,{ ,, ؋{,,,,{(,,     < O*UUUUUUUU*U,ߪUUUUUUUUo7(c/#UUEUUEUUYUUUUՅ[ -UU3*UU64UUUU4UUUU**UVUUUUUU*667UU88.UUU,I(oa**UUUUUUUU**!MUUUU/MUUUUUU UUE t4xtϋlx, pdpHpd PªªUU UUUU[`UUUUU*UoWݪUUUUUU4UUJp `$IUUIUU\UUVUUUUU*UUUUUqqUUUU/UUUUЪЪUUUUo?{|x !UU$@UUUU֪2UU$UU$Cj UU"ުUU)USUUSUUsHU(XUUIUU  X88pXp }UUV7UUUU7UUUULUV;aUUŪaUUŪoUVU}UUnUU88LUUM7b7bUp~~UǪcUUǪcUU>NUVUU9UUUU9UU*q*UUUUUU*UUs`<UUUUUVUU7UU7᪫yUUyUUUUK hQUUQUU( *UUUU*e?UU?UU)֪  ))??ej UU*8UU8UUaƪaƪnUUUUUUUUU`<Jn:aUU**UULUUDD BUUUUUUpt*ȪeȪe:UU:UU*㪫*UU*UUUVUUUUUUUU*UVUU iUURR)UU ր88eUU??)UU  )(U?Q?QeiU $uUUuUU$$EUU$EUU UUȪggy^UUU<<7UUUUJKUU7᪫#UU#UUU,~UU**UUNUUNȀ UUUVUUUUUU*X*mUU@6UUUUUU!$UVUU-UU#ꪫUUUUUUUUUV⪫UU%UU\ )UUAUUUUV䪫gUUUUnOO&UUUURRUUUU 9UUz$i$iUUUUƪƪѪUUܪUUܪ\UUƪƪ\UU#UUUU#UU.UV9UU9UUUU-$$9UU T PT P    !#$$%'((),-'(,- "##$%&&''(/001FGGHKLLMMNNOQRRSSTTUUVVWWXXYYZZ[]^^_fghituvwwxxy}~    .DFLTcyrlXgreklatn   CAT PMOL XROM    aaltcaltcaseccmpcv01cv02cv03cv04cv05cv06cv07cv08cv09dligdnom fraclocl0locl6locl?@ABCDE4%  " $792:;<6513F&.6>FNV^fnv~6>FNV^fnv~x Z L > 0 |6 !!"*"###$\$%%j%%&<&z&&''*'(','F'N'z'''(( ((()))*,*4**+++,,:,f,j,,-x- ...//NOP?%&'QR;L@AMK<678FGHISTUV():*+,-./012345wvxyzjZ { !"*0<>\^_qVY %()9:Z !*,.0LNPR'()*+,-.CDEFGHIJKLZ`cefj|}~<~$4DV\hpv| &,28>DJPV\bhntzn@PoAQ=Mu>NX^5CY_yD`rEZasF[b7Gc8H\d9Ie:Jf;K]g<L{~?OjRSmW6oX=YZB[zT\Jp]^tUu>N5CyD7G9I<Lhijklm@PAQ>N?O=M>N B[*XY+[_-ab2dd4gg5no6uu89:J  ! $') ,*(,7<>EInopqr sz  |  K  ! $') ,*(,7<>EInopqr sz  |  %  ! $') ,*(,7<>EInopqr sz  |    ! $') ,*(,7<>EInopqr sz  |    ! $') ,*(,7<>EInopqr sz  |  Z  ! $') ,*(,7<>EInopqr sz  |    ! $') ,*(,7<>EInopqr sz  |    ! $') ,*(,7<>EInopqr sz  |    ! $') ,*(,7<>EInopqr sz  |     ! $') ,*(,7<>EInopqr sz  |    ! $') ,*(,7<>EInopqr sz  |    ! $') ,*(,7<>EInopqr sz  |   9;<8: JK%Z =l"lTf0BT  X r :  `  ^*4^8@HPX`hpx   $ &,08@HPX`flrx~   "(.4 &,"*06<BHNTZ`flrx~! $'%# &,/-)531  &,28>D=;976>FNV^fnv~ECA?   IG$*06<QMK $*06<BHYWUS"(.4ca_]#!2:BJRZbhntzmkeig1/-+)' qo &,{ywus3 }  &,28>D75 &,<:88>DJPV\bhntz            $ &,06<BHNTZ`flrx~      "(.4 "(.4  $(&$"(.40.*642  &,28>D><:86<BHNTZ`flrx~    FDB @      JH$*06<RNL  &,28>DZ X VT $*06<db`^$"28>DJPV\bhntzn l jhf    20.,*( rp"(.4|zvt4x ~ "(.4:@FL6 &,=;9    &,28>DJPV\"  $ $ $ $  "13;BJLQ"S[(12345>>6_`7no9;< MwM -w-wMw- (2<FPZdnx"" (2<FPZdnx &0:DNXblv  &0:DNXblv $.8BLV`jt $.8BLV`jt",6@JT^h",6@JT^h *4>HR\ *4>HR\(2<FP(2<FP&0:D&0:D$.8$.8",",  $   .  0@  "2BR   $4DTd    &6FVfv     (8HXhx       *:JZjz       " ,<L\l|        $ .>N^n~         !!CL.CL $# ( JO$ J"nou{tpqrswvxyz" !<>\^_q&uvxYY [g (2:BJRZbhntzuuvuxvuxuvx**uvx o"*{ o"*{ o"*{ o** uvx{Z+" !<>\^_q$ "#$%&'()*+,-./0123456789:;ce   !#%')+-/13578:<:=?@ABCDFGHIJKLMNOPQRSTUVWXYZ\]^_57;=?ACEKMQSUW[]_a{suwcksw|nopqrstuvwxyz{,BCDEFGHIJKLMNOPQRSTUVWXYZ[   "$&(*,.02469;=`abcefghijklmnopqrstuvwxyz{|}~68<>@BDFLNRTVX\^`b|tvx !_q "#$%&'()*+,-./0123456789:;ce   !#%')+-/13578:<:=?@ABCDFGHIJKLMNOPQRSTUVWXYZ\]^_57;=?ACEKMQSUW[]_a{suwcksw|b:," !<>\^_q" !<>\^_q" !<>\^_q" !<>\^_q" !<>\^_q$ "#$%&'()*+,-./0123456789:;ce   !#%')+-/13578:<:=?@ABCDFGHIJKLMNOPQRSTUVWXYZ\]^_57;=?ACEKMQSUW[]_a{suwcksw|nopqrstuvwxyz{`8-" !<>\^_q" !<>\^_q" !<>\^_q" !<>\^_q$ "#$%&'()*+,-./0123456789:;ce   !#%')+-/13578:<:=?@ABCDFGHIJKLMNOPQRSTUVWXYZ\]^_57;=?ACEKMQSUW[]_a{suwcksw|nopqrstuvwxyz{^." !<>\^_q" !<>\^_q" !<>\^_q$ "#$%&'()*+,-./0123456789:;ce   !#%')+-/13578:<:=?@ABCDFGHIJKLMNOPQRSTUVWXYZ\]^_57;=?ACEKMQSUW[]_a{suwcksw|nopqrstuvwxyz{\/" !<>\^_q" !<>\^_q$ "#$%&'()*+,-./0123456789:;ce   !#%')+-/13578:<:=?@ABCDFGHIJKLMNOPQRSTUVWXYZ\]^_57;=?ACEKMQSUW[]_a{suwcksw|nopqrstuvwxyz{Z0" !<>\^_q$ "#$%&'()*+,-./0123456789:;ce   !#%')+-/13578:<:=?@ABCDFGHIJKLMNOPQRSTUVWXYZ\]^_57;=?ACEKMQSUW[]_a{suwcksw|nopqrstuvwxyz{ou{vx J"nou{tpqrswvxyz" !<>\^_qJ"nou{tpqrswvxyz" !<>\^_qJ"nou{tpqrswvxyz" !<>\^_qJ"nou{tpqrswvxyz" !<>\^_qJ"nou{tpqrswvxyz" !<>\^_qJ"nou{tpqrswvxyz" !<>\^_q$|}^_`abcdefg~j ZZ$Z ^gjj |  Xh^6", a ap5@A=>5yrs789:;<?m6oBptu>5y79<@A>?=> B[)XY*[],no/uu123p5PQMNCDEFGHIJKLORSWXYZ[T\]^UNCDGILPQNOMN B[)XY*[],no/uu1238"B80Pjzjz"0BPP/N$YZ[\]=JCDEB>NOP?QRL@AMKFGHISTUVijklm$MSY ()9:*,.0LNPR_abdg YZ[\]ijklm _abdgf0X=W !"#$CDEB>?%&'@A67FGHI()*+,-./012345h0*MVY()!*,.0'()*+,-.^ Yi_ [kb \ld ]mg =CDEB>?@AFGHI MY()*,.0 JNOPQRLMKSTUV S 9:LNPRF  !"#$%&'67()*+,-./012345 *V!'()*+,-. Zja \vDFLTcyrl(grek6latnDcpspkern:D  "#$%&'()*+,-./0123456789:;   !#%')+-/13578:<@ACEFHIJMNOPRSUVW[\^_acefhkmnpqrtvw{  -/3:=?@ABCDFGHIJKLMNOPQRSTUVWXYZ[\]^_   %/13579;=?ACEGIKMOQSUWY[]_acdfhjlnpsuwy{}   !#%')+-/13579;=?ACEGIKMOQSUWY[]_acegikmoqsuwy{}     '()*+,-.56789:CDEFOPQRSTUVmnopqrst}~ !"#$%&'()*+,-./01234567} 6 @ V 6 @ f t t f$*<NNtNNtN .$ ($N   N $ ! !!4N!B!\$""\"f"p "\ "v"v"$"v"\&&$$$$$NN&&N&tttt'()$ )*4*+,++   +$$$$,2,X$$$$$< < < < N,.&./N N t0 01NN1tN12,2f2,NNNN$ 2$$$$3 4L 4 t t t t t t 7h!' '!!!$$NN7~7<N 77 8N8\$ 8n8t 9 999! 1N9t t t t t :$< < 7  $!< :N$$$;6?h&@2 @t t $ N!$ A $A8t$9t$B"t  Bx:$B:CCCC CBx ++ N N: + $ DlD 9D !D!B"+ "v"v"v"v"v"v"v"vNt$NNELE^H4$NNNNHNHJ.CEL J.J?@AB.`R`@@!#\ p@ A!%k]_ac +PPpP:PP@\  @` \8"+@@BDpEpFpHpPpTbpkPppppppppppppppppppppppppppp pppp GpKp`pgpppppppppppppppppppppppppppppp2p?KWp`papbp:=EpFIP`papcpephpipjplpmpspupvpwpzp|p}pppppppppppppp&pLpNpZ\stuvwxzp|p~ppppppppppp p pppp"@pBpDpFpTVXZ\ppppppppppppppppppppppppppppppppppp p!p"p#p$p%p&p/p0p1p2p3p4pGpHpIpJpKpLpMpNpWpXpYp]p_pcpepfpgphpipjpkplpmnopqrstpppppppppppppppppppppppppppPCDEFGHIJKLMNOPQRSTUWXYZ[\]^jpXZ[P\{ !m\Pp]_acwP05 70809`:;P! # % 30578P:P pp57pWpZppp!#%6pSpkmspp p pX\gpqppppppORpTpWppp]_acopppqprppppppp``p `wpi @`$(025`67 ?@J`KNOQRSW Z p@``` ```   !`#`%`')+-/16 EFPRS \]^_dk`m`ns `` `    ./09:;Y]BMTX`Z\bdg`kq uy~```p `     046:@BFHJMO`R T W`gkmq}    `.`2468:<>?ACEHJLNPR`]`_`a`c`egikmo p q r   ``    56789:;<=>?@AB[auvwxyz{|`qtwp8`9`:`JKLMNOPQRSTUVXYZ]Sm0rp0m rp` ZSm r P@lp@`@pe`p`pp`ppI`Q`k```` .0`Rb`o`q`y`{`h`l@lp``#0m0p0`@ lpp#PI`k`mPppP0mPP!m]_ac.`R`!#\ p@/5@7:@@!@#@%@5@7@Sk@m@r@@@@ @C@X@Y@\@_@@@@@@O@Q@S@W@]@_@a@c@oq@@@@@@@J````````` ```8`9`:`p\P@p`t`x` @_lmrsy#0m0p0moptuIPPPPpp56789:;<=>?@ABCPDPEPFPGPHPIPJPKPLPMPNPOPPPQPRPSPTPUPWPXPYPZP[P\P]P^PPuPPPPPPPPPPPPPPPPPPPPPPPPP{"+5 79:pp! # % 5p7pSk m rp p  pK:=CpFPX Yp[_p   p O QpSpUW dsuw] _ a c oq}pppppmnopqrstppp\p@%@\gPP p/5`7:!`#`%`57Sk`m`r`` CX`Y_```0O`QSW`]`_`a`c`oqwP@lpwp `G``````````a`b`9Pe`o "+5`7p;!`#`%`8:<Spk`m`t``K:=FKPX`\`````pO`W`suw]`_`a`c`opqpmnopqrst``@@@pe`p`pp`ppI`Q`k``` .0`Rb`o`q`y`{`h`%@pKpe``pp```ppI`PpQ`\pk`pp``pp/pp`p "`.0`Rb`o`q`y`{`h```pp``I`\k```pZ`3/j5p!p#p%pSkpmprpppXpppppOpWp]p_papcp5p!p#p%pkpmprp`ppZXpppppOpWp]p_papcp50!0#0%0Sk0m0rp00X00000O0W0]0_0a0c0 ```````$J`Kp``pppppp```pI`Pp\pk``ppppp/pp`pp `"p``8`9`:`J@`jpnprsyzp``moptup!.0`Rpp56789:;<=>?@ABppp0JpK`ppp`p```ppp`I`P`\`k`p``p``/`Z`3`p``/j p"`pp8p9p:p,JpKppppp`ppppI`Pp\pk`pppppp/pZ`3ppp/j ppp8p9p:p$Jpppppp`ppp`I`\k`p`pZ`3p/j ppp8p9p:p2@pJpKpe`ppp`pp```ppppI`PpQ`\pk`pppp`pp/ppp`p p"`.0`Rb`o`q`y`{`pph`8p9p:p pp?_lpmppwPp @pppuppppp @``pp!.0`R _lmpwP @u:57r CY_QS@````V`Y```p!.0`R! p:?_lpwP57r CY_QS l`R  @ @`V[_lmuw ppp(*,.029;=ou",-FMNfhjlnxpu{&  @ @`_lmw pppp(uMNxpu{ 0 @ "`+@`qw ```````pppp```(u``````````K`MN:=`F`P``s`u`w`0000`xp````````````````````m`n`o`p`q`r`s`t``````C0D0E0F0G0H0I0J0K0L0M0N0O0P0Q0R0S0T0U0W0X0Y0Z0[0\0]0^00{0000000000000000000000000p0w @`J`````````` ```8`9`:````V`Yp``F`@lpw"`@`pp``VpYp`pF`p!.0`R # 0 @ @`[w pppp(9;=uMNxp{ P @+lpwp `G``````````a`b`9P:e`5!#%kmXOW]_ac@plI`)@pJpe`ppp`p````ppppI`Q`k`p`pp` p"`.0`Rb`o`q`y`{`pph`8p9p:p5 ! # % k m rp`  ZX     O W ] _ a c 5!#%kmpXOW]_ac #`%`&`'`)`*`,`-`.`/`1`3`C`I`L`M`]````````````````````````````````````` ````@`B`H`I`T`W`X`Y`c``````````````````````` ``#`$`(`*`+`3`@`G`H`J`L`N`O`Q`R`U`V`f``````````````````````````````` ``/`3`5`9`?`A`C`E`G`I`]`^`c`f`j`l`p`r`y``````````````` ` ` ``````````````#`$`%`&`'`(`)`*`+`,`-`.`/`0`1`3`5`7`9`;`=`G`I`K`M`O`Q`````````````````````}`~````````````g``````=`>`?`@`B`C`D`E`F`G`H`I`W`2@pJ`K`e``````````````I`P`Q`\`k````````/````` `"`.0`Rb`o`q`y`{```h`8`9`:`0J`Kp````ppp````pI`Pp\pk``pp`pp/pZ`3p``p/j `"p``8`9`:`&J`K`l````````````P`\```p````/````` `"```8`9`:` KP\/ ?_lmpwP @u5 ! # % Sk m r  X     O W ] _ a c 00`5@ lpp!#%I`kmppXOW]_ac065J`K`````p`p`````!#%P`\`km```p``/`X````OW `"p]_ac``8`9`:`\$@`J`K`````p`p`````P`\````p``/````` `"p``8`9`:` ``#0V`Y`m0p`0ODP5!#%kmXOW]_ac5!#%kmXOW]_ac@l 0 0p@ @@_0kl0m0uw {pppp0p(uMNWEIMpZp]acghXij l mor`tuvXx{|}````xp !"#$%&GHIJKLMNY]cpC`D`E`F`G`H`I`J`K`L`M`N`O`P`Q`R`S`T`U`W`X`Y`Z`[`\`]`^`000`Xu0{00`````````````````````````00 P\Pjlxp P_klmMZgptx@u`  @@PP@_ kxl m u`{ IPruxCDEFGHIJKLMNOPQRSTUWXYZ[\]^   Xu {P    lPP@lWo\x@E\cm} !"#$%&]< `_lmWag`hit@vx{|GHIJKLMNYcY`u@\=hv````C`D`E`F`G`H`I`J`K`L`M`N`O`P`Q`R`S`T`U`W`X`Y`Z`[`\`]`^```````````````````````````} 00lrsymoptuIo@  056789:;<=>?@ABCDEFGHIJKLMNOPQRSTUWXYZ[\]^hv\o\m rp` ZY`@` p_kplpmwppP`psu@ppp@@plI00`%0\ @pP@`lp@pe`p`pp`ppI`Q`k`p`` .0`Rb`o`q`y`{`h`,JpKpppppp`ppppI`Pp\pk`pppppp/pZ`3ppp/j ppp8p9p:p! p:?_lpwP57r CY_QS - `#p%p&p'p)p*p,p-p.p/p1p3p>`@`CpIpJpK`LpMp]p^`ppppppppppppppppppppppp`ppppppppppppppppppp pppp@pBpHpIpP`Q`TpWpXpYp\`cppppppppp`ppppp`pppppp`ppp pp`#p$p(p*p+p/`3p@pGpHpJpLpNpOpQpRpUpVpfp`ppppppppppppppppppppppppppppp`pp pp/p3p5p9p?pApCpEpGpIp]p^pcpfpjplppprpyppppppppppppppp p p pppppppppppppp !#p$p%p&p'p(p)p*p+p,p-p./p0p1p3p5p7p9p;p=pGpIpKpMpOpQpRpppppppppppppppppppppp}p~ppppppppppppgpppppp8p9p:p=p>p?p@pBpCpDpEpFpGpHpIpWp)#`%`&`'`)`*`,`-`.`/`1`3`@`C`I`J`K`L`M`]```````````````````````````````````````````` ````@`B`H`I`P`T`W`X`Y`\`c`````````````````````````` ```#`$`(`*`+`/`3`@`G`H`J`L`N`O`Q`R`U`V`f`````````````````````````````````` ``/`3`5`9`?`A`C`E`G`I`]`^`c`f`j`l`p`r`y``````````````` ` ` `````````````p p!#`$`%`&`'`(`)`*`+`,`-`.p/`0p1`3`5`7`9`;`=`G`I`K`M`O`Q`Rp``````````````````````}`~````````````g``````8`9`:`=`>`?`@`B`C`D`E`F`G`H`I`W`# 0 @ @`[w pppp(9;=uMNxp{E 0 @ @`V[uw ppp(*,.029;=ou",-FMNfhjlnxp{#`%`&`'`)`*`,`-`.`/`1`3`C`I`JpL`M`]``````````ppp````````````p`pp`````````````` ````@`B`H`I`T`W`X`Y`c``````p````````````````` ``#`$`(`*`+`3`@`G`H`J`L`N`O`Q`R`U`V`f`````````````````````````````p`` ``/`3`5`9`?`A`C`E`G`I`]`^`c`f`j`l`p`r`y``````````````` ` ` `````````````` p#`$`%`&`'`(`)`*`+`,`-`.`/`0`1`3`5`7`9`;`=`G`I`K`M`O`Q`Rp```````````p`p````````}`~````````````g``````8p9p:p=`>`?`@`B`C`D`E`F`G`H`I`W`#`%`&`'`)`*`,`-`.`/`1`3`C`I`J`L`M`]``````````````````````````````````````````` ````@`B`H`I`T`W`X`Y`c```````````````````````` ``#`$`(`*`+`3`@`G`H`J`L`N`O`Q`R`U`V`f```````````````````````````````` ``/`3`5`9`?`A`C`E`G`I`]`^`c`f`j`l`p`r`y``````````````` ` ` `````````````` `#`$`%`&`'`(`)`*`+`,`-`.`/`0`1`3`5`7`9`;`=`G`I`K`M`O`Q`R```````````````````````}`~````````````g``````8`9`:`=`>`?`@`B`C`D`E`F`G`H`I`W`p 00!@ lPwp 00!@ lPwp`w P @+lpwp `G``````````a`b`9P:e`5!#%kmXOW]_ac"#p%p&p'p)p*p,p-p.p/p1p3pCpIpJ`LpMp]pppppppppp```pppppppppppp`p``pppppppppppppp pppp@pBpHpIpSTpWpXpYpcpm0rpppppp`pppppppp`pppppp0pp pp#p$p(p*p+p3p@pGpHpJpLpNpOpQpRpUpVpfppppppppppppppppppppppppppppp`pp pp/p3p5p9p?pApCpEpGpIp]p^pcpfpjplppprpyppppppppppppppp p p pppppppppppppp `#p$p%p&p'p(p)p*p+p,p-p.p/p0p1p3p5p7p9p;p=pGpIpKpMpOpQppp`ppppp`pppp`p`pppppppp}p~ppppppppppppgpppppp8`9`:`=p>p?p@pBpCpDpEpFpGpHpIpWp#`%`&`'`)`*`,`-`.`/`1`3`C`I`L`M`]````````````````````````````````````` ````@`B`H`I`ST`W`X`Y`c`m0rp````````````````````0`` ``#`$`(`*`+`3`@`G`H`J`L`N`O`Q`R`U`V`f``````````````````````````````` ``/`3`5`9`?`A`C`E`G`I`]`^`c`f`j`l`p`r`y``````````````` ` ` ``````````````#`$`%`&`'`(`)`*`+`,`-`.`/`0`1`3`5`7`9`;`=`G`I`K`M`O`Q`````````````````````}`~````````````g``````=`>`?`@`B`C`D`E`F`G`H`I`W`#`%`&`'`)`*`,`-`.`/`1`3`C`I`L`M`]````````````````````````````````````` ````@`B`H`I`ST`W`X`Y`c`m r``````````````````` `` ``#`$`(`*`+`3`@`G`H`J`L`N`O`Q`R`U`V`f``````````````````````````````` ``/`3`5`9`?`A`C`E`G`I`]`^`c`f`j`l`p`r`y``````````````` ` ` ``````````````#`$`%`&`'`(`)`*`+`,`-`.`/`0`1`3`5`7`9`;`=`G`I`K`M`O`Q`````````````````````}`~````````````g``````=`>`?`@`B`C`D`E`F`G`H`I`W`Sm r )@pJ`e`````p```````pI`Q`k`````` `"`.0`Rb`o`q`y`{```h`8`9`:`#`%`&`'`)`*`,`-`.`/`1`3`C`I`L`M`]````````````````````````````````````` ````@`B`H`I`T`W`X`Y`c`m rp````````````p``````` `` ``#`$`(`*`+`Z3`@`G`H`J`L`N`O`Q`R`U`V`f``````````````````````````````` ``/`3`5`9`?`A`C`E`G`I`]`^`c`f`j`l`p`r`y``````````````` ` ` ``````````````#`$`%`&`'`(`)`*`+`,`-`.`/`0`1`3`5`7`9`;`=`G`I`K`M`O`Q`````````````````````}`~````````````g``````=`>`?`@`B`C`D`E`F`G`H`I`W`m rp Z@J`````````` ```8`9`:` 0 @ @`w pppp(upMNxp{ `````#0m0p`0`"` P @+lpwp `G``p````````a`b`9P:e``@ lpp#PI`k`mP`pP0PPPp`@p!`@_u@jPl@r@uP` %\Ppp A!%k]_ac`t`x`7 @@@p@@S"5!#%kmK=FIPXOWsuw]_acmnopqrst    ""$25@&BX2Z]I__MabNeePjnQpsVuuZww[y{\_   =??BBEKMMPTVY \\$^_%cd'gg)jo*ru0xx4579\bz{    "%(/22668<??DFIIKKMNWWY[]]`befkkmmoptu33::==?CEFHRTTV\_acjlm oo quw"%&*+137Vcghtz  {  ~&&/138?HLUXXZZ\^``bdggimoz|JLL@NNAPPBRRCTTDVVEXXFZZG\}HjkzNWW(YY)]]*__+aa,cc-et.}>NZ_bdfiry|  5UW^``ghqqtt||8VXX[]uuI&t8N?0ppppP@0@0`p@ `ppp`pppp ` `00P00PPPph@p`ppP p```p````P `p(08@ PPPP`p00ppP0`` 0PppPp`Pp`pP@p````PP@@PP@p`0 0000 pp`ppp`ppPPp`ppP@` ``8@ P`pPppPP`ppP`PPPPP`P`PpPPp p ` `@ 0 `PpP@p`PpPpPP`0P @Ppp`p```0 @99537661<C/?D/1@@+8" $( .*! 537 -  &% 53'>'7+'=   )  J     """"))) $$$$       *&    8".F(  #      """   )      $   0 0   0 -  0  M A( A!HI E K  !( !L# %B# %  ,, #),, .  ( # #%/ #,  #  !%  !     !/  # #!%!%      8-")$$$$((     . . *&*&*&*&*&!%!%    & --                   (:2;:2;''62>="'++++   1</?G444''--(+))8%07,3%**$# (+   (2+ 1="   $    #   ;    $        $ >6 6 <4 49  $55 %!! ! #    !  !     # #           .&/.&/")&21"  %0,3:'''""* "<>?0B_2bbPjkQmnSpsUuuYy{Z]tz  =?@BBEIKKMMPPRTWY!\\$^`%cd(fg*jk,mo.rv1xx679;^_c   "%(022668<??DFKKMNWWY[]]``bbefkmoptu33::==?CERTTVVX[^`chmm qq ss uu y-13ACJLx  y  |}~&&/138<=?HJJLVXXZZ\^``bdfgimoz| NWW8]]9__:aa;cc<et=}M]inqwz  5UW^``ggqqtt8[]]nnpprruu '( p,@HHp LpH(t,$t,0 H``(pH p<LXLLX`84$hH p8Xd ``x `hh`$(p|P|Pp L pHpHpHpHpHpH HH``````((((XL444444 ,$hhhh`ddddd ` `pH4pH4pH4$$$$hhhhh,,,,$|8t,00`080x0<HpHpHpHp``` | 88\8XXX(d(d(d(d(d(d p<`XL `XL(0h| `t@( hd Tp``dX\ `L`DD\ ( 8@ pH4`(d(d(d(d(dhpH4pH4 H ,,,,``D , @HppH4 H ,`pH4ppH4hth``88(dp(dX$ptpH4h````XL `hTX pH$0xX(ht 8X `4 $|h\ll0`ppX@ d 888\LH```X `P `($`,0$h(0 X\4X Hd4Lx|x\\@48<plllxxlDpLx@Ht@hhhhdThP84\P, 88L 00XP(00088PT8H\Hl(8( \<<<L<04HtpHd |XLhpH(TpH$`,pH Hp`XL@L`XL pp ``p|p```dH0(p 8pptHDH`|H,$`XX HpTPt 8 l ,HLpH@(TDH pLHHH $`LLD x  0` T4|, 4h xt||P@hh$ `xP 4Hh,thh , (  `h 0h p4$   H8`H`H` ` p \hH80hTT  pL xt|t<`4  $$XL`XL`Lx   PHH pL xxhH$P$h P pH4pH4 H ,hhh pL xt|HH````L `L `L ` PT T LxLxPX(XXHXXd`X,\48l\48$X0l\|8@8 H\ppxd|<xtHxhxp0$ppH4((($hhhhhtH,$$$$$l,,,00x0x0 HpHpHpHp````888p8tXXXX(d(d(d(d(dpH `pH ` p<` p<` p<` p<` p<`LxLxXL `` `4`H40pH4pH4pH4p44pH4pH4pH4pH4pH4pH4pH4pH4hhhhhhhh```````(d(d     XL `XL `XL `XL `Td||        pHpHpp0pDpppdd d d d dpppppppp  p\0L| Xd , LppppppppL L 8L tL (< t   pppp        pHpHpp0pDppppppppppp xd   (< t        pHpHpHpHpH|ppppptd$d X $|phXh|ppppHHppXLXL 8L X t D`  4hP88 0plp`p0pl\Pl  lt|lt| plhhxPL` (ppT,dhXh \|H\ \pldl<Thl<\pPp|dl<Thl<(XHX`XH\d\@,t dx@ H D p<PHH `d,pHpxP|Ph 4 0 0 ( ` XH`,tL  D  |0 \pT T TT L L L0 8PP `T`hx (x  dt t  x t     ph <H      l  h X@ X@  dtx0tsL $ H`<`XDtl`@x8x\ptPPxlhl,L`h`  hHHh$XX0XX@48lPxXh\,xPd$(kXHt(lPxXh\,xPd$(kXHt(lPxXh\,xPd$(kXHt(lPxXh\,xPd$(kXHt(lp| tt xxt0t8$$(<H|LLpH`4h ||4$4\ڒ۬{mozilla-vpn-client-2.2.0/src/ui/resources/fonts/Metropolis-SemiBold.otf000066400000000000000000001262601404202232700261540ustar00rootroot00000000000000OTTO CFF T$.dOS/2k 'l`cmap$K# $headBB16hhea$hmtx90maxp Pname`{`postD iImY_<۫P۫PJ3 P R1oBXXXX@ "3 "" 56}    +   D> $ & j 6Q  4  2   4 V'   $ &MetropolisSemiBoldMetropolisSemiBoldMetropolisSemiBold SemiBoldMetropolisSemiBold SemiBoldMetropolisSemiBoldSemiBoldMetropolisSemiBoldSemiBoldChris SimpsonChris Simpsonhttps://github.com/chrismsimpson/Metropolishttps://github.com/chrismsimpson/MetropolisVictory One Media Pty LtdVictory One Media Pty Ltdhttp://victoryonemedia.comhttp://victoryonemedia.com""""Copyright 2016 by Chris Simpson.Copyright 2016 by Chris Simpson.Victory One Media Pty Ltd:MetropolisSemiBold SemiBoldVictory One Media Pty Ltd:MetropolisSemiBold SemiBold  BB !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~  "#*+./01679:=>ABCDEFGHLMPQRSTUVWXYZ[^_`abcdejknopqrstuvwxyz{|}~7&       & 0 " !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~  "#*+./01679:=>ABCDEFGHLMPQRSTUVWXYZ[^_`abcdejknopqrstuvwxyz{|}~7&       & 0 ";;++++++XXVV""r MetropolisSemiBoldSemiBold)  #08=CKU[bkv      )5@JOPQRSTUVWXYZ[\]^_`abcdefghiru #)4=@FLR]cltz $'-39DJSY_ekv !'-3:AHOV]gov} %-5;AHOU[bins  $*/;BHNTZclsz""MetropolisSemiBold SemiBoldMetropolisSemiBoldSemiBoldspaceexclamquotedblnumbersigndollarpercentampersandquotesingleparenleftparenrightasteriskpluscommahyphenperiodslashzeroonetwothreefourfivesixseveneightninecolonsemicolonlessequalgreaterquestionatABCDEFGHIJKLMNOPQRSTUVWXYZbracketleftbackslashbracketrightasciicircumunderscoregraveabcdefghijklmnopqrstuvwxyzbraceleftbarbracerightasciitildeexclamdowncentsterlingyendieresismacronacutecedillaquestiondownAgraveAacuteAcircumflexAtildeAdieresisAringAECcedillaEgraveEacuteEcircumflexEdieresisIgraveIacuteIcircumflexIdieresisEthNtildeOgraveOacuteOcircumflexOtildeOdieresismultiplyOslashUgraveUacuteUcircumflexUdieresisYacuteThorngermandblsagraveaacuteacircumflexatildeadieresisaringaeccedillaegraveeacuteecircumflexedieresisigraveiacuteicircumflexidieresisethntildeograveoacuteocircumflexotildeodieresisdivideoslashugraveuacuteucircumflexudieresisyacutethornydieresisAmacronamacronAbreveabreveAogonekaogonekCacutecacuteCcaronccaronDcarondcaronDcroatdcroatEmacronemacronEdotaccentedotaccentEogonekeogonekEcaronecaronGbrevegbreveuni0122uni0123ImacronimacronIogonekiogonekIdotaccentdotlessiuni0136uni0137LacutelacuteLcaronlcaronLslashlslashNacutenacuteuni0145uni0146NcaronncaronOmacronomacronOhungarumlautohungarumlautOEoeRacuteracuteuni0156uni0157RcaronrcaronSacutesacuteScedillascedillaScaronscaronuni0162uni0163TcarontcaronUmacronumacronUringuringUhungarumlautuhungarumlautUogonekuogonekWcircumflexwcircumflexYcircumflexycircumflexYdieresisZacutezacuteZdotaccentzdotaccentZcaronzcaronuni0237circumflexcaronbrevedotaccentringogonektildehungarumlautuni0326WgravewgraveWacutewacuteWdieresiswdieresisuni1EB8uni1EB9uni1EBCuni1EBDYgraveygraveendashemdashquoteleftquoterightquotedblleftquotedblrightellipsisperthousandEurominus      !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~ LOR(fS3[\[ I   ^ W * $J [U!8Q |%j;de,I=;p " !!I!"-""#b#$$@$%%&r&&''e'(g()*<*+,7,-t../K//0A01K12g234 45,56a67z8899:W; ;<=o>?? ?@?@AhBBZBCClD DE#EF&FGHGHZHImJ JK9KLRLMN'NOPEPQCQRcSSTCTUyVVWWXNXYZ8Z[\Z\\]J]^^7^_i_``z`aEab)bc:cd_de}efg:ghi+ijKjk|klm'mno^ppqvqrs$stHtuvEvwnxxyVyz{{{|r}}}~ ~G~y~~ `ހ|R „KЅuAfʈ*׊]77575׋ԋ׋ԋunb"nb7]igb_hgav)zulgUu/zFUpzulgUu/zFU7A#_A􋋋t1#a:.:a:-:'勋A%勋򋋋<鋋a<<苋a<򋋋u1AA_A[87%K0΋/=L䋋`m~W8 ȋ拋MiYG/_[YPl\#cvx:Zlhd?*ϓkDFd'H؋=+(F@(<oC狋oC/8h]\icORb*B)F׋<,(F?(h\\jbRPb8#\=܋RXIn;+Ջe}A,;T`#\baW_mNlTi\ie`]hl[imdתlaXG[dRlnfZHcʋ)zulgUu/zFU >:-DE,I"V,+LH >L΋+,V!͋!,ED;-!%]x|ltojjx}wg`j}xit}}{e|aez{w}kx_g||xxz{w}jnmeae{|}x#KMMKKNNK"[ai]LUnWHCq !]igb_hgaRvAŋŋQE'ed'ED'de'D""C&%C(""'O @X㋋UC︋^ѪTMCWfYO:֋޼40aB 0$ˋ /S݋UjՋ݋X*>؋܊UAGWj\WA׋ۿ &=8SUAuzV. &7/)狋싋M)Cꋋ ,)nnn7N䋋WhЋߋT9U\ydm6L׋EƋ=" /1K;)(W4&Zh/Gm\N,aaN*F.ͨыlߋNjM@IVfwQ0Rދы󋋋-勋!) ԋۨDX  F;VXFolO: C(0ƎϮTCCUbUNiĈۋɳ=KL$;.Ic|g[VjL(FӋ΋icNj򃋋k:w7jfuË@' Rc=?bN+-U܋ʞѵn@\Cu1t׋ʓlL򋋋*Kȋ_NKA]ϋ[G]XF*X97UU96X`G28aMI ΋Ȫ9IF/`&ۋCw4"]]4#w%%%a:GCBC{IF `'Y:7UU:6YaF28`AHÝ?sCC닋C닋ԋCC5.^݋[hϋ[c,)BڋC䋋䋋-Ë.w.'XQqCыۋCv7w7C_w7v7_ۋCwwCww*X97UU96XW96UU97W! "ҋC'--'B{4ƶP64v>qC_QOI>AˋN\?n9X97UU96XW96U2ً؋;! 1 "6{{C'-#H;(l@Iv4ƶP64ـ '?䋋RgZ"( %fPG/OFGF\a[Swr#k]-48ыpȋpы EG9݋GFCCCnnnC'76'mC@AW>C닋싋(IJ'닋싋)IJ&BA'QˋTˋ"A!ꋋr,r勋85?58RAŋŋދ5?5ދ勋|1.⋋.0 W W1)ۋ苋;苋e& ,64ˋ ɋe[JqO8̋yj-Z\z_uaԯϝ̋jXƋʩŋe`]NZiZZlŋ 23'>Mb9Oɮ؋jIыM45OF**E)''+ًjX9Co`_6D?10?DYVFi9 `OOۋbQMi?'32׋iQ&ыO54MF**Eġ;^[1k@(3/'4 SDYՋGT`yG}_Bs` & 4 4@֋{rh=xvnurhXb-229X䋋jvȋ˼΋bTMjA($$ԋiT؋" ދƋP89PP98P݋8 ݋ ŋ^KFRYM͋OȬЋdlkffljeMdlkfflje2jct苋1NL3i{b"&bB>O?t$΋OO*,8 ݋ ɋbPH]^Lы ɋbOI\^LыE̋ԋfṈ؋8 ݋ ŋ^KKV`TۋFǬϋ *''**''*׋C65C?10? 23'>MbE9ɮ؋jIыM45OF**E `EbQMi?'32׋iQۋ&=ыO54MF**E$T5ζ#AI)'<6Mgs̋^PDҋrfX2ZV^`jznjtsm(9K 9nra{d5Oɋ@֋" " & hr5Ћk[OjG<ދ ݋ ŋEYˋˋۋr* ' o[C񋋋BAAVM 5 5MV@Hd`l㋋o+"od)O^=䋋ՋA2Ջ I&::SoF؋ҋľ9>{uwCOqgXzyhNAx{>Aŋ㋋ŋ3&:؋ȤYqu؋9|XLpы>FpqLRW:|VQehs}zcL߬ʋċSˁ8i^M%g_big]ahb"bߋ`]RlHK1ϋ*͋勋M̃m_9CvnkًDLEOrɋQrbOF%ڼ/H֋΋\H69zUQHZU>D@D/B:Cy#@MFM@M"L֋LЋL֋"z'QˋTˋ"\hpmkhooibgpmkhooi`֋⋋@⋋ ,u&6P_odW^kOghv|tw׋΋sTg_big]ah}A&DaVZ`ϋŋASK>](3ދ狋:}6& ,@}WWCC|  &6 ,uWWCC|   ?<<$󋋋$;>ڋnWWCC|  s}zcL߬ʋċSˁ8i^MQehWWCC|  hpmkhooibgpmkhooiWWCC|   OYȽNjȋZNNZYN:mrrmmrrmn+WWCC|  :G@# C狋^Y鋋Y鋋F*΋Ȫ9NK8b/}jodW^kOghv|twËD"-GU96X`G28aMI 3}6& ,A@:GCe&6 ,u:GC?<<$󋋋$;>ڋ:GCwhpmkhooibgpmkhooi/M:GC}& ,6w}CC} ,u&6y}CCw}$󋋋$;>ڋ?<<}CChpmkhooibgpmkhooi9CCCw4"]]4#wCӋ%%%=@?FX{Qehs}zcL߬ʋċSˁ8i^M{CwwCww}& ,6X97UU96XW96UU97W! } ,u&6X97UU96XW96UU97W! }$󋋋$;>ڋ?<</X97UU96XW96UU97W! l{Qehs}zcL߬ʋċSˁ8i^MIX97UU96XW96UU97W! hpmkhooibgpmkhooi$X97UU96XW96UU97W! wa>>AՋ؋A1K7/U97WCGRc\OfU96XӋuf3$TWbhb` ~ylu~ }& ,6EG9݋GF} ,u&6EG9݋GFq}$󋋋$;>ڋ?<</EG9݋GFhpmkhooibgpmkhooi$EG9݋GFB} ,u&6}A'QˋTˋ"A"ҋCC',-'B |4ƷP64wҋ;EgTNjeL@0>>΋[HVȋaLM_dP6& ,0]4ˋ ɋe[JqO8̋yj-Z\z_uaԯϝ̋jXƋʩŋe`]NZiZZlŋEU&6 ,u!D4ˋ ɋe[JqO8̋yj-Z\z_uaԯϝ̋jXƋʩŋe`]NZiZZlŋ"?<<$󋋋$;>ڋ4ˋ ɋe[JqO8̋yj-Z\z_uaԯϝ̋jXƋʩŋe`]NZiZZlŋ} s}zcL߬ʋċSˁ8i^MQeh'4ˋ ɋe[JqO8̋yj-Z\z_uaԯϝ̋jXƋʩŋe`]NZiZZlŋWhpmkhooibgpmkhooiP4ˋ ɋe[JqO8̋yj-Z\z_uaԯϝ̋jXƋʩŋe`]NZiZZlŋOYȽNjȋZNNZYN:mrrmmrrmp4ˋ ɋe[JqO8̋yj-Z\z_uaԯϝ̋jXƋʩŋe`]NZiZZlŋH+ġ;^[1k@8@YaQAf18ˋyj-Z\z_uaԯǝ̋ދkVЫً4 SDYՋGT`yG}_B!ԋļe`]NZiZZl͋^\PkE|jodW^kOghv|twË"'+ًjX9Co`_6D?10?D6& ,ڋġ;^[1k@(3/'4 SDYՋGT`yG}_B[hpmkhooibgpmkhooi*ġ;^[1k@(3/'4 SDYՋGT`yG}_B& ,6w ,u&6yq$󋋋$;>ڋ?<<hpmkhooibgpmkhooi9 - )> /0-$΋n\p_UZlԋҫsrryzz赋BPp/ߋʋR26ON41L s}zcL߬ʋċSˁ8i^MQeh'8 ݋ ŋ^KKV`TۋFǬϋ & ,6*''**''*׋C65C?10? q ,u&6*''**''*׋C65C?10? 6$󋋋$;>ڋ?<</*''**''*׋C65C?10? Qehs}zcL߬ʋċSˁ8i^MI*''**''*׋C65C?10? jhpmkhooibgpmkhooi$*''**''*׋C65C?10?Nbkjfckje4o#o[bkjfckje J[LG'*VY`qm(ً^oˋ'*{pG<Ghjpbtqp5C?12;׋za}6& ,QЋk[OjG<ދ ݋ ŋEYˋˋۋIU&6 ,uwPЋk[OjG<ދ ݋ ŋEYˋˋۋ"?<<$󋋋$;>ڋЋk[OjG<ދ ݋ ŋEYˋˋۋ[hpmkhooibgpmkhooiDЋk[OjG<ދ ݋ ŋEYˋˋۋ` ,u&6kd`l㋋o+"od)O^= 23'>Mbɮ؋jIыM45OF**EYhpmkhooibgpmkhooi{d`l㋋o+"od)O^=@⋋֋⋋WWCC|  e)@⋋֋⋋A4ˋ ɋe[JqO8̋yj-Z\z_uaԯϝ̋jXƋʩŋe`]NZiZZlŋ``SuRRT_ŋp{QWWCC|  Z``SuRRT_ŋp{Q6 4ˋ ɋe[JqO8̋yj-Z\z_uaԯϝ̋jXƋʩŋe`]NZiZZlŋ<LtxmhJ^lWWCCnvwnnz  <LtxmhJ^ɋe[JqO8̋yj-Z\z_uaԯϝ̋4ˋnvwnnz0Ƌʩŋe`]NZiZZlŋF} ,u&6 X97UU96X`G28aMI ΋Ȫ9IF/`&] ,u&6)''+ًjX9Co`_6D?10?DYVFi9F}$$ڋ=ًۋ"$#X97UU96X`G28aMI ΋Ȫ9IF/`&$$ڋ=ًۋ"$#)''+ًjX9Co`_6D?10?DYVFi9}$$ڋ=ًۋ"$#R}Cw4"]]4#w%%%a `IOۋbQMi?'32׋iQgXatcUQfjp#ыO54MF**ECw4"]]4#wCӋ%%%=@?F8IMՋۋbQMi?'32׋iQ 2͋2ËSɋыO54MF**E@⋋֋⋋:GCi)@⋋֋⋋5ġ;^[1k@(3/'4 SDYՋGT`yG}_BhpmkhooiM:GChpmkhooiġ;^[1k@(3/'4 SDYՋGT`yG}_B<LtxmhJ^鋋C:GGnvwnnzrpdu`}~nzLtxmhJ^{(3/'4 SDYՋġ;hGT`yG}_B}#$$ڋ=ًۋ"$b@:GC#$$ڋ=ًۋ"$]sġ;^[1k@(3/'4 SDYՋGT`yG}_B{``SuRRT_ŋp{QDIF `'Y:7UU:6YaF28`AHÝ?}``SuRRT_ŋp{Qfb-229X䋋jvȋ˼΋bTMjA($$ԋiT؋" ދƋP89PP98P݋{IF `'Y:7UU:6YaF28`AHÝ?gXatcUQfjpdpost{qqoijoniDb-229X䋋jvȋ˼΋bTMjA($$ԋiT؋" ދƋP89PP98P݋k֋⋋@⋋CCe֋⋋@⋋v<LtxmhJ^CCnvwnnzhpmkhooi/LtxmhJ^OnvwnnzhpmkhooiMCC΋BڋC䋋䋋-Ë.w.'XfjpgXatcUQi{b"&bB>O?t$gXatcUQfjp&6 ,uqCы ,u&6yOOjpgXatcUQfaqCы΋OOlfjpgXatcUQ{q=dٲًʋM\d:bˋ?eױ拋ܴ} ,u&6f}CwwCwwXU&6 ,u$D8 ݋ ŋ^KKV`TۋFǬϋۋCwwCwwfjpgXatcUQ8 ݋ ŋ^KKV`TۋFǬϋkgXatcUQfjp}$$ڋ=ًۋ"$#}CwwCww#$$ڋ=ًۋ"$w]8 ݋ ŋ^KKV`TۋFǬϋ֋⋋@⋋;X97UU96XW96UU97W!  *֋⋋@⋋;*''**''*׋C65C?10?} ,u&6s +u&6QX97UU96XW96UU97W!  1 ,u&6s +u&6L*''**''*׋C65C?10?q:GۋX9%UU90Xۋ_͋_I #/}ġ;^[1k@4>YXL=c5*''*cNֱ4 SDYՋ@׋C65C?10??GT`yG}_B1l&6 ,u5"6{{C'-#H;(l@Iv4ƶP64$U&6 ,u>ζ#AI)'1a@"6{{C'-#H;(lv4ƶP64 gXatcUQfjp$T5ζ#AI)'}ogXatcUQfjp1}#$$ڋ=ًۋ"$}"6{{C'-#H;(l@Iv4ƶP64$#$$ڋ=ًۋ"$42ζ#AI)'e} ,u&6 '?䋋RgZ"( %fPG/OFGF\a[Swr#k]-48. ,u&6<6Mgs̋^PDҋrfX2ZV^`jznjtsm(9K [ 67)|iodW^kOghv|twË(6H䋋RgZ"( %fPG/OFGF\a[Swr#k]a+0EM"|jodW^kOghv|tw‹HGWgs̋^PDҋrfX2ZV^`jznjtsm}$$ڋ=ًۋ"$# '?䋋RgZ"( %fPG/OFGF\a[Swr#k]-48Z$$ڋ=ًۋ"$#<6Mgs̋^PDҋrfX2ZV^`jznjtsm(9K px_odW^kOghv|tw̋mыpȋpы_odW^kOghv|tw‹E\ċ@֋" " & hr9xzqq{f}$$ڋ=ًۋ"$#}ыpȋpыjOgXatcUQfjpX9nra{d5Oɋ@֋" " & hre֋⋋@⋋;EG9݋GFi)@⋋֋⋋W$Ћk[OjG<ދ ݋ ŋEYˋˋۋ OYȽNjȋZNNZYNrmmrrmmrEG9݋GFOYȽNjȋZNNZYN:mrrmmrrm|Ћk[OjG<ދ ݋ ŋEYˋˋۋa} ,u&6s +u&6WEG9݋GFU&6 ,u6 +u&qۋЋk[OjG<ދ ݋ ŋEYˋˋCGE~|nzLtxmhJ^0,9݋<LtxmhJ^|Ћk[OjG<ދ ݋ ŋEYˋˋۋnvwnnz}$󋋋$;>ڋ?<<"}nC'76'mC@A$󋋋$;>ڋ?<<IC񋋋BBQ}$󋋋$;>ڋ?<<}A'QˋTˋ"A%$󋋋$;>ڋ?<<d`l㋋o+"od)O^=Bhpmkhooibgpmkhooi;A'QˋTˋ"A!~} ,u&6K}ꋋr,rB ,u&6䋋ՋA2Ջ I!hpmkhooiꋋr,rhpmkhooit䋋ՋA2Ջ I!}$$ڋ=ًۋ"$#w}ꋋr,rn$$ڋ=ًۋ"$#@䋋ՋA2Ջ IFjct苋1NL3J$󋋋$;>ڋ?<<J:$$ڋ=ًۋ"$#JoRT_ŋp{Q``SuRhpmkhooiAOYȽNjȋZNNZYNrmmrrmmrAAJ^pnvwnnzLtxmhRQehs}zcL߬ʋċSˁ8i^M ,u&6s +u&6fjpgXatcUQ}& ,6}nC'76'mC@A& ,6HC񋋋B} ,u&6}nC'76'mC@A  ,u&6FC񋋋BIhpmkhooibgpmkhooi$nC'76'mC@AhpmkhooibgpmkhooiC񋋋B:GCmkhooihpġ;^[1k@(3/'4 SDYՋGT`yG}_Bmkhooihps}zcL߬ʋċSˁ8i^MQehv:GC s}zcL߬ʋċSˁ8i^MQehġ;^[1k@(3/'4 SDYՋGT`yG}_BB}& ,6K}A'QˋTˋ"A& ,6d`l㋋o+"od)O^=Ѽro"or".Bgmmwkkg`ahg`Xj$[ai]LUnWHC.Bgmmwkkg`ahg`Xjgmmwkkg`ahg`Xj$[ai]LUnWHC[ai]LUnWHC!]igb_hga]igb_hga]igb_hgad'H؋=+(F@(<oC狋oC/8h]\icORb*B)F׋<,(F?((G׋<,(E?)h\\jbRPbh\[jbRPbF*΋Ȫ9IF/`&3\$;ҋ͋Hҋ.`G28aMI5?YbBD|{{D:Ro#o'r7wwWo[Wc|Zj|NWNp}r<_**r|7{{PeC"Rr^2^y^PG1W$'"eC$}}}}*******YCCCCCC"RRRRv^yyyyyWy^^^^WWCCC""}R}R}Q}R2<<eR^^^*y*yrrr||^^^^7GWZDE7G7G7G}Q}RW=n  Wmozilla-vpn-client-2.2.0/src/ui/resources/fonts/Metropolis.otf000066400000000000000000000551241404202232700244600ustar00rootroot00000000000000OTTO @CFF ނHAGDEFh-Q,*GPOS]XQXGSUBZH OS/2gi`cmapǖ lhead 6hheat$hmtxw= maxp,Pname׎;@)post P,ÞO_<Qa]           ^4444^ *^ *sOsOsOsOsOsOsOsOsOsOsOaO444OOOH+\\+D+D+D_%V^^^^^^'4'4'4'4'4'4'4'4'44OO'4OOOOe.e.e.e.v-v-v-VVVVVVVVV 9%9%9%9%9%&::::=2=2=2=2=2=2=2=2=2=22~S0000~6s<~66S0S0S0S0S0S0S0S0S0S0S0_%|4|4|4PM?KKKKLLL3%LPMPMPMPMPMw0w0w0w0w0w0w0w0w00~S~S~6SSSS----@Tr%r%r%PLPLPLPLPLPLPLPLPL3A#A#A#A#A#' QQQQQ6666~6~6~6~6~6~6~6~6~6~6;aM1J(o(W8o<X;^4o<;IMISR'I,GGMP+.nQn1e>e.r8A8s8GMGM"0e.k7QCQCRQCQCJLJ7>//q,)m6QF???8????F?????L?A~SS3Qa+JXKX^6UKWN@ "3  " ",3 "<P   + D  M a :o $ x $ 2E w 4 VCopyright 2016 by Chris Simpson.MetropolisRegular1.000;UKWN;Metropolis-RegularVersion 1.000;PS 001.000;hotconv 1.0.88;makeotf.lib2.5.64775Metropolis-RegularVictory One Media Pty LtdChris Simpsonhttp://victoryonemedia.comhttps://github.com/chrismsimpson/MetropolisCopyright 2016 by Chris Simpson.MetropolisRegular1.000;UKWN;Metropolis-RegularMetropolis-RegularVersion 1.000;PS 001.000;hotconv 1.0.88;makeotf.lib2.5.64775Victory One Media Pty LtdChris Simpsonhttp://victoryonemedia.comhttps://github.com/chrismsimpson/Metropolis    !$%-.045:DFGKOR[\abg$kvw{ 9=Ulpnotsz  " A u BCe &'(*;<>STV!)&#( %'pP@/~#+17:>HM[ek~7&    & 0 " 0 "*.69=ALP^jn7&    & 0 "hzwiha_Pn &(*,   !$%-.045:DFGKOR[\abg$kvw{    *&'(9>;<B=AVSTUcEplntosuz|qmrxy}~"#+,)/123687@?CHJILNMQPXZWY^dehji!#(')%`]_fMetropolis-Regular)  RY 8j!'-3=DKRX_ku%06<EKQ[ahou{ &28>DKQ^ejpw%.6\nAbreveAmacronCacuteCcaronAogonekDcaronDcroatEcaronEdotaccentuni1EBCEogonekuni1EB8GbreveEmacronGcommaaccentIdotaccentKcommaaccentIogonekImacronLacuteLcaronNcaronNacuteNcommaaccentOhungarumlautOmacronRcommaaccentRacuteRcaronSacuteScedillauni0162TcaronUhungarumlautUmacronUogonekUringYcircumflexWcircumflexWacuteWgraveWdieresisZacuteYgraveZdotaccentabreveaogonekamacroncacuteccarondcroatdcaronecaronedotaccentuni1EB9emacroneogonekuni1EBDgcommaaccentgbreveuni0237iogonekimacronkcommaaccentlcaronlacutenacutencaronncommaaccentohungarumlautomacronscedillarcommaaccentrcaronracutesacuteuni0163tcaronuhungarumlautumacronuringwacuteuogonekwcircumflexycircumflexygravewgravewdieresiszacuteacircumflex.altabreve.alta.altzdotaccentaacute.altadieresis.altamacron.altagrave.altaogonek.altaring.altatilde.altEurouni0326descenderascenderCopyright \(c\) 2016 by Chris Simpson.Metropolis Regular"=Liy 'cw+OZk !+05ALSk0:MV_fr'05:@GLQX\nt{ #(,4<AVZbhnr|  &18DJNT_dkquy( ) k / /l  &T  dI8%23ʹϲ S=4 B <N;ExVPS/F$ F!io]c o E.3 qwuI ! Ҍ:?BIB8umKhwujuvyyK"CBN4?V@ 8γ© |xuv{nutwxa{¶ * (- +3 .nc GjTL?%)5 `k onBMUQ!+DQZN ªו][{wqbnP 4 -2GbFU67R E .5.% snlsromr 58B jYpY8dXArKDi  5%E - VVYFi@Di3 1 2 v ͩWdh[sWE tfZJE6 M'% +`+cI9(&$$%ͱʳ7&EP KMcjPcn) b ; * ;I2 0 R66U sRz= hYKsd #~'5!5J!55;:' %'  9 1 x43# @\ < B& {vfv ]  73A+2,$Dm 11B; TSC3 < O <%O < BS̷ vc v0w -sq* *L 6  p rv { ] " a |.h@ 34 @iVK wySZ]kGRPPdT %' .( %D 2%g ~_ <5v U//?U + Q  ˚ vb u ץ =R z ~U T 1 |vCw # qwvs ,A_Fl M@ @v @ BHcTm " 8v  : tvwUU b0&C    TH w nsrmorsl T    Tӧ v =CY¯ {A{ jCITa e 0e S إ V A#     ơ __ zq   kT _vl  x VAAOV6   ^GG O@ `  .w   v A  s M 3dv  > v "#$%&'()*+,-./0123456789:;BCDEFGHIJKLMNOPQRSTUVWXYZ[ = y` {h@\^<> oiwAabd _z!]?}~|,&Pl 3p2>Pw'2BZr}<m 5AOa+Au!,Fz  , i  ; B N b ~  6 @ Q _ z  I`x-Nx)4G} 2u}  ,:N/TkC 'EP} 7Ua  ,;`u,a{%=Ri0<,@R   7 : !=!!"+"N"^""""##2#`#$]$% %2%I%O%i%p%%%%%%%%& &)&3&D(aЬǬáլ.|>9jIfj:ͰJEi:ijgNjIE'ͰI:O'::;jtiA:j'\SjkE\j:ѺEN  }~N R ̵ > M R   L~M R  *9;P,~f~N R µ ǖ M A_,y2 .zzspQ PR  P -;PR 3; ;P ޕp] C00= qpW۬ =pm]KKU]?my (< L v< n /<ĿVVYFi@Di3,FI#/CyGܳSgI ҹSo*gI 8 R V ^~_ <5vV ^ LV ^ڥ z**.7 ^ |.8 v@ 5 ^ 3; Z8 dv͕ C vbNb> .9A NP? Ҡvw)C)=C)C   L  zZ  sٖ* 6 |d x f ǔ 0Cw`,yC=:v{|ol   c=c*QG:LKpRSH\ {  Cw>{ Vd & 4.[ww p|qtqt جbw ZD=8`@޶Yb ss ss=K L K ڡ o0K  Cwh5K Vd  Uq }K ~ (!~ L 0!  Z !  *+!~ x<!~ }\I! — /!w '6lXQQC>F.5K3&_qk fPF wi3aqQT^3YJEl  hq K!ceR64UU6!R>3(.-(]  \AAW l] [\ ;{l  / تxE>Ȩ/LvPi\YQ.f]RuNg. Z . vչ ~_ <5v. A55g. 6'? р4 pL s4hҹ o,4h[O -2NdG(yٮyn ٮyهoH = & L b&  Z &  *+& x& }}\I& • n /& avGw C4 )n~zvsrޞS %@{ pC6CCG L G r \*Z ~lZJ r # ^Q*nZJ x(G  'C,kk+kkAL 9Ae]Z AeVݔ*AxA V L V n V |V 0 1f \0 R̵w K> - #'R a\JR }Y) $G#|' } x\0 R¤ T6JJ@\ z,ާF! &T Eޏ Z]c o Rǟ Ο@e @R]b@r#@@'Rœņw x3; #u'DhK˪ >9AcK\mM, &#NݼѶA^]+ 5,4W̷~e:BG5X>w  @|On!?!lU vp?! j /i?!p^̏ƨWdh[sWE tfZJEysR .޲ncX>w tO$|*O' v ҫp(_tsq?lyww0cMYUQ`UE*#&,)(9N@#B7$C=&Xw t8$o*O@Xqwvs H o ' m ýYJ@K>Y>9$n*' ";f ,^  | ,^  a,^ د cQ% 1m$ l 0^ Q"VA  } ,^ £ ^)"V1\#]l %D(~}r.a3 ^ 3; "vVjw UذpihAWT>D:HVV r2Bܞ ̵wk> ߒ2Bߞ 9ۀwrkYK|ojco::2Bր怴ր St>wإ@OCv e @ U du[]n֘)  x Ra ×7QHyٰ@tnsid F 2 F F "^wց" w^ww? 9 9 9 9vw O@KU} s  A9a@?d@ײݵ =G`HvoN=GSiUqזѼh Ѽb ?v Ef \v S إ| \ޖCS 4ޖC? S ٮ إ3; ޖC " U bp" z I[i" ں p) +" x ^" I\Nn"  5 /)" zTT K6\mh`XTFJ[I>nzkp`H~vq|Q`fhmha]5.% 3; K" (`  /:Y=^[7-@ػ;Y]l V6S0qw ب @E 2%g q>w  @nqwt*$Eb&A  %24 f ]4 | j4 ~ ֊24ة ~? 8 ?U jp8, xlj #i8,$T$58B jaqɄGCWYEKUS?TDHD 54@!  Ηw NOqwvs H Ο U f v` ^f t FWɂsRwuLOu3f ;POu ץasPzu ֍ [) ;1 yu} POuR< %\"< #-x8@u ץV>zSiz,ܧ@-C܏ u ִP %~1 6Hpm7=A@A7 dH U dH\[C]lYK ^) nYK x dRH*{[S3'W(W3T\0b/b-H+jU J+3[!+, Z) +x + ,W ,LU dW ,yj @]W nQfWt: Qf EP: [ ̵w k> *$_*, [  a}PX [ ڽ yQ% ;*$^*, } #P: [  t>X i z,֧@*$5֏ [' [ P /~*$_*, [ œņw 3; p*$U*, 1; 'ed ';< 'de '< 5 :: ::6  HHCL=Q PInH0' >X:R^˺ĶZ7GcI*8jMGGK#=EҠɿ+ ?WFW\ƼWC=F_/ACb:=KV+7H^UXIWrv;w;$U1[JZMM#!$L\vrf{*oO34HI,?JVXRK^r[A:DdIa9͸maXO705aV+N#"~ +SwלοR-?N;$[eeAaNM! G8ҥʾ GҍӉ[8@>X,r[O#('=Ҳ͵94I^``NZj05aV@+N /B@#?zHW4QG` rmvgbd|gvmmir}}abaimg|}vzdzbgv}aba}AHA6'D 3 4v`C D \7\ wHU/O 9/HaVwvGbHwR * |NWb|N*  b b*b* !O=. LU 8&CZIVX߾QNJ8U  !J-a< r8:2%:ӼXU_Wb@7X ~A Hw 2Aғ 4v3p'D {C }A{H{A6S"" !: r#rY$ڢ .z\[# IqnCTҦpH!$Z/z P:/!ΦCq \.V yt<$LizYyriL#:ru y>s>oR>s>>>O 78 c@*A@+>k>+@A@*ca 87O ~DeeeLP(5i 7i LRC oC Py`(5i RyR`C !vvgJwwWnkeua'vlN`TOf=VJ r, qHɒçN T٤=PhwO 1m`UK:=N}:;'^ lFU]ZNReI$.ºϗqAbUUrmLQ\U0D1((ay '+W'Hxww}}~=H"!- nO ZO^^bQk4/1)MHOJgeJHwx!C`BHc;cHcxcccBTTU@UTATTTTT_ w _jWVVWT ND {%{ TO&_+M[[v[vEMMM[MM[vP_ 3`; ({ HcYS1 U[ HcYS1   }3\R߀>ϟy_BNz9P20MbAAaM0&B FUTcq3H~Iu_D  #Fñ߀Vj7U?7`wSRv`C?coG_ڮX;*O>-bv֣OǨJvRpWm^M[\ѶE/$CD1X]U!\XJ2 Aϰ|0bVRbm/gfzƶT=I˹֮QR6Z]VnOAAO2 ‹w`1oo5PU0"`d U z̵we@ 7j YH^Wj=w[ ) BQ ax  %$\h @AkyNnsidHcʟ:YӮ 3P;   @E@2m7 Ώ ֎ u $(17:IRWbhnw (8@EOZsz3INTZ`eqv ,4OW`gl+5:CGLSY} &,1JX]gly~"(,2@CGLPXdpv|%D$ + # % k'%6 6  (] T  %&C A C6%vr ! (.F l 0 b' 2  CJBFZ&OpO U46S5 #|'- C6C0 C. &4-4 7  @ C= 5 O < *$\*, Tl`? : > @  H wk\qpf{hhfp\\cw  +eŹ{jTIK$=Q-Afg ֜tektromrlzlpl 7 $5 -( hZJ s ts ySC|FAa .]^y.hRJ4JR3> >= ~9 ;P- P" Y b Hc˫ _ < $ %LثȰ 9 8 _ <5v *$^*, e R] ~< Xw jv $Tծ X K,,J=-)A S kp YM~fA u|zmkQ  tw  ~Aw  6 T    s qwvI C= տ@etpjvp{M Gb3 wk VVYFi@ X @#w   w )(  8IbGd @ 7 ]<ɯ Op w   mE  iv G " ,qx vOw Av  v  h Q  pkkp œ A  $\ HI^H "CA%  p  wt   w q (w гôXG  d w z  "  "JDFLTkernmarkmkmk" &$Vn(>* &,wwwwwb 0 F    "(..(4::4@F@LRRRRXRLR^djjpv||||  $**$0660<BBBBHB<BNTZZ`fllllrxxxyjyjRjjFWFjFjvjvjNjjjjnjjNNj,,j;;juujjOjjjPPjBBj%%((///EEqqIvr99<<}''cy&&t  $**06<BHNTmbf   ..02:>@BGJOQ [`#bf)uuwz"  !#%,!03)5C-FZ<\`QbuVwzj}}noz}!&()  ..02:>@BGJOQ[[\` bf   !#:>@COQ[[\`bfkuwz  uu[[\`bfku{{ }}   mozilla-vpn-client-2.2.0/src/ui/resources/logo-animated1.png000066400000000000000000000015551404202232700237660ustar00rootroot00000000000000PNG  IHDRPP`PLTE򌌍oopQQRǷCCC557SST'')&&'DDF``a```bbc/TtRNS P0q̏IDATXýr0 SBRmھ[^x!?):Y|;1D^V7~߈xT$DuV$R[$05]SZ5]h9 W0,O, U-WjoH|? ``/ңj"@P<> N6ӻ!# Px(fؖfz8YI̓E$P,,vt5WX( W' \`7^q4 Z_݉Q2&Gqץ/tmʥsd&=p淀R 3oIkUlM?<}kqS@̺0K?$?r-c҅zC]lVD:I/U6f=,U8뢏{Fc?. bD Ԭpr"{s@XӼiyӟՀy{Y7QW Eнluo_8lW[b3 k}\pS;6@Z4{. .EGLK=K=L>2M?M?vN?O?w&OI~\H?BԬґIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/logo-animated1.svg000066400000000000000000000021131404202232700237700ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/logo-animated2.png000066400000000000000000000015441404202232700237650ustar00rootroot00000000000000PNG  IHDRPP`PLTE򌌍oopQQRǸCCC557SST'')&&'DDF``a```bbc>1tRNS P0q̏IDATX͙r [,5^rk- eGO&3ߜ Z8J+ğI}8X& ^@E稣4"=i0%pcOXidJA[_MSEUC9[ɘ#CbCef껻̘<RW9@^,r/0JgOp:ACI *fzz8uOVkЪDxM# =@?@q6e GT{t85CaHpj &9# tVBI&y7Z@|w?|xj\Wdw7%/8/zqzݸo34Tl-64s.= W%>?*bX2[?&O8;;Gdz^t,О $B>)6tbiC$6K#ĶT/7W5^ l;Dw/xut^P[&5i(cB9{E,<՜z-\GlcnHVn$0h8G̰ND@:"8R@?hxt\|$X5K]oHmrz+q;(E7{eЅХ$x1-t/tA2x4|Q7|9|a<|M&A\/xԧ5SIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/logo-animated2.svg000066400000000000000000000020601404202232700237720ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/logo-animated3.png000066400000000000000000000015461404202232700237700ustar00rootroot00000000000000PNG  IHDRPP`PLTE򌌍oopQQRǸCCC557SST'')&&'DDF``a```bbcstRNS P0q̏IDATX͙钫 3f%fy( ^Y-~̙$W}ҹluOa]Ū[Wr3y)C><+ ^4kIW9Y4pDIH˂+]x"*ȿX&k}G(و~PW$>GCvj0'@@ , /HfO108N;G8wJN.`aJ!@/#;\1^~4'p=Ua@AS&310BX8zoaA}zP\Jܪ#:~t}񒸡T?m!l*)-'&qvPo&[ѷ<:ueμ ֹ,5B`Ty7ϣ1brX$dj pg,Ǜy2w_28y׳^pzq{(E75{e؅إ4z1-v/vA2z4~Q7~9~a<~^Fq!ԟMIDIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/logo-animated3.svg000066400000000000000000000020631404202232700237760ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/logo-animated4.png000066400000000000000000000015611404202232700237660ustar00rootroot00000000000000PNG  IHDRPP`PLTE򌌍oopQQRǷCCC557SST'')&&'DDF``a```bbc@UntRNS P0q̏IDATXýv Ĥ{̥} (.p:DΌll>|x[c)v39bމӫ=.+/o / ѥ>H:tZG K oP]I[>ܘHg7&~ԓ|?[W9k3`"e .r`/r[/0VO>b{6qm w8N@ VB@3i 8]%9,@+RZJM0ܲ n-Ѭ0f(դF@@dPC2gjE 5> !+:<UvZer`#_3O-JRz-uO{QWcX mozilla-vpn-client-2.2.0/src/ui/resources/logo-beta.ico000066400000000000000000000661471404202232700230340ustar00rootroot00000000000000 h6   !WF(    WR+SSTTTSTTUTSSQ'YS-TʽTTA;>'&& /..EDD#!!.--0..YXXkjj'&& .--vvvqpp(''3,4/( @   CNQFQfQmQmQmQmQmQmQmQmQmQmQmQmQmQmQmQmQmQmQePAKUc4PN R_SƼTTTTUUTTUUTTTTTTTTTTTTSPSEIPN STTTTTRHGPQHGMTMQMSULSTTTTTQvBG]SgTTTTTUE?UWu&Wu&=Ta+Ei/g.s3Vt&QTTTTTTPT_QTϽTTTTTUC?Tk/E]8=Qp2CTWt&E>8LAWn1UTTTTTTSGRXTTTTTTUC>Sg.G_:>Tu4DRSp%AOLf!BYGUTTTTTTSNCRTTTTTTUFBXTq%b+?>SSp%;q26HLg"Ho1Ur%STTTTTTTTOgRTTTTTTTSLKRRKJOMKJQQPTTTTTTTTTNpNPQQQQQQQQQR\]TQQV^[RQQQQQQQQQPKp9F#9G"9G":G#:G#;H#;H$;H$<<><`2oDH+dJ !HK.4Fu3ƀ9H a9^gZ.lM@ WtAdR:vdsi v%H !R~N3 U#gcA&l7zW!0;;d#`f`g &:]_1Ɩ ?!W ~l!ɕz"薣o\o+8dNO:lmcc!~JFe1˞:[Fu,ҼN[hB8 V@pfIS#U&t/w:+|#SRC}` vW0F%. @ z5/SaǦ AJ}$ :mC8 "mB]6"սIp,:EDKSȾ d w:S A@\!RJm.d=c1]ڈi (gEw6<$ ltcSS݋L?:O6,IDQp/WпE$A$0FSBer IRi18DENS#Y~f~UVJ)(yy0(.(,,NCVVL&!"ZZZP_߀*477ՂNq8x6|0!C4+vp9Gl6Rz"D WT)G% Fc)?~<ʐFAq\=N uuu8q>;w#hiic_ Њ{e6to::46_(؄TKT|h fϞE%\Bhej#@ހ\=\s V+?͛`ݺpavZ@9oX1@33FC8styn]y@tHRc2d,[vƌ^=i&\.vAR4_pt l2fk-.Ie }pE?)=I`6l2<ԓX& 2܊ɄcE(G]]jkMgGrU2BGg4d:$/y(KTutZj{G2?oS'3O!CȮ1qD,\:Ǐ@g K 2([F$B 9=!|Bxn;:<Ւ %_M>cF}j{V5o O[+&WG7c{a6=ZI|1 эpT>)G)cd Pt1ƲR-m G1 ~kvEQQ/_I&A)K52+0LcG&u+l/gpUKpw|/Q== A8րHZDMr_~<3G *y bѢ+q-ߋJoKUAQ-tJ%g W@UF;'OVB@_ QH"p"fX RYQV^P:Oe˖)bȐ!;N/ /yWAT z=n 0@N""鿮rMޕi(nfٳ!0|?QLX@$H vOkźuaۺEp`߾}ؿ?}~#''G6x"۷^-ح 6Ck3f`P"Vzwy>c.h:<%yh4Zs*b T@}Gcud1`00wق~$I[o;WSjoV_B8|qq2Euu\#F`Ҥoʪ|Ġ:@ 'R1BY7n܈_X,hD&Xk~^V,`ڴ8.dZJL99+&*?cǎ^ /ںd\o5ߎl SN^Kh 2u$з)$_NzDaČ19YY_J7| _}ϗ?>gIr9񷿽cǎ','TTT 7לpBmŎ> qRQfNCyy,}>{,>C, | KP^^|E%5,RDj6?JJo޼UUU][}^w=>B 6!aY1h PU4RrC~h2 *ǯjQXX(\.|p~5}8ùsHDPPPU|4p\$SJ" WG=oFA^9hnnƙ3g?ݳi6qӍ{)dPꌿ뗘5$*P_I"{^BAm6ۂooA$ {OSd$C 5 t:e"1[J@_j鯼7YYYNSFIl-QbF'TXI"5:P"%IBGGG£b61h HGB=^_աbx@SS#nw;i9I2iL \N46&'wT>2! 'mm] ~[)-I k4kOt:Q__/K.YYQ <:;;W;0 5.GR,urs88{,O>3f̈mo`!+˄o&SUQ]]<H~Ga9x, OJgfs.vRH7gl̟?? ~CGĠJ H8trÇEo} Mb$IXP|}ɖl$b)AA u&b ǾOF8ǏE*NxW\q9$I%يsrrbH){r9{9Ǧ=CAGGl"[OÒ% NRnGnQTᵵd%_x %H@I*q f5*w{1l%`ˇᥗ~YYYp]]eY@ARe9HIn>4lݻOQS5ԍ~b"M#ضm4hx 1|F.$RwIPVV_pUWUn|p8b:$   _8l6;>C\y0ɢtR\veعs'6oނ}Bu.0qD`̘>}:.\#FȪ^9r7o [6qUPHm֖'3ؼy MꥲDnn.-Z X,Aӡ ?FmmmWR+;ՂEl&`(bc7 9ػw" 00(--U>p^o#p WVZ2Rʡz26K^oA(px7qY![>y#WP0H"/^<CPؼy3Z=9?Ĥ (ێ NSzjzV%KgС}S߼&2WtP#o2A(9 + Y N vFr>5M T[t%W]֭?#NHª| *P#<,ᶄ\NP2o*?lC)V`VF#Rvvt:p8|sZ϶:VxϚ$O>O>sAD\'J^~ޟ ẜ$Q.ݵL R@N߿-E$ z`Ĉ5j1dlCvv&t---gpI;v <χW~mmmx7+BSS4O"ʹ+{N6zLF 400 58vX,tvv ոp, l6c]DH#^@5(F#`ȐKqe0uT!;;ۗ*ڴlhnnѣǰyflٲN+VMgѣo^' nM/%%F#FiӦb7n) zft8vߏ{#hnn(![ibtsM)^&.\҄θB`2`2PZZ+555ش |صk7l6z^K b'Ny$F5ܭǡ âEWbҤI(,,߄z˯K,jř3ga|8xGWuoidx_'1ol~Rz_ Mȶ'5A)o… QPP bO?šCp*z覦:L9sȒ7a˖/kap]>SV#ѸxTTG@cc#6oތ~;vn_OKئPo* A%X n4>y!4lXp 8p`zd6|ǷT uuuX|9V99?-[K/Dv 7o|{rӻ\+..wm|AYYYR充q?ǟ8pJRٖYJo/<,Y]tjã>?WR Fƍ1{e5jȚ.\#GaܹgΜ~ÿ^W\q~”)SIL-ohjj h~yqH}y̸{p]w"///U3jkkkq\΃g<9DYYY>ÇW`ĉ(//Gqq1F# 4-l łj=zǎǩSPSS #Gy7`sݸd+B(DQ_ ĖФ=Glx o;Cck:1*5k^W_Gy,NldeyxMnՊNX֮"(e>ȻUMD:rHfHCbX`>&L db8r(aÛv֬Y=2ۧ7o֭_ԩhhh"`0`\tEX`&M7ߌݻwU&dmzE̙1c>+{ٳ]㸀t1ϩW_}s]6+"())EpLԪzX뮕4kkkj{xU8y$\./W?NAA| .]'88.=z 7nf`0g}_:B`eI0nX1lݺ <<;<;˅'ObÆX,?~d2P[[m۶E#A^n1P,KhRҡ6mlE?/+&dq---o^Fuu5~_Pƍ3fʬOB&K/Tm[IG'C!srtC{{;eOz ׿BUT3gDa< |'x%(}ud-'`bժU ੧D~~~t:}())˯B!cbqdk}ݺϱ|6E{A)ÿ cРA7rHL0_|HP5Vɓe[kڵ >lgLwXR{(.~X\]6 ]6KM?>|O?t句T9CTk~A'>\nn.MM預Pu fYbVǹs<$q$7ė_nMY8đ#G{,:-+Wu&IG@PBCAAlc6l 2m4hjj믿͖aغu+֮4\Db %Պ^;eu̘NS%Ґ!C`4np`ժwadR~/*;ש>Ӊ>_DRy޽{k.Y,,&M CVMSNaCR[lNPYaǎyp lذa,J0tЀj #%sۚسgVk*=СChjJ|j,?>BUɢǎ,/ǃ8Jtvvb,N>UYR]_hnnJ=΍0Yr)8 %1&M1B:;mhiiM 455v<ˀV ֎[# Pi`Ϟr{Qcp]nMR\Ew(Q} 'rP0i NQL& f. 5--- AС%2c+#??/C2:G)CAAl.]@eɲ2vXtڨ,=zF5(YyӧOLԊĎ'&,PZZ){)Ŝ9sR5X} <_ϳ+,,ĕW^!N.\"`Ia@ݯpߛ8˖-Cq.+@>"o|)k!IXW]s謣UU PBx gΜɓ'[nqV(4i"&\a̘1뮻je#p>|8~ۡ$rΝbIN&do9r,<]7`*,,o}kYH&xA)믿_+c\ǖ,[YYײdK*++ /<+5@ŀv#G)4p`xz-fvÞ={}Ug`GGˇau|;w4-Ν; s2g)"t:-.r;J=N0kexqߔ}>ʕ+)+ Q!n,YD\& ^z fͺF6 nnJ)$_Al6cΜ wF!;˯]oNW:k.@}:̙Ʉ6+lN.n߳$ ϾK?O0~8ٕpw=8u*1ӂ{2+hl_yǶm۰tR9G SNE]{ÇQ[[^BcԩP,j>N$M`ׂcT' ַd{1a;w}:Cυ6 _|1Fh#G[|Yxg\.͛H>APVV2\}ՠr $[l]| K%I4y~WKJd,Y \.8u}DQʕ+wJ4r lڴ)iIS~ł7| V5(U:jÉcǎ'%=!:.ixYƷLK)|1k/+4<Ú5ky?8%Dd9SJ)z)4"t:Z*x;w-Ɗ'O N'8. >pYpJ). >%Ԅ?Ux=m(o{ [|jdAgg'^y8z(ARA=+?zbyf C郪*7 MH#sGeeeEJ1;XjU~e= dJޣL^_o+no[ݻj1`";XyǮ]⋿bQfd7_ x "oV1KŠ}W%I[o/fCb3"g "y/n 1cϞ=xɧP[[Eq"+~8%X>'Wz 6=-M/TA»+ I^}5wO+سg|!:t("='C2_F+s=I}_˗?ֵ/GO lr6p8͆'0))޽=08 K[A{Ogً@YB#)ߗR 6ٳgU+ rR (3#`СVHO?O=4N>?a7ׁ$QB"5h4ɁF#u“?PW#[}}{wx3ϢSHP%J)y]TUUG~3g&;boW^#"w$)j0hP1FÇc h }gp UUU8yN{j[kǔ)c FKK >s[صk$IZp=XmC._E n$I2aʔX*̛70  chllGgaÆ 8{,(e}Ov'~O#F w܁nyyy #VPJvSSSG]իW#''òe7aKPTT(랻qF^_}k L8yw'1'͛mV \I&a)2e JKKa6a2zQZ/ nڵki&3sܹs1j(Ĝ|ӳaECCۇ-[Ķm[QYyޗcNb e@p[<A`0QP0eee13כŶ?߆D\V}( d2cǎQQVVCKPPPNs˅7Ξ=cǎhllw6+R ,McBeeTwi'hA4f xg2Aܞ}/d{RY0yQVr٫ƍO?.,-ͣAMM-{1|G8 sy9uw%8kjj}m?sBgD"O*J +Pr݉,#HOp8wcرxp%3cj/U>VnoY1=UE| B&W"g2xv$?XVP句F?[Ў0O&&r#\8'7++ =Bjg߭:{A._>8pd$(EzL֟dlю]t"uCF}r?h_d?{l?lZнד/9nPH2@Qs)]{RGQ|tԄ}D~%06%|$=p7K/_P… x裏dG^rQZ@fP %'Nyo铁>1ի KFRG7Mγr3bk7\!M]w-F)_g=1>} p\Eqq1ƍr(lDĉa4vRjg@eP x&k]o߁իWc/ (x^NCvv6ƍEaҥ(--]%K`w7C:jD$=4o\% f'hmm39h; $7om;+q]w l 7\۷1D1 9IE3\PPفU$3`2xbٲPJn{o $ TYСCeYe;W.&t/u񚚚di:/_c<X+Iנ&(eMTonnӧU;dl68(K 痜j>/g?cEH$ ( ܊0)p{c ]ֆ:y~#`c t9QUY D/7:KEX;ru:ɽdeYH M$]&E;$Hq݅|;.Q61n7 8Ⱦl #JP^UDdʴL좖ĩwL0WMKoRw콪 !?(/Ɔ1NSHTjitУWs(ulj#VhF BӘCQ7`pX# S)|n*6k0ޤLLW!&ɘ>i2oD^W,@d$2A ~ dA?F2Ƞ#CdЏ! 2@cd 12A ~ dA?Fa  #ޡ됋"lJ>x#<Dq"Ӏ \qZpeX/.u=r!.3JA]byǁj&QP;v2AؗI`>F{mcˍ@!!cd-C2 <SS_GAdn@.F?mJz'~vy WQ~ ɀgpOvw,]=Kb&^s$dм90,J qs@ Z_Ƹv^E /Л<9-2ˊPq5,MlnCݧlj$8(\- Q}^9P:~tQiQqս_ 0 Ɍ,asW)dsh%ڏV_;07ESσDcM !qeIpZ!D&Jl7~{>,#2$a 1?(VHAWjYgJ8poւb%S(? gc[X""Ah?zޓ`6DĚ@R-(O| ?3ҁ/B# I݅V">@m ?q@\lwsM"t/a `ڜMǔ7h#~FlQ 0-88j[PzON6Rpko V=zD@в(ǫBv)kظw_ DʨBmN @檷t yNI^Ub/AزT 9\U5/Q& :!h :+3jh/1(.;^Q€1PN̕>qh $F)G` @; 'R dFY=Pэڵ;!v8z$8sh?|{O`Kp9x? ȲH04(Ia(8AV M~6NzUYKnDrF"ڗQ=ܻ.,!dwHD%k ULF=̓GPT? :Ԣy0B~MIڇ$,ַp-EDs4%D<\Mx.BE^Cɍsa2 h&.7ܖNs~|Wzz.q} V~p>x}rqAtM%MP {mKg1*JhX栽Ɛ7mFhd8 G`$&I.wUQtC/\p$ 8UwF3\Y;:0Oh?邽ի@J^n̓G gliXybN@B &C -ÿxSWM3`ΦP ly4٦<?}P򻯆x`y4m=#aCPpx vvPWV% BUImƐ8xx7ʄ3 5_Eﺝ|~q& (L؛3leM#~|.O8Õh?x6v aŅ[@r8PH6g#̞cŀ9`(|$8XWeQ n 20FZ9Oۄ\/u"um[H-9qN?OfyM]7G@>+aT!:Nנiۡ t p+cI! L 6"dsvNP1 u}{@ 0@MTt8sgvd!.M[z2Ҩ Q[ftN+B6@8U YӁcQ.:yQ'Kƀ4Qo[șs#AcBɲy!A6:קbigh ~{N-%ԯۃa-Ɯ~ <ិ]yɁ(fc4jt<4rד){ѧөpzXRtjPP8wRl9{OefX:N(tPv'V[0"wR&+8|o{ۡH8LIyC2$jwzw&<^g%/h]}RtMuSHv'NGċRSM MDZ]U',CR4&`;%9. ma/`4#3.( -l7(;,b}ۿ_hx;z F1MItmuW%IuSBo#I#F?m_{y M[#x8v < ;)1 `n@8lY/=gVp ʵ@ƨiH̢84qbypL~Gk鴉&4xdQ)#;~X\:EtEӖہ%t|y/~r7s'ĺEA͐| [uH0a!@yc9Fh54̛2]IL5:E߿{ph;x-:Mx]1r'# 4l*r_;0^;@yڇlB@ |sWh [,k^:J:*v:ja6ƒB=A3]}<['_LW+@VU/ʃ1mA.. ޠи`=9'ۼ0ǪvZmA6^1Di$WUm*8KG~e% 8Jqy _ME})8[NvNO8p~eteMi\f{Ar+: ٺ/7`hbLXEC4E-Ys'O >SS0 $K76?DŽ,;\#4aI -d9+`z2Ƞπ/ZTo&;o46UCcM0.ǀ~dd!D#TM 2PIbS-}luLN$/,0maE7b.mE AH:A2^Kk_hyu [V$@MmwǗ8A;sd/(:O+d]u gnɦ@ sѱ#{f>F w~]ljs6xr,| LtJM]w8b0 ; %ADvw}'m+w<{ i;eU~@&"`D7UO-8\>rvFSC/=ޟ[^rF(Ȳx!U}J-m^j gҿIiwpk2˂ ;[jֶ+Vt4ތ>!I=T~@Y88$;O f=tv&\p$K)92@`J[lןv ą0>  aU,=p[gӔ|3.%"ޫH:< p`&+/Z,BᏗeEJ)E 8wwX8^ro{ԡ*9n, C#]2Đ`x63Qjdvwn?zq¹{;`nIMkѭ)0#A Y!ZQ^Vjs9%_?s y\3h#: !6PF QbNw+l&(YlmbqXgq-n$ 3#:*7iAÔ= z$tN$I4ٹsGK&K^/h8w',.b-Z6βHgVqv 2ᑷť s񱫯No!gUUIk:sb5ӢL76 {/_x_I+?)5ņ'z箝`U{3Ό@{]ǗG~+זJH\ ~  "YLtYƘ3Xl84;_Nűj1Y&iÝ]]٣Fy\d!hhht=mgP++.A`nVg__599)\hlk?WeaVZhcԹo:6~aLlQ(ꫮ4H cJ t6ΞyuR鿥\\$FsP}M^iӮ>É(6J"@s\"+޸}|#I( ?6M5˹DŽiQpy+!ЂKjZ_*M% \/pekr٘RִҶYk$7M31v=Ǧeii#J[^=99)dUSፚh 6Ʃͼƿ*W"-@Wg]u[DKx%͢& Xlxy;7Vb\Z: f~lAWUqx55lXW\\ҊT-r>{_H:L]!`\w]L̍M{KK#Kv%J=19g(Y5 .N?O|,W<\R]CufA$cuDAAulDZtyK#ҩ tqBۛwjZ7S-$r^Xzw͜^NU=Ĺ}ȅs;5M $B b ŧfn6U5(pg#H+'Žnoue)q躭P?/B`m'=+y/Sj^`M+޾GH֍Gy.\*M~B5`5[$%rёyWaM׻YkA+a=$pXkXRfEkF'nAr5kɳ_ 1F2+_bmM_<6v[YP7wXM^ Y]9hOb=^5\Rr&s]n@-XUqoOzY7g_aMؒjbAXжg{NY5dd_@SHZA-hn4 _KɃK ,Ϗ\8wGX[cWV{U%.OrfS@jkA^^JBekH $-;]&K\9ơ͂8:܅UIev4 .TЯ3b]|4J |JUvֲi[ȖP I 9cbb\Z+1%Xφ{[ռ߀ɪK>k9O#~[4O3ܥ4yL4y;Nj{Yl6y1. 4'zlursW3mG/tuT"+ YD8ͬukiiagZmjkkcMMM^ۿ!688X__c{zko`eQxU?úp~ˆ*.p*+BZɉ_`}OMAϥj(xHȧ2Ʀkbsd%,+;[{2ڪhgg&!=xK[ p}P D>Jҭ5,PcЅ-d_QFy۹ kկ8 =Oi Ҁg{MjAMAPM7ٳKɥ}Гk y9gt:X!{;m +& 73S=E5޼n}pidÆl׮\Rs\HdKdMC119s~Ę(nMӚ%878ˋ-6 3Y 84oƥghƣt ̄XCi3"ִҼ_ڨ$yri?W]Kwء/nC҇*'6 +社d Fir.Ȃc_2slTgZԏ!Otcj7QY!(xk1hTce sɞ8k5GXq(҂)i9HT*L1=~3!+gqv4Ɯ!`tCY7-H@}1^7\>8'M4M[lƘs̐H޽vݣ]dC=w6E]&տ[؁ЮXSއ_K;F.nLȵ V¢8ǵ\ZB$ !.YKƝ$7$4J@l۶U_ K6FBj"*xGr;. 3mܙ@tgǣķL%D:N 5Ը,8Ij6,4\#"M(h҂%>W7vD:[΃vEӮ͊8 DcqAcgB OIh( >-"mT*]+IPS'K7SL5QY%lޱc'\{n=g,fΎH痉ɉ{ө 1kZ}bq\s/-O" 6@K0&)FSLn٩gn?X]C\ [7sogo;Vmv AVGGd&ieUwvv.vCVeزew([r{7fz!vя/3tm[".pu;ɹ@Ο8ǝWٴe\}!5lZ̼uln~ɓ'ukZiZev, \vr,2|U98^ m2MjoEr]SVrd/Zt"mt?hE:J;" TW^-33M}"q^Vyͬ1qZ״"1iEps3[OXJHmS9[;Y礠ZJEi1[\&qGFirsS4;'WAZ=7NV95|EJuII\%qHEM8Fc&Ֆ]DnYȵ[˒8' 2Z{aGk(ftAcύrr VGGGՀH'\ݹh\۲s4[7墦[qEn99q6EGa/s9Ɵt+z"?Α@vsepiWnrq-`;qf"%+H3!m͚OJu~>$K.L\Up[F5]"L===e׶(8Itҍ-vj4Bɉ@õuYlei)g;QnqcMddc tɾH՝9h\4Ko,ÇجSs8:YlznYhiiIt IΕ[W9f+ƚ#K.nY8|pY-16[ּl#GqY%^ES ҞGϡ;-k[槫~qNe%Wlh_ OTux.$m+>z\H) 5*3o1>Oy|ަKnm>hW)WV$7squHߚs$Q1X*2,XGYvji#8qm5I)6x\s5svEVtRdTeih`YX mVr$( t*4]μ.7Tg,Nwh3s2D˂%К09%=&yi3(pmWGUqalc[,?X9Mi_G;u$Qj]ܢXˍue$lAAP2&jX(*6ts=cE 6QD:yiKW`AǂJ2'WնdHeNpi$&2lEgDո)G|inn,i#*}̹U_l"Z*raL E/˓:5^ Ŋ \G KřlM*H%Kr _'>,Xyny}3dv:gvYӫ{2aAXԻKu~M!vn9 [x^N*LwIOn\mpCqVzNpIVc>DF49[;\@X{{Gʥ26YyFvrl"|X.]LZqhkٳg9q&~Hێ&@˟8R1xX<ޒf U>s|i}?CDv4%]; B/_x 'c;fP,Yš IDAT*ǏEf"-;֭~yN/g+_0hà-c mK%4X?%eڵkz4Egbr`D {6h RHo.@}Uz~KѣG,R$д+dXdME>lؼy$r@4daV V|,ZU[yg#GDV(!+Rm 8&-0S9ny{Ky{'iXܧ Xоd9ݞԈ9"mb4o=4nzr3n̟?:t&݇ߖi'u4@zFS^mB"eH]"s3a5N_V.Ц'$tۛpjE ͷk[Nqv-ۄ_:$1.Uߢ0RW#149cǎ@u?AVXχHZn99 d5|QbnW^y\\f~طog=UH=Ê4r\mgs8h_ JtMݹsQD:t옑؜9]w&stHNbQe%0:L XveK IA\\ NM+ZEzhShMHHsniwY,Znm~,|mcHl"@zzVOz4I"~8 (dEmƍ=BHÊ[;Mv޽{*PϖvU6hW`= {nO n}Tr<6OPn=I)HSQeCDw8$h} َ}!R?S. 7w a=~.!"?T ZJW♺S~}c>]lN3fc޳]}0 ) t2 ۅ qV w^loϹm*k _{[Y֨m7Sm>Y~ʖ; 7? 2qmCkɓTHbqm D:!ڪC|0msMQ6E:~VϊNY;W,7A_H"ȒuCG127nr%r^"Yv0fhG8<׃XϚ Ss7Fd?9l۶]_%sZ>.>I+R tn>AOjf_j L!۴*~7SBD\sn<_Cfw @b/uOލ]lYQe6uZB`3e~Yo29FTS#Plt?w W gjSXE{Ya9 o^t;v+XBI"7^35Xߘ7ZXޮ.kmfnammsXss3kmm2͞=xiށ600Xo1F#`5t|߼8e#˙^,8-N]sGߨNQ}t*wfݯYϪ [6Rfz+ΟZY{{;kmm ]&}^Pcׯ7<30^ʫi= qNt*Z8p@ab- 8bH{[|gokjjT{|U'nޡ!Lu:}}'muFkXدM b h8ϟϺ:YgggU8jmɒ!u~=7,U]峱u\eÆ$bmA<g9sXGG{U2lc_ lFu#GVNٽT^N@ 8CIPio9RiSQZtm\_^lRcr ]ҕ jIyd/?XlqVriSte+Sl`ԧD]C+F[`.-Z7M_g1/[˓%$' Sr7`rclN`D ^Յŋœ\/ۻҲܝʜS^֭iY@0[sàb%{rY3(kׄȲrLYsjzxERfVVC 0Ϟ=X-YDƣdm=#؛$Νg1ǚIyQ3Uw+^(nx2˕𲊇mPًaBm̰Q?vb`lF1Yt/ nׯc<0NNWٜZ#Ai[nYso%f-tm5 V*5k{?͛PNY9:/;tI6ά81_]Lo~]@#^ : q4Y ɂܲ}Tf͚[ 򆛻@;IFdƌ&k׎7"`g~=[=#VqKBbM 0j)([O9%|mdSKQtK0[4e^ Z"GF]AK7ʏ28/.\:XE{^w v{{% 3tA6SR*IkU=f!J~݌^Lqvtt45:e§ \y#Ns;I+[S%ܴ"_s|i Owz=~yvq!:#{A)!I b3elc(tw_'/̶ W:E9;yꩧ؆ @kKG,2Α~oِ)`45|nlPuM[;H͆°kC2yVlS,ouy<\>k{N6wn+۴6F;kN|Ypq'f8S?|WLiNi2?ͱn\dPފ;(m—8,!%O|\Cz*?g9:D$H͂=xjHH%8Źn{s" i1z涶Ĩ4.Ο&^/f\Iᘸs:yRe9Ɵ!j.ڢÈ=meme(ꭶƨVݏ_J1㞙O-H75,~ ѢN\1L3Xǜ! Fknf~.[>ⰠcJHDFkoPdfeMKG>s@iE:"вߎ56J@;6Y_Ze:E3$ˆ قbndIi'(W{ Lv6f/^M} }gJٳ͚5&bll;"p+V,ח&Z;VɈ[뮻"ؿ pu'Dn:nٳ1 ?a+&IYV,3H [~}""@1X߹s'zFj܇Q$ƣ)UvHS , ;ktƛR\k\NĞ=];2Ep؃n>Lqer[98hEˆچk[>\,Qq~$wBFQےCU縒?"[>%eUW}K@|5UP8]Ydo)0lODS<9U2#%?>=}踭RHQV/ᯃڵkS?#PK^C3' γ tHP,k֬.ӟq;mYϘ6ly>`EGCcN"=2՗q'N600on mGGUձvr,]<qv(OuyppPO}}}644Nss3kkkcsIeY۶mcN @= DmPlU7{[gN}TT%vu#Z^R(۷uww^! Gd4---zղeKN[n-g kHR4! o 4o`uT*YþNagjz]<;~5 ]WW5kT:ROR!1oՓF cl16+A?\ub{VI!Yz5kimx?;vkh0*Py/nSNG}~G-<;pgjmf\/\bU [޽'߯w.*s Xيv,fHTXVTcgSr9fNA74_?[9rD 5(zɵ ~܂4}tLa^h=Ջ0ɾ?~-ZtьUXjefE9 tw*wo 35f/_p<3p wߣe[D޺&^g5Lf=7nř)Obl%ɱ6'yo.fٲe%Ó@eiD jLlܸ ;z9݂wpnotg:PXp^^/s%-S;G1lͪO[}ھOsHLZ괨&S X!1#M;;;-#UfjLp="/pTǻO;c͋7̋4}6־f6{Z%kmpBzb}:E.wK'Fִ.e˖u:Nvіoo`'m={Vp4"P{oRFcZg)gikcgl6u3u Z@)B\W '7n,#ҏKS'|Y+ xmiȈ!Y!ԵqϾe{WZ`1C4H,&[ [ [g|M( tG[DO4tuuF#HT7ZzK,>>7D8kU,2ݒ 5Ԙ?%A(gKE˖ټONA/cXEMߋ}=Rz(ʑ#GY`DF0`AYUVnmw7sXQ7Zbq#KZi# dzPZ::[psCF:zQzTF@IZUrj!FeDid:4\d.ڄ>/?^fq Y--]ѓ]t M6^֭\u.[F4.mU;g8fyhʗ햴@ro_Aigdv!q6N}GOd&[n;*V4 }9(&7MJ.Rg~ã9қ6ݟRSҩOn3&|ǥ%A)gAuUdb֭+YllToG M>4Ju)-/cv}&}aA>~¤`,y@Ncy]F9i\VهƝ#崘 dO,! i@@&Ç+Yn8]qai?3(kO$&au="ZFY)`L&xo#ł++Cms=~ن~}m3KFoU"9 m\,hqFyt981 4V.'\YT?}F(wTIJD,h"N-_P!2p@'+ %K07d_Cv+ck4@q:V"9sZ :~ YʶVPhi/cEnoH4 Z #%`кƢ(z&פZ|䗮.-AYKv y?hzdqqrǯk=󛚚5( 455nnނ;bu&,蔪}}|} oL;,[M> ߂MTZ|Gޥvu-HH1cPhJ+ɂnmm0_+WL@I*7{ʽxbHsŭp1 ,ȶ~ҥKm~.jn=as ;;;m^ޚُ˺ʹbr鞤^pY%1Vc_gx/ӚK.)yY_|7yݗ& IDAT6lؠ 5/~&rcbǠ)IX!?֭V-ۧƞ6AbgO9N^.Ll>dΖ,Y"]&Ԑlٲ9*'&ZbtX :$+]mf͚-Mb?U͛Эdf]TMwvw)U,b :0JaJFu/LJ1lP/SHS zgc)'No򀴩r{ fBQ؃ݑ^Qǣ޿QqΛ ^q2*%$ٱct /hhɒ6$DJ>DQhY |v οj JBˆY5k`cŢ^:Ne7+ʢ5.q(rK> P'S&v2\4F=摄#x`y⻤qa>0@b>lT_9"m9D4%#i?"߃Ag}\VB]>iӺu,kȾ}l(/e2 {pJz\Bno0K#q ؈Lr+y'xBԖ٥CoVƪ6}ܮ3y4dw/b$^~%=`r{iJ}rί!m4&k1uH--F|]ʸܸ'O?M62ǹƧ6>%POŌsT[q|VhJ)>u*apq~;թN*YL4ll3Brcl1V:~ Oril޽ 45 6>)U,s;y"Ȗg$gLU5b|۷oWL~t=kHi]R={")Y4Th@D4Rr+`g۶B n ֭̉[rM ɥ@GoYZ %N8էr'= D k*)6ֳԹHW:WX4p*5*OP Yxsp"]=C|IvNmذ1fI'nAg7$o<db=ԩs0*[)8hNS®]ˑެuZqwWULO۴iS*jDqmoU(~m۶;U?!b.j՚Jm~f>D':q<guXj.!r;Zr+q,ćt͚O-4l$%qHgU[q~ *lݺMJ%a^$#Y`AGeYBMc3w}ON]hγڶâ'xdw>lN&kX@G+27m?CFnWvTO!yҥKr@#ANJnK ծh$,K.r.VzQ &Ҫ Cʕa;#gѶ6~d_OXZtܢi۴U@^Fϵm6$=* uB ;Sk ,ZiU]q]x{k<hHy+h%$ӵ8.Bœ)rSycy @IA<8L&IaI|!ـ([Ɲnm[~?."89'٘) q6$]]]qacfBjIX1o5Msr!bgӭRwyW~)lHjLj aa;v#Yy_pbEȤ~,qǜMW\,AuMTbܾ͘W@o9[smSwq'qcu1\*8eq XDdEkm0Zs6s:3Ƚ@Ǐy4UF0ݐSfS2 SYUh{ qjA3Ek8_dhIh&I"M)A|p [re#2vXq6_LYD~|ٳgy4CCö3&汌_:_/ {)Gk+wW_VpcRkZӔ^\.m\J`!:5L/1-Q ޽{ѣdž*s`y <':'8uh 0CiYɚrDwƩ9w0F cl16I"O0(nwgzsܞ:5[D9Gܹ*5T-8HC`)#~Nne.^|9l쥗6o~͚J$9N3ڪ@sGG2z%8IzJ}滹/aD.[nUe 8Sf17*&]p=^Sxa?d?;="A+V_|pEH| ]́GʜB'wz ֖E0Z^-U[nY&SBm E*DY+:+T[%0]63qٺuk٬YءCj}-xl!aZ-`ZFx3Ɯ 1h&84Kq,ǚ ,Ǩ,Y–.]5MƑHl^qP@f&X˗B%Cjiia7n0X6lXi1A(Ќg﵎/B+ӧ,]j@bL 3 0@H"5Lb޼yl˖UzV^=o2,UV(Qx.>|Xg 2?A(W9flVtohwvvN=D1OSv4Tay%}]{[|y ``C>2"+eP`e.Cdnl9·@i=3tcH9‹4ji=n;)=\.ʔn]ű/Ɯ3 uþ56o{z/ ?$ᜲg>cRŪ;2uww.$ʃ[/afLA*IU&vVP+onmZܹmutop(PGkzA<445̵pO2b]0}+} Mij`YEGwR!zC^^WQD٭NUCD*1A:LbyTV-Ua,eIʎ=Rٷ}="WT,-<wh@X$E)\PA5~sl]F [gt=uȨ?)ѴGCfioЃhHUD[[[ uvK۾z/2_g-Wyy yK3jwxf̫"t[g57qInNCRL51Gp`4a+wT(lǎ!L!얜t{ZŎSXDZ|8z?"of_)-l1BENnB寿~J^ .ϦS~-51$^b\Ǟ,e]3؅#s\{wWoYDK)M'M8&N݆\,Ďs=T>X !I?,Z ZH V,[%+ zk cfE}jLi::X5ë$ qՙu=`kT!Oaa*[^x v&,nր3’83Zݭ][Eڭn׹0 Y= 9^-wXG2"--e]w~hYD؊nz;Vy @͝] ǘ/Wev9snf7oVr߀xرQ2Zpo4D:)p_ٚ˛{;Yduo3X"HWX,X@nm7qNry @+x? Җ8+SռyEhmqGdG2-+F4溿=7ċ a*662,hX2Z,r9]۴d$BG&qV:ik1~c?> Ghms6P~Ҳ_'YD >@~0 '2[fH-2OC CjZϋ߅)U ~zj![N% <Y~VZg8{s=-tVWNjX˗$gxxZ\Hޕ]8'5H:f= ;CӊfuL#ca=\"DmR ,+tg!,ٵHX W;%x-xYm$,={S*;|V*p]q &MO[ɰ{0]i3_@<ȣRYjii]YŇ- Ww()cr점gsi󂜲6Wò[0J6_%ZZLGɄks= )=kB--r,szmъ66/͂ 6`j Vtu/WEMg @-z6ڍ2=zT*7u5 mU@mGSU nd{+Zmm黸ɽ=00%@2.c(L+^Ql}9QXr9 hXQ 72Dp ̄^fyilGrG<t$0z0"Ҍ%pC5V(GUG,<& sUyV*lmXQѨ)hƏ4,I:F{޳, J v=4n~]Ʋ`-֫3U%<WY,td4aV$Y8g!F,'=CCCOTiܓ/'<'KF\ܲEf U$jH[{d:c,Z.7:[b_F[?GصE}zg2(g'&^:h.?Xn=} r4$~"4+b2\ORŠ s]nӑGbA7j/j ui_`@P޷2\O,]Ws7)Ј$ H4 !h@B @ @$ H4 !h@B @ @$ H4 !h@B @ @$ H4 !h }4h@B3$Kc5\sܓ|2tl|b">y:kTR=욖w[l7.r`9!jYg/2< {4\=TK[@j8 '~14]7mesf[ꡧkSǨl\>q &d>W:yU{vbr'rAõWp\Uk 4+j<Q2>V 5h+BA>_3@זPkg^jW|YЧV A*شL_/9 ȿU"Obn]c{`0JРBAP.}ǯscfn Z#AƸ?)u:A.PdQL4k6 O$HN3Aw|{|sae=-54Ez|,ZTNYtr'~A.|<ĂY@QO$*Zm=!$gѭMIqhJyGzfbn?.ʠ/# )d.VABÂ<; rew9ai~]ܺHJQ'4},on`q&޿&@>}n:) oqfsqt_o܈; 7,@+/Hmb9Ih wT([vo&&;BTR^\zUiO./=YE?jlތ;DXQByl({6%v,Z58 #Z@_cfC.8 zRHPbwtŊ{9oHj?ehnoX5gVK]C!cow ElmHA3y[)MZ77pk{sfU.ҍӹ)sLPƅ|}/hO$`N4|0O,h ?w_iCӲ=!1 2-/hbn77ࡡhG]zo`琈HY/dICECRD@\~ͯyi@Wso4SwȂ)h Cs:uV40ֳ VıhZƞc)J%45hOZ+:ن? 'nagV@{L<ϖ/Cӥw{M˻q32=A>e|ca:8,h&DtO_lxL %$C,2G1':{L^?݂8;nYLbU+/g.HuiJ)5~I#BޝȹDfR'ւp'71Ʈ .٤.fΥ(`Oo5\wMD@Iڭ0+WTŞMSCYLiubebNC_@g7qӪDq=YnDW]Xu}'e>J`zGԽTu8ߜ76~iS qHƠ15 iha~L4=rZ!YǾw90l%)I$c&Qy&/o>ߎ8xAcp#fvPv0'Z}ȵ>MA#wmD&eD77mgw|c6>q)cP\ "hpXˋ DMilrٵ=&9=GE27Z;?\>1[ 18I2DiA&#/ "E92׶Imh:~+F)ք8gBFi<A;h?ceaa=Gf@{eaן~fŗ>&'_c; $+8u"4M T A<,g[u(g-Du'1Q^hP3;ZYGmrDlg{=ƅRAV}y#um6W2ǣ^|-れAV Y̿KIKuiS޿&XsFs!jp&;IF1̋((g+^_z>5fY\BbL)-qnĜ+z׀sӴ0˻g5HPuB:}Ϝ~Tb`Xm&:e.r` cl{O;@.?_bʯk$vfHB͋}{ӣSnh#B5<,evmZU)u//r&Fg@YpcώI\D,hfwun}o~膙# M׼7.x݁eǿwŌa$$"1 c~nEΜӣ]ãRuwkDř%iAAcEnY'g@s\DzXHPFt\6?J*qfi 4wn"mX[COYNCIH~~{?9Nm <\U҂txHc͡We&$$✦dh-Ҷ??X6g')B}+..miř$.p \}ӯ߻jK-Lvf"? 3g׹I"Hz߸o7Y-'$YҊ3SA{7/:U-w1id5_~epW87jT,Ev5h-NҭEyú ې_^_wke*qTh@Xwmҕ-J>xj_0,5,K[D9f 2 kw-oy' SN46qyb/Ufe\"J 4s@ O5\wͯ\l!xks 8gSΥ-@x4m7n_|~?(GTcWFzs7/Qǟ]]_E 仔D˷C.A7S QtʢC`aBC`ꮺ:;,>̮ ̗iO0[kGʹ]h=k9%o )f[/>sDyٻ>,VO}6fKwVFޑc7P1]c1AU 3މl͈:.}x'vVhWpyQ(5uMOcwWgb:ǔ&Ѵhwx U_qLm_Uɑ7ʇ`j?w߾}p̎y Lgb@tBmz:+✋^Q5PG?ۨil(Mf s7266:خҴڄj)%*G_g`dcAGkh1EٔיMUu:8-RYR#k=ex٧Q|+j̊@Suj* EIe5Qau'QD<&{|='n h6waVmUL 3 vD[.',e:OT"%۳{t#t?l%ܷ<5? 35 (SfUZ%5; R+ E[rs$#OQ#V##G `<0e!dDg~bm00D_xAF9hj"5;E[ΪB|Y> *ܬ|RYIy%-k"5'|eYgg9,.F&BOz:T<ٰb(kѵhztT,ϡD+> -K3m56z6f{W4}D lfk=Lu 51ZшX [ 6щX~ `DmC7 ` hnb${0#[ !hcI x!1@';> I B|uް~SIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/logo-dock.png000066400000000000000000001150661404202232700230460ustar00rootroot00000000000000PNG  IHDRP| pHYs  ~ IDATx UtgzKfP nK`$Aw tK@uwUv_I| /d!dtWtWשꪾVuzOg:=]y.9Y  AAQE߉X\xڀ߯Aqɭ* E/4ANWH+ M〉tAN^̞gp[ E^:`3A]B ^͞p` t.!/[ R1A'9tg v@HA6 w=go |ĉgIf&.DL~q!kC@/Dz9t(׃ oP]UTW 5ԇ X$?p 69$p¬e#K*1<.Zckd_}$@nea>8Arh ̅Wp(k&ۊU \ۙT]h bVDC,Vͻk:R../VL{[5-ˉDrS.iUA%isk})݊Qeob=21;;-μ%X{vr/'$Q|sH;•@;\*@XϩU \Z&mLP\9K.2C|ZY_R&kdY:Z\αtYΈt>7wUהLJIR"xj.Z* ( m1J{EU>V庸{bA{ LH._Ĺk R/!mV9lzf țtk\UUkYo=$$nQko?_;B-j+,uѹK Z,Q.^.kKd ^8%QtB+i7:R#xs)mX׮6KWx|b:ioN7w.gAW{NtaBǻ՟VC.JIFwN(g\zWh5wP۽.lw7& $1^C۔B:U3cɽm^K+ j>_0`zzR %asGg'tvvBKKycD"Q8}H #LJV[HܽBBg&{mg@T3u"_q&a֯^S)A^x>p9Ͱvݥ0fc)<(D"8q8~;ccsLMOr"1EIu~tPHrbiq]A|օgA7b b| xAIEh8WB׷.[6*~K۵v]j^᪫2ϡ`p}(9t H0[JU [kUP2.#:q-܅M>[?¬! Bow7$ZеL4B@kϭ =z?/ ^ XN$8:ݑPg%4Ax|qeMM݌A&wY5W--_JJ.$¬[&vVؼe# lf1 ڽ*,$`𥊲] `߰Mbv =! ™QPV.~RZַ{.HKuuh}F`КƭBй,h>LrI!Upn_FU\c-ZY93+¼k`dbnێǧ>i{B}c 2VWi(ˢv[@B]D-\ Zi:_Ŋ.Zisrɶ3v_/ wkjqA8~? xF]+VW] ejoւqd2‰s.!ZSAl~>?\vM?.mn`ad-+4J|ӪUsυ]~lyQ' oXr%tYQ$187YСBBg ,I,)%9Tsq3~/ sv\ʕгb_侧=c1' ?|l7=wCo X¬WKBIi34$՜]K AO3 h-% \n"^>TlN c5o[3O{>zu 0͛anSakJv{t$.ŝe}q~) p2kgftgd/v+{7 6PkEJŇD<$IjŞD1CJAIb V nn' K;3o%5pnZf5΃4׎@ښM>k@"]:Dq\.nB 5~^q8JIHś].k£$rAkcزzW`;o d"M5M"]:Kg;CK4pImWX 9 @Ogٟ9㷿U~]wu'tpZߏ[5M"]d.ŭg&rO* n_{7ZF&6X(,HKY˱ owK4t$ ] :i>L5ˬ$IzYHHE=87oĆ( <psVXqk/i"d_rdE^gw  ("t VpP ΅,@gY X2+·[Ĺx;)n7P8S25"lԪc_)g$.R+3 eVE'΅[nF/w{*e):ޝ$û1*5$R+6Φ y\2K\Qvm8;w[&x -pu.˛ &hb=ŭ QZbe=?35}f3(ҷ::  %E\)ކt~:q.:I,K* ggf5K M/R NIǒVpPH{D@ *]H Eautuv@oO|[>'C'rΌC/+ X~*'5Xۯm{@k^aXC\YxLI øsu(Inn 2vس;67[dIH]3˙=*s!eU5N}==,J΀w/4׳ɒўmQ)-& `(Y" dqNo`D֭[GIav:v l0~mz ]iC YOuЉ$d~SyAdpWYP0r2\`C\vHSz*+:>CpGzvV&|nXZmm^?u$+&._(AԸdrZYA+0ׯ^1SO$ (^( _ya߿p-߸'ʒDl\lMIi}1ҵ6q,Lz7|@ 10ahhs)AN BY}rvF[%~\| Nό07]-d"z=$)L˅i1nIkJ c@6g+9+7l21 #[oϰZfZ@-i彆Μy?#ػgW!?Wo+{{49v`5M +mD m9solb0Yݘ=JC$>hw.Εti+9[ڠ 1P*] +o/r 9 |fO {,5k&;Wխ`ͽN">uu:}qvef<{7ؕ-u wRȑ*-Ixb^!v:`ұgp`MCJ=K_ Hֳ7YoܦtAptn1  hڗHOPaLYϞ^Lnt=>}vZhw} [! LUlЎB(9_^i=Q084Gom,0ޝigXE7.nѭg7'B8/GVva:SSYHf-$$ /h[cc#WUni1 {\" \Ak4t*MsoXO!÷Ii,K ͽvݥl-8O:k݊< Ww$1_S8;CAWjrQ;:W c77>8~\pۘGU w=wY,\Y"D"Q'![P*JVxPM_BZtw^N'b, ʏNl2MłVъt\{iH{-rlĹ:qRKO},:gJ)PB"]Yi?$yLNq7[,jĭ]:S{buW7%قֳę yMʘsxHEI{KV(^MD:]At!qy6ıh $22ˌ?@CWeA4a5hwXϕMl-d?\%?!.]g &$N\ Q:UKO"]dE[Mg>㈚]Ko}N]mkY؄CN>ms+D}JVXgnɵqV>K! R]4E a%Υ_[\LOM Zl 4hES jr8gWR෯b9JC1>Ʋ,^]mҮ?www Qezz-J"]< 4Yg ~AR0 {4.cJimd6z&!w̵ޔŽ{ nBYdAH7Ias׸I/m*`0>-" 6NL$pؼeMK391I)۾;IcdE[Za^ge3 ~eY`6ox4& Em#n km?pƪřDr6KV4Y6׶rp%E`@@VS}[N#kڧJCC臭V"4KZ|빲WN 33K/vbXn\CK8h'Eoj=cyMu0mpī&ˁ,hpìKlfr/ֆbqMPW&0Z򠱞:;_9SiyR@VPz"joнEPpX77Zћy뙷puYwUqݳ(3D{ .o +TȂ7%vk"M!FaV(n9p3@8-,EgΊ%) BpHMV=LŶIQq63ð~;T uF$YB \.;1_"s[wZ/r[L"mHVt)]&z]DE#~*766\yeXGs3Xz6K̇DZ. ֳ9^a]ַء婸@pW(~(== EzkZ1wksvZ=w;qYL *,'5hTKoI] F/VD׶=\_kⓑhT,FPZ^Ւ2Kku Թ‰3rk^] .hOl枍Fg V@1_%twt@u( :0Zc?:ޮ.ذaGݏ!k@Vu]}-nU~{[!mĞ=徹-|3߸-k_ٛS)F!5fݲPN4?>{gA8p럋hA]&^^`퇷Zj--ӃCЃ;Š}O 0Wsu(pSBXhǬyU], `ʕp޹¿'%ݳj`5š]Y i6Y&nY*Ι҃y$Kn7}6JyE[ߠ0[ ݝя}M DZJeww74YEхnnˠr"ߑ{ dbѹ9h_-] +Ȳ%0`y iQ^ oyӛ?QxaZ{8R3wk ˧J,s3B?FX " 5޽?,s..-,b:i~cC47搒IzFޫ+M`rnz- R%wlo5~' zv8ˊRud,p{ᇏ䖃 eB SG!Vtqq 2XRhnG]fW 3{C@WKkgek/HMqR'@1Acn&!ߟM|u9D*39g ^!$B.AYd>k}N {Hc ,Bk[/Fܢ'yO< % 7]ە* &" _oGCPg91iwguF*&/3э⬯%F(Vbϼ\LHӕ[hDeUSyq.w˕6l q,;QݚBg7 tݺ}^ygK֚&qoRgwÖ+)$!`a{~*QH]&=8AKfmӢd1/ kɂ@ֳTRuI?wM[\Â\VB;JYA-޲+-(V$_ 8Pf5&~wo:%s$)̸?"Mch~DJbvoSo2Xq!qv 'vwyٞzU.6c⦙_~c=K#qv+wm̵-ɲFy׶}"m;jDF Wg8HHG~"ss0\۪{-ۦzK0&ER z8gk8 L 3g0kguPLDhBbЎ#BQPlǮslmoadJ$ZSP,Xh'ݵ٪6ɖ|{(jJm(ek{^UA6g(H.$&q;JD-ZB'5K?=oRLSê8/)L콎ՍY4^eݣ]r@ brX1m2]3!.w$) oQk 0! cΪ8CZH̩Hb9yڡ$/Zn)]5tz!@v#DY/7pW.edSCCfs6g}b8 %[{ttgm4.!'gDڸ_d7댭\-uIo->& 1EHՁXe]جP0d$[gIb|dY|dggPt.n)a'>u:3Kgʉd6ApOs ( ~"_'#vCbNBm& ǪP javf8Zڠ}=F෯~neX\Z&ܒ,FluAB0{Юqlm;N[.[ۨvq}"-,(I#I85 BU!.VbK <;;< &d&fzXrs#@˽]\twuCu(333p9+>X"ZgpA_9C r"Hx8%SO4$OɅ5^GpwXLv{{{ '.]:'<ٳ+ &X Ʉn Oӊv2&+I78,ДX(¬Z 0tu({ypƍl`Xn-mUSI 8>8߷ v"(FXӏn?[attF&&X6ZNUļX.juPE6}/.5LP6\u'^v$14m9#kd.D>w[hoa,CU V\y啰W8:p]=N1^jP#貾Io<{7/,.},Ms _aؤ76'%łB: Ç$!)0<2k~\"Anncyko(5-SßO$tz3N')755߰ >ec\8x.lz׋t9NH,/Nypǘ0MLՒ٢\2t;[ΚveK =mhQ=!7w@xެqHs%`žs7yev2<4g4f*_\ߢ3P,ƉK~F'+nT(IyJSm̙ o =>Nڵ jjjPŧ A Mg0|d>{ӍFVG8kR̺&hllpS\i|캏 'J=3wDPyϥS/OxNx:;nqo&J^&Y[d2C t<+8-֭c^7$U`]hZu[O.2W9"<^aAd.3OaO)d&==L&FOx1Tq׎;Zu5^.i AO_gƪ__Vؼe-%ra$u[- J`9GGGsӰg|,_r óZ|cǎAdvarj"ss0fl}l9e#ry ֬9vݞugN4K03,Eu4T˜%!VBoOܳskHVG,Y@~MH2x|/߾*|@cc+Ej5C) lq'N|dϰxDƒҊ27,ѝ4 H|RXps7 ؉Z;ya >QIhQJ # Y}}xۭ^q`SjMKR\vQxV7O̺ ){yv}yXmNØvUUjp#.˗}%S,TB.fn,s>vwCkMx `}o^x-JHLʔ'Zגp@93&Al$)3aAN[w?;sFM.QGĘLxM%b#Ԭ/EwSlX!fYqeh j6~+ ϟKj~f+H&p]w7 p; )hkFetn:V@`SHC馆h 7xBJi@;\RyYz&qlm#qNa$Υ0TqY΀X ',$VtQi"q̂223q -gǹŹ4 3y^i*Ƈ׶B_Rۮm ";يMT$Jmb]GKȎ9 Y,zq.E~JjVʥtW_M a6tcC=Wˊ(dA[Y wV܀"0)ӵzzzh)U@}u*;C< h?̌>&@*#nf=ʾmֹ9*=G֝ z>Y}+zUAv`h0:IՌlT^UTX[wR,Ϲ P2U*-H#ŝ6nmGbSNvabFٌ>K"mܧdV%싷~C`T9)e-.OKXoY> }p[ϣtlx5cHŝWO}AkDjY#vu=Q,20fI${>oI+P0D~rߐ(\&OViEX 賶׶7U8UJZζS?wuvP\"KTBmʺ,=YlC|rÉVG2\ȮT$FXmIkE-MG}J }ίʞn(RS~攫,2/%;pg2q3@✋Eڨ RW[K}J@}yo-`'*$K p]k׭seD.0q>=*듎:Z[`5P,`J1oR~K *w~ֈ7o͚7'lald9CAY!u0]٥j9 |pù_BLn] F 3?zYCCu,dzWF77ĹX9yծy^~]{a8'dkaXpu@=wofhYVRv_#THKF3ZgnOC^P[W㰸>QWWC\J IDAT<c1pea{b(7 0v=yǎ:74[y3pÇ8Z ?>}:UV"e^wWd 6og+"P:DžUp!h|"kZn?֜Wlm^"As8 cK 3=<{7D SSH&c~sFVg||tuP%V~uHJW9,&r ~Imm5?PD"AR*?BdUU ̭}>v\=kpaxl}VK0R9{+ֶ6hmY()y8򱗟=lA?p*V20@v=P(+1XC87 ,Cdnf'z zIo$P_u+)*$ιoEk'@@eU0{i֏Ƨűqz1#/zDjlpBtH.+pOhW|"vZRz{Ww#prp0c=1)ޡп6u c0:9 /" 7&>SS\ !eڣ/AG:)Y_'k69PwO~I|(SQϟFa36mbVW{XE렋(9Yc"W ӟ3ccpz RZ] p ,;21dq:Yn*=*K&v뇄'oC0;7DŽ/>۟ =^ ΋V׬@4* ^sjd6oxr=zΌO07~6o,f~PQ9=4Kg#rA]L@Z[[rocB۷Y,A$5lԳiA6M|e2c6FT>a8NvNb\Hʦe@OLOnPкu`a~^|Q;y' d1F\~PB sH`s,鉩)B *"&$΢Lv1H& OVdgԟ][[?k,'8K\A[{gg%R c#\vhO=RPpxhPu###0=;+@JdПF'aI U}J+ ֣cub7H\y a3Q mujpˢ6NLʝ982BMuЈ᫥e~!.sS8,w/,lP^ٙH#ccV=Q_ʗ5_Oڐ&̆ʊ5ԗBU# HPqsyAkkkQ{p d6`"hB$ȯ,#?c]|V4Q+/q.+$XHKwpZR!Ğ=PWWB_ߓo?Ȋ&DbhȺg[(|Y^x. s)TLݚg RӃt8gѻvt!УV(VUHi>oV٤f`f̨.nS鯟,袑YpYN< K˚NaoEխw)gV A8 hkoE/FfEW4bm5@[1 5/@Gz#"(>795vuq&/ a rm?FHS]]KEq 5YDC:W+3#X&,*6z?$'jS46/D;1{]f~ ԛ+ AGg_duy.8m]_жIuձ-1 )0\nb"pU%WdR /K+@&]h]fřG;yPťEM8ʉ5hvp~Oo(N--λh,QgfY< M5 ",Q, 5^nw GC !t,T.p)E,zwǓfrK;؟5W!*ENEoBcc#?7% ڥ`̬`ͳ;'EKK$Єsr8,'ϑ QACM8q EB$H ۡ!v)}}^ͤ TUU+%5P]U @~VG8fy666+% ergR(uPxHK({҄:胉QƆav# .Yfu2X^f3z`.MMXڵVSܛ}}lAp ۠9ܤ>5]Jj,2lKÜ1S @Kcǎ ВT5Z2k@+mZϸ^'! ] -ͺ~$W([ ,]p 3ÞroWZȂ.% koTB2 $N *~}醏o&; nqפ>ఱe? ܸ&fk$,m-K t\5mBdrCڊ񦛠K/`Hg%1g>]D!zW@oW-]n X(yhpۘ@ tQb18 ,}ۣLJt8 Ntdٹ" R=w{9uW^>mGKs%Z6=q446gEI$`(65=G_w'_2TfMYbt/%*LB zAhc˗YbcQPۻv *:0S 0Cxt?֙`ˉ$ 1W.lǣwAuU(]GðH[QKR-^ذaEb=048$T ny2swcSSLѺ;S w_&Ev|ohGfAK;o&1 )Y {NIu(_q'LLMqƊkBuo6I-EI\.t8= a&3:Ҟl(D!TY#EJX֮]+8nv6)})tsE(kN>̮r&ች{ԧ߂yzzXp8#J(?)8f67^r%wÙ+k.eKeag<'ƒ}AkSiYP Wjw Ago>, _.8=h/IHE*Rr=::{?"tњEK bdޣP0_vU\|1ӷ?|lV Ƚm%) *#5-SßO$w:MlhSFջ ~fpb@ ={#,٭)f;.tIq`@$lww7\id'z&&! qֳ5]hɏa(eURl6s Eᄃ^{ Z[[gg 6?x5Ck[+.΃k?t-siQm_xf# (Ss+Uњs6hmY(\9 ^~^@Zn}%67^Q脨|ьD1պXl.]DŽڍ]0ޫVHG$7$"#PwpGFN~_9q"Swvҳvݥ5kWoc FGuU`.Е :!д̪D5Q7X[ ]inq7ZZ[a:M${;&lU (䒫B@'oo !*竬rC10%]B\zƉsi8233gO;ϱat|\ʅ(2Q%'PU5鲌H\ۅN|ݐg/kguLs$֥KbM d\IYv(%)Fpa t'IbjX,_V?a_ gVAu 4TN{k;&&W_kǝ0ٺ"]zYKQ==L#W'OyO$\~N.2kV[NB ~{?̶ōCf@Ia3yt_?]fV$qS#gL$ QruT\+뮨xs 1Aİ/+$lwng7Sa]e%7qjU L|rʳ?$W| IdiEHyJg~Ŕ8w8/r : f)$Жܔ:7ߴ\nW^yY$P8>߾ʌDZ|+V, %Ս{2}+ gƲ6e"1"]8$.O>f{>3,hvug_7>K@'m lm# i4}g}a.6vmk9z STϫM@>5< Gla2|d!27Hf IlߪǜI n0s&e[8G,h-\$[3HU3>:$leDm6SB7)/2X^+rq[HK֭$63<I/F%fud7uqm$q&Nu&]Z0d̑Hۆ2$c`L{JD.MMp蝷# "Ox Y6oo1iAqƁdtr"0smW({:`aTcV_J*8>2ѹ0?ED1օHYud I@JRG{I~s8p|boTPq8fA}a$ҊE18OjY^Q5m.’FHKrڿLEIglmnm9V:{_@.cҴx0[=gIg9nmQpF`#A?}. ̈^t*I\ҵfm'9xgT)D&U~#-t,Ƭ!g>K`1X09N:C##Lsi;h$sQ!?_W~e9?!=(Jy^i3 T$nQd a%f2>hQL.N$%|pq=wӀfBXd.ʶLeɫ 8ƪPT^=&`` ---XdGb1s(zv݇䑵FsMV38~_iq hN@;;P:^SVLN_7nw/]Η ,ҏ26, !^`|$" 745u}}oz~E"QGXl~PSSM0ėall$˙AX}-h)'qv,S{vV! &RO&ZzwۡtH˲: SЙ5 om7@.޳e$J5;7&3̚$8L8/o1sh755A{[AsK \xᅰuL(I<>y 68_FvMqeϜKR-@$GLJ=`;dΌsœO+VğEhG@<.`R15Ѐ',>SEQc2A큝euJw[kf='b[F@VlL98ek9[&hnjh$Wn{[rXvѣp}`ffB06>/bzTvow7 @6&X}9@WN5yVQWpeţ0>`w(8F^~^@\i.S`5o7 .1&F7>ߥ0W΂QL7\um)y&ѹ9V7I/Q?g7o{BpœO9%Е5iIhpH! TWx| v|yvDqlҭP0 IDAT}O? ٮ7VRlaΞl!+:;:bVo,e7|&vÇ!!I09=^C t_ok!LfBD?-@nnFU4&z5ėP+޽P)` )oj'V]g\|?550>9>co/VՋq!' olaA75_`>%v@xެqPM EUj,-.Ƿm[6;٣T&yf~++scEGAf Ls03;BIp<OIYb A].$E[lEڑ$e9D-0;3♛? &S'O:L&&3Fr KiUal\5K=Mnl;3ʭy(x~tV vm{y0=QJȼg .Y|/lB^,kq^il޲ aă?=v8=4 1]_q<. 8ex!}D lz'쁇Ŋ,-/Y6[&WR \"@n0NB ;Z+{{ Bm]-\~,S-J2 d}(Aìlt.<{DB/`P[ nٔ(%.8bmzf7|ĤU0=5Y'Jr /-<}L?c[!@ynͳ5YpFy X0Z8Ȝs9pE1˺F fD^d" 5l|ITF6(̥]h$ngN<8w'&6)дYQQ*A%ȸT6Aca;CXG,!킷Acc#pxpQLB9y$S565RcAnF qRY1{ m?-pWj}[9{B(2HFB_>̖+=p0cqk/Xzܼ_aގ9tXkdق=P T :/a߱d7TٙcX1(5DϿx4gĥThc ӧO6^HoԠ`bBI]cih0k2DZ֌ovb-g5*8&B|*9C/Cmm T${>Oʮ^jxp%Lv[Z[ _V,ϙdH[}Kg0W4a,[NN;ѵQ]UF;des5e/>Lb<ܨnP(he4Pd?g0r_I O][;M|Oj_¢c b]lk4?M 泒xkIrҽ<6@T7P3277/Ϊ O[>3S\pՏ wPi$ΊaT*f3q]΂zy1)((_^1b}Mvoc#9$ЎQ͓=K[Nt&mshoތBDڨos_Oś]DvY5)X4 &J&dC#j-N} V.|.@_nn{14;o_ص1icY 6`&뀍/HUs)50{z7R ڄL0Lz.~/iYgV8\✪(P5T[_Zz&vnH}J4Ҭd [Nc6QQb$";[;[še+Oz~A-bm2úvU&젴WXoZw8"$s9*RKir qDL{76_5 3{%S\\z|"@B챢EkeHgglk9fQR*w/[;wBX*DX #y݋ PB𕹹Ӄ^]3*_ut}ۺA fb-셉@` `񰻀k&ceCmlX±`Q{bl5 Zj]]Gn̪2x/"2UVV~iD 50ߺW ʙ. (2i5"i~gImӟjmT=Ljo0'Mɶ{v8pέ-Wkg͚0G;A1rIosB*Ģ0Z0z^u0 ^N`J*_2;sNjmy 5&w}Iwۯ5|wZُjm :}#谧w7C$ݜI,IPVCzuD3F~{z0mb^xv6°n6rٳj#.sm=4!r|,hcM<ž={jc> Vi1D^93#p*hEdԼ0,Inɍ!ˣGaeuU}2c s.$E"xb( Ɯ, VLUej,jaԉ):3gg`mm ~3uݧs,ݷO[o |jZE])gǂn^tyT=Ϣ;"H6sl&#TL$LaLEo =?19 ?<OLL@Z5Hx4|ϩ~oT'F}yuefKKܮ7ہhRY;g$\wn1H w|?}jkRIAg=kPV෧Nѣ^yөdssv8'I=QttI_#P؀knN?LO5=7hڸI OU;.AX;ݱ!QŻw7OEq纀r t.WI\̣^Ro6_PёpS՚ :@wƕhI_"|/ۯԖ8gg@AhpۻvrWF˙TgBfeO !77{W9rKIcv pb $ѳ"h 6|N {֟oyqEd\.R"e:µ=VY.5V"Lxto83y]ug7DLoF gx$mޖE{p()妛aaiI)STNG3*z旗_z+MXBWsG=@(ڛ;;/2_ɪUo*'|3U,Wa%mMKIGtwD4`VbѨ!4eM8>{Q0 ?&X85r|_cRLLv&hIvנ(G)KwaFc<uyV 0zCmcpH9qfΞo|kpC߯rnb%icWc/jV+{OμD\E#hAInYմ(9CV&O:撮?V*on8Ǐw^( :vQêA@AE{ $m}_G5}+m=z![X0!$gAPP"ӶíCl?>k3`"MzFoY@"F]~Cg,$PU٭s2 ,}Ƕ!g/;6zex- 5 h#!'Z v>KK3;B(z->fxp"10Issg=qcs  4O%iv(, .ڸ{nUݔ6a* (ZWtI ,۶_;y:6-g$M5Ѓ4 RO*(/E.h =FNtRP$M"K.}|a:.g#*:uKW;:v!y#J; @62gf1[CVm졵r/mfj%/Qбs[笓`j=FNۉstcoAD`}Dhcc_4T>f~i%0yIKy<~"%i%*1Qw1"]JOvmkom_se *qn=Ɲ.jN'{P8VWlX+1z #c h$〯!1reu*.5׋ExbsȔd ,zkFNATSd 4LOO&#/nH9cjFπs aicU6"q̳HbD({z"X4r 0vt8CYC ʹ7&hEho:\. Z/& V9zGИ6s)Xdv29<0F<4z0c,ztC{Qj[H"q0> y ~ zdE.~&~@i$*-caWnv $\Eτ=B8u; 35FIۡxSu6EŻvgc _rfsΝpWp=N:jŤ0[9Q[=D]FI :bE0m(JwI˹,ԪU_ӓvIj.joM9HlZT7H^b] ߺ.G0ib5$}77r$E.n&&љ<6i={'ge䌩m uJ.4/Ip${V?|l*icі6%/v.M TܔQsoV .-]LCfExq @TdpżbsGGwZh{$+ASϺ!yKuEcਤz=:^5y!oo\: ,IY4ySPSG|_%|ӟr%őÇaS6*BT1mnayѮ_ī!_ߐIࢱ$&uY[Wz|~e0N7 P0N!417_pV(g`jIm< D9)D,.FzE.fgᧇ>!=/pɮ IC$˺)Bs3jص"1>?OLkώZ8̭E -ܶy-f_K$ &v<&[TMC Lj{߫Fc#oI뒮ֈF`:xCw<[qj5~kimg?["ySGC%V+YpiHџ_x[ CLCMI(i+nj?Pe/^Tf{=pzz^}NLʹ[߹4JcsCԄybfΞu]Dd]$];B2ZhxSʤ\52˛ ~>?è Y2#^\ N?vG(n~gs<6{O';Ov8{ymu)IT98<'.E_?)c8j t "A7̒Fݶ *266@F5y \z"D,`\f2"?R2⦵g~es@VCOҹOT*9iw|/pA>zڗ\wϏ>.$033ΪeS޵k7}˝ Mo iim $I2,fPaKm$RǾKhж%X"Y@'--2嫽J4 }݈4-Dm&JկAUA}oEٝ4*ʹ06+uܘ#,ٳp߽rb=\7u[P>s?Π[$UI;HQ4/ټb3YPfMq蕎>.VQ{(SHvѳSQ8(hKP^a싎+Fz+_2ol1ѳbF9;A4_t JI5EIba;N5[s sai'hh<ĽSϪ`le5kbErPVZ5feR29#Ѷ+Aa5h` >7; O>$G{ vy8a1'+g%-F,9V(ڔEbQ$-z76Ts]j(>fGgϚ;8b_rh4R*{%ڮ8 "HM[JE 7AfXggqzD/C9'XRۖr&+Y-hv6GhPŶUҒU5(n@IY>MQ=z\HmUJrtsAvmIDATV \5'x"镵kQ$ NytwAJuQB n돩r^GϽ- rOT=C$+$H3v=ـg'Itn`u JuaTZ79Ʀ RJ'"ILU2 E:/">ٹyU6u;Bf>.y~nNn9K<;Y|)[hNDEJSݝ"hҘ׳Cʛo'G{D2]߀sw[P/{'LNNΨGa*HzJiH"<mj6 g*$ !iZe`%`Ns'׽r iB"fSgӗԣfzO09XR Z6ʹ7*%f8hD8Ԫ[Dti$̫FE$c7}Քwأ陳g?o|_*%b1 RF̀r mUVrKq>,EK% ]G-Q`ϫcF$j~W3ϞOn7@_#8τ sĢ[AWd9ǯc/ix5g36nw]BgffBOr7XOLN[~ZMCw- %$9,DSfrt`"Z*{Y 1d3\X߽.EQ,B>3s6뮃&G?v 7 pP,tv]ƭ0D*f@9;Fr }$nN#KHdNQq*ihi{ǿV }Ԧpk#j1O z!jj6FAso#s mRܦ7a2IIRř;~"%uQi|7lTkjD};=A8b].?5ʹbӣ2M&,Y"lQ!Vj{ /icţ5h"E 9^>y47•pU쉃}J +R62F͍r!.Qc R V6:Qt RxJ"zrskC3EMr0?>]ws׿zհ~.dML|0} /̪>fv49]̀rvD"E?Mۦtr&OJf K"KPdF[4uZ>׿bv j<˹v"䩩)8aѣ$LϜr)F9AH݁?c9-e=[VqAC<Z?ҹOW*k;J\cy}A4ֿ_e6: t`l| .w>ػo*^$*R '&SC!T: /,BVmdy[e0(:@9G"<6<4]zQ.hjڛ.~rT=v#i)PEJCxƯ T9J>i| b1XZ\Tw ^\V϶buu `qaA;v@PPVSBɶ*B17نx]bImRtfs40u2 =Iu3*ϑF[JcMQ`R3ghgHɟ.0Sl dbOg!(gwD"Ӛ7MRmx7Eb(ZV+9F*iVZ(jF˺4D^Ty5 ?0KItCL?}CN4"g:6"fކvS}R+[+X,xh}ĞFqov8R[9-ߛ|9q-}*R}ӣfsfN2ĈxD< #gYL6z(RcYz!2a>Qtl?0=Vm -2Li{>nAĎJuW|KeRc9A_pYCTTsT_d(goۛm; jVTQd3O>( o۬NfZ[REv;w{\,mgAeG&7JTHrib0vs4nt:5$UD-jgeߣॱufo-Ś JoSx)m>!s63όKf{ڍ[A[QF*3;_dk '\Ⱞ߬^"h0D[fI؇:h=n%/ku0jYN%{VtlYkArV%̑ M~$?7d>{he 5 C:;LEEJ]Ͻk LXE/#Q,G'hQA)t+Wqv2{dž7Ȭad&e6z.w;1 KOfEodC_=\AI4bHd3)#gzsOChb@X"i`xlHēČIjۑt(ztx=8J0;(#x<bBU 2Im3X d z[ LRAU_w`:[\aUj%WaM_fRbxlfhL{K;pa}b'ІU1) ^gp(&]%ˍދnؑ[&eGQc- Njb// io5ا$$-EMrԶ#tKhAAz,$5cXZhj[ǩiM^ȎIt!F;,sC[s'4E}E3='05b&dBԢ5m+XLLX NGZjB I$RM&_qr_Ṕ'g(A)Arj9N8ч]^̎E (j$RI>e2rp\cTZ6:㫒$*PH8@1YĜ( PYcR:nE`. *I &(@F.9QaEY\tѪcl&=H$Fi/j5"Ws!Eae9`1[ئ|ۮZf1 =՝O%f?!X_P?7QAf0ށD$U. Z=)nW( Ro:eItҩu1ٕ!Uf*h0zU"i"Pnۏt-_xEx=)Itҩ܏_PC3!1MР Pnۏ-KZ. 1.M(c26]ۙ^>E~D෠ZqO:C$]w.\dV}F-(d ȪTd mnU*α]I㚤S˛oوto`vA)#As֖ԣdznmz:r^ ֒E=@k?(|PQMG7%oN'2ٹKґxw9OsIKZttFOyonnj~`4,7R2F7c<8c3;v$$7AQ8ixL4-_aqg+TwHēC3Q3l3م;A03FѢW>(>^|Rc2FGHD*}V6Sx4%:K'XJyZ~][+9h>j”F #A rّC]dCZfUs;Ko ŵ`4HG#DWfloè:xԄNܿ*ހHpi'z+^  Xx&3x,1C-Ș`Ϋ B ̣iiC{}}URjE>A d9z:N%'( 7,Hi'hh4]@F5+ kiAai1߯kl:R D4fStYVE_)&tNcU3=.F^Q^iãxшgɋBOi$tq#NBZR! E6tKlRYS7fboO2ރ2O@H.s4(r/0dOq R#T P@t'PwOwfVXJlT0 qnNѤ0Qj9o[aanRwTNk0 ` 7)ެ5\ʁ1w?6y\'' D n^x e6{mkrM`{3\ʎxbB@7jM%ch { seh-:o`L'N? Il8\ዃ:x*+xm-ߌ}|W[@g+ ԨAm&儬qbTs5y zYnH=Zn$i8|!i ;F=.F8Ňdf)^Mx rz+q]$Z"%bz)^f]]D/..HF/./;//q!գm_?IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/logo-generic.svg000066400000000000000000000022461404202232700235500ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/logo-on.png000066400000000000000000000020571404202232700225350ustar00rootroot00000000000000PNG  IHDRPPoPLTEppqQQRCCDՌ#%mlo''(``aSST667@hcثEQ#y<&䑉DmETi"2H7 X .n? Km~~&wEJLDg ZIO#輊 Lyt}X6[]AɤiGiڄ'vN^KjaCW?\|,NtmU ϨxSJ3"}hɞ7'x<N<A$ 3.K4Pc9\oqR.bjEqvP0'VYoRT젣ZL,w }9z ~x`AR*z@x:/fRW&9q XYtk_.SL!%IENDB`mozilla-vpn-client-2.2.0/src/ui/resources/logo-on.svg000066400000000000000000000023141404202232700225440ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/logo.ico000066400000000000000000000701201404202232700221050ustar00rootroot00000000000000 h6   [F(    -+.+&$$cccaaa$##,++VUUfee'&&&$$! 533'&&'%%:99tss{zz$##lllfeefeeaaarqqTSSVUUccc,++onnedd;::&$$-,,A@@! ,++-,,TSTfee%$$,++rqqlll'%%3-3/ ( @   6,.I:=#OqyyyyyyyyyyyyyyyyyypM("#]IMB58$ d_/'( & !" 1(* )#$h_("#X0//~}}+**M433-,,py&$$srs y(''^]^! yWVVdcdyFEE977! y1///-.HGG-,,y4330//.,,,++YXX+**y655977866866877877~}}y$##yxxdccy&%%lkkzyyWVVyeddjiiHFGKJJKIJKIJKJJKJJ^]^srsyCBB877" IHH0//yFDD0..GFF433y,+++**XVW.--(''&$$yzyyy+**jiiy,++eddylkkyxxyFDD1//q]CBB433N#&%%$###sd% ! !jqsh" ##]Y5,.)#$PNG  IHDR\rfZIDATxw|չ>33UmKnclcL)!_jroB ˅ܐ) ɥ@LB)^pظH$KV]im1;3;+|cY=my{C00c\7a >ݷ5M0$ H) x^iy ,-,<Ѵ=ϖ\A2!XNPz`SP*J(%2Ά`@ eP T`R93Θc @Ⓦ X ,# KAA11,a9|ph8cr)缀sV9/@8s^~ !A$ >PNRB hkm )cO")|BE&B[ džD39g8#sB=dA'@:!')G*D]rS r8 @}d($ rF O#mD&PB@1. br|tp>D>?!X F@j DQb9?u9= y92UuH"*(2YvxFsrx l|4[GfCЇ !R[m8.@|1?P@D]"%(I`mZ(TfbT`.AzawvTd<G@5>%h:7 d-㜏y? F9!vux ~~['/ ,-3A5Wrl`1pBH(Ho8] 3#S/ Twz[zYX i!`ZIqp3Ƽz Z_Q B9gHƏRG2CT鰻.lrνPaLEf<5,7|Ƀy!+!0Sh{S:aҒ&?QJ\- XB>'OjmrdcYPsd9:[AeUhj]nH"-,ZN^ ✟~[RNZ(lL(}v$%T ZFn(/R$DQ>[pTj$ 101 AŲ E4hH/ "W_:tu'3_  `A~ Y)!YPHmEA !2"h:cۿϘu &?H Y@dL %vD u@dͯhQCH:ʘ<)Jb@Ǝ A ;TCѷepCАQA{@:H -HXSJ-j/bŸ\>@a9?TH !_ ri{]?c0dA`M ˡ:^gұR"$vViFVi11pj8u J)@~foim i?$V!8B6j, q/2߭F&|+t&yqX $>Q3qN \B@{\@v0$ӕd9|NWWHNB(_/$hAYAwH! PvRRZTD2& >,<Ӊ8=k!8BHZV@%vz$轝gMR8zoNrxFww p(r6T,*6e7`PXYCВ8M)_wO#pB HJڟ  ᜏu/G{#D&n_ AN=aP h dPINo\`P~B a 2 zfB Ћzi`' \9u O?AO'd݋9gV@RgQήieqȵO}[Ia %A8<M$xܰ@$3YvK#9M*>~e_5(B- $%"66Zfl9>g~zB >bu5h(<7 TidM9CtƢO d0XLqC :HJrx l >=g +YaIn@ 6clN;޷ pLeYF8F(B8F8{Cv JD/ӂA(B 1_' @ m_rU(,(@yy9$QDQQ!A9~w MMM2S.4%j̾~2pu7@=. w^BA#|eS1Ũg1c1nX >N Mg9pmmmhhhpQl߾ǏGCc#O6?m0O88-LzAgz BWW{|z;r+|8;ƍE,‚ 0k, JiJf|qI &L " |&aZZ 3vA,LJh眫~P,^  c\7>udC#cw9yYK1v9,i KJPRZfÞ{ꫯbqdW@]v"'s0PB\ $EJ"D^x?N1ӧ[EK`᠂~˅ '`0wpP___O =doPwӎ!6QC&o hXD^dC6G>88c(++rnfL!$7 ,tbE9s-_?ض};0TAnK[BXN Ad&z:oOuG?7꣘gy&>r(92'{s6L6 χ 董 X0|I;\N':tPo#"{\7.E:fVɅ?#WL~Iq٥+.(f]9GII /8p,8}Iۜ[*6C!EIaԂ0[~5]ﻜp~I&j 9$IYgI&b}h>u*2vZ@Bz~kJV'BId`@JK9~^].v-;PRRG?e˗C%1Dv }ڟ/w]-3 hv}9/8 ,?&ڐ1$_w}7-s /Z~ MHLm,AV8ιHpj L8g\3?7OvWFAaAs /`Q#!V uQH@`99/uk]7Fq1pwň~m?]VR" ~a\.q!|ԇ37v n?f_Vʕ+!B sؙm X"1 @BpeŸ8p:[0Ⴜ~/noaܹbnÂ}Գn8p[l֭ކnp80yd̟?g͚*PJ #"Λŋ/K;8g g @ߘM kZUƍ?ߐJ):><ߣT "A P0 $!=<3sb…pim wߠ~5h` *%`;g**+uG!x-!E/E݉z//pX 8@}NAm]Z,@F"w<2vc̙<'λqd%'' Aކ'~6Ac cƎg2KdMkw]F['߬ Ԅ?0PTXYgbZ+3 ReݟXA@]}=~8QWeYӳ|-rHf_QQr]ytzDl݊_~YRL: PՓZ?,01HfP~BM ǭx{z۷4Oh@5llmL0#$`@@.,ϭ'Choo7"jTD͉=΍LB$,DAaa1JI}٩<+³ y hokU+U\=`clƜHՓ#AYVPO>0oB-Y~m=_|t[E\| 9.E]42/D]] |Ȧ`2{'$K?{_9v]1̜5 +V dr Grh/)ĪkV [VƀO?EgggCAKr<FL_bvuyفt. 7r3Od$~We$^{ ̝+ Xo:,G7 @ oU_~O֎͛?ѝq1ӦދQp$~&C\q! ؽgw4)icǧ3h%/d0F?m~`hjj2$'ŗ\}{ `L:rjr +e\RJ}{(u{ˬ`j5T8صs'*++3$EEQ5]b<DZw>aPd<]%x$SQV]}5.TVUNNǏwy]ݾgYd'> ؅l$c&!B::;kŋuGH.^ &}o&+ӄA>ęӧƛnėt!wJpal޼@j9 }$epjt_Ï{._N'jUX:lܸMMMh@ Eaa! 1}h\xbTVUbHB@I~~:D R|̱$L k+;446⥗^Œ΂ۭ?E8m6=<=gP[WcG3r$YBEEƍ#vmDR;z DQJH=5x ˽`&,t3Pr~ M|uȌa]8؈&&|B `QV9sLhe^EsƏkBf`0Xی &. reQv3=߾3fiW%R{ſ@0QXAu`m@sx㜁1ѣFիVAԹn%Bݍgyh,Fib2p7L&bl]QUʕWRu m6^kxWKZ8 stZ<}l@k˩韸 (}Xr%0Rݻv'Dk{{䚲T&*|e8%3#}rU\m%PJQ[SG~{HyET2PȽoD^̨Qʫ\ryqP ,eyۯ '+Вۀ5N=>Ϙ"?rՕS[ZأWA(\Rw75O~K[:eŗglXwmh/sl_>^0cJ͟`8viy$U&yՉMRQɮ/{9ց 4W>7P9@ qǏQ=ӦMCEn+"DA@ @:/>}ڂ/ge`+.,g/Hg(1{.-_?k[kS`͆RL4 +.]&ڹ>#446BfJxo4DŽ5,a@Aa!Ai|xg'@)$K,5^Yg0ea|TTT.^ݻvᥗ^®ۿW9?'pgs(6 FhoǶm۰fλhik!4VD%mV"uvμyXyJ?>*++!b*H&bY3pW;xp8"Ps 2c<$/=R,oIFӻj ÎE ƛnEv9̂!^/N66b̘1%ux衇p1PJQ9r$-Z9sQ><}nI9G0Dزe ^kرs'N@'I~-OV&a9|_aÇiwaǎxŗnzD,n$rR|IzO%u :Ǎ[o+W]rW"ɪڵC?ñ1va)={/X 'bȑ(**7O`0SN}݋ǎ!/iyQ? ;f4n᫸PYUUJ]]]x? nE(4s3vwEOԿ?h @;y$Iwߍ Ds qTd]Ȳ Q`I(// QRZ*TVUN3zO_gg'N5B]]ԄCӃO,_Oy{pιBŜ?ǏzFs+Ɩz +Gr<6I+½߽ƏD ._Zg?ZPA uL1 ǎ׀3At8RMG 8=]5VᎻĨQrԶ;0m4<㨩牖@.՟]=˅}QV6hHkuZuR1~]Eз7T/a&n7PXXhWvЃ` )owZy |2r:pۭ஻FAa&^ĄP>?uiHɖ&q^ϒ\},ˍ7pXUt%n<'r.=Gt-qZ~((,4v@&R# f~ͪ lQTP禛t_9 pqĴSe۝;YD>t&ÞR/|^X~7 fM (5bZ`lCc v7pn^2N\{5T]mP\^r1W^'~#ǎERqY` tMY"?3/K.dUq?pzi%9сCCC#PJ1tFU #ˣ!Fsr=?ݛb|_ۖX$;G)%\gmDzQQ((.*ƨѣPQY Hd+**Wo*>457TG'u9\K /o[Agg'cV$ATWWsŗ\fru]yÏA'w`aTeV\ fuAihh;o m]9?*fR cXbΞ3ǰyylRŗ_Y@6/daرA)5ũ-^}e-طo?|==LoYFl޺vիb |͘:m(s_3ΜMlpkTRDӧ) #_dz<]w9 ͻcBcǮ]ص{7^ye-/[n3g4Ě+oCk[[SP^V(Jxpf r0㪫ĕW^$| J)90q1ȌC>ב#vuӽ{mV >'L vҊ7A D8 'c\;q麅R<ȯO]CxK.ib`d#mيbL;cnGn6l@ss3Ɍ<\U9,Øqt|_Uuux?ŋ/u;}ͧNap:1c!$vЈM6ENUE(3E9w9GȑkJ)O?}o.Wmצ&MUW_[6Cu"F2A PQQB҂_x5هcǕ~3.eFS-AQq1Z%`(1'" K;9(!8묳P:l!xOw\Ҫ -!o @D݉z<1aL6MD$ ߾vF~n-p/T^(jiO8{]saر8А@@tO3NǛ6o@4х*DH@w>;kt$ 3VBՅ^|qk 2|M޵KC0cߏ5נ&$a xOtqv;ƍQIk @yy9ƍ[8p6} =#SR X:\ݠ!555ذaC4]N!6]ĉ1|E $6|.`a֭h>u*{Sslܸ}!![6oƉ4ƞ`Ν?qB9Gee%<:hNNSWYP7o?zX>& ABؿo?}ݚlƕjkjQqQRZVLZ\²PךZ]wtvD7EZ{8vBؘ`3sآ(jz+#AWtvvE4yPV%C B~?#{CAtttV$6 َ5 Hz1 A6X{͗o#!L,#dݰ<C9\n7ēf&%6ncȆ)I$ȑ% z!sFN-UQQap(//`ʍJe-%S>p@KPȑ2ө-/E6jҒB7nh`8p{<;nlAĩc [ٸ [7M羺 `>NWizoQ&%g|!0̛;JEk]nq3vs_w7+جdaytY3gbr$0Y6E8p9hɒuoU00:eJa=  .Xd%3ͧ S&&J)p1]0PUU. vЋԄ19}:.p9V^eM< 8'Mu+Bͽnn6o2 ²G"3RYsDVzR{uQanFT=9 !Jz+JcL__5Ǡ^^Tq &NdǏGցeQ_1l߾^:3F;555;QA$` GɐD _e ^xN\nwEp#Fλđ#Gp@H0 ?ǥ+VU@)}rb}tnf^f ,KZ?~ǏנT%K⮻/0Z"aƩL$5K/e(஻ۓYrp{ lܸI h Ȳ _]icw9w> hIHET_u(.B|_475a}ŴKhhloY3u)n >zŞ{p%o|/^3Θ_~5:tH9ȓҔ+8v;FE]U׬œs7W׋7x@ NH⟰]f3@ ֺuXj יA_h&Okյ矣=ɸ*m0bx-ZUV΅4/Ʀ7S2v(2&Tw M2TK ^}3b1Qo+W۱mVԢݠE TUUz2,\IՓv!3b !ر};6oقoW ~Go/:^}e-_taRj#Gĥ_.Y^]!ʻ+(~T]R|q0֭Si}7V 9Ol_^ fΜe] (**Bqqqj=fxMS' 9 {[[ZQf#yXu^=hhh@kK+ L{w(,,Դw(͚5@8TddKenzۦ%Ǩȳr?9<xm;Ա:e2s=5[eʮҾ{fACfTZcǎv{6w|Gcz$л~Waяe˗`pȑHL]7uQm۷~|y=(غe+ߧ7tuH 5ᯞ4 ?tٲAwFsW^~oFԗO#P#kח^ZuA)EMM ~c8őE Ki~8(Rm['>n}*á2Df_1,Ek{~';ikmo~86n6վ${gUrInO;z="jN4'56jӟ￷!oH@Mu'5k .L} s Uқ<XtF{ i*u, )xA 'JiTSm:&qOwiXlipPJqQ@f,%I eU8}=%٩}]y)3zRҩStTVVb:/4SO]oޱU:D͇>1\fKskۧSoZg?_7YI7(NQ9mZt9 ̢ء1`gqɓ'j*=8)8|կXSl:#bϘYD[9$@$I,3Ȳ |>B(!qjBN}8?RBߏG~o²& VEy T RMm~p`۷qǙ(x xDZs.0Σ 1v %wr`(//GYY0l0B=hiiCpIq.Nբ~J) ^6~ؾs'cq'rט>CAƔx^<,ߏo/QU6f9x Tͯmsc[ ..10|L4 e())$ |a c N457#شq#v؉mhcMbQ/{976FZ!-Uk&Zaa'~z\wu;oQM9%QaQ1yd,[MMMصc'|Mlx}743)S&CA /`̘((,4UrGGYO? lۊ`0|_c5̘>PfjH÷Qu?eTNc2#xʕWbܹ(--[1 = 6Wb=''>%럪p+1㬳3j<ߏ;wᥗ^g=ދeP(yO;o.z̞sGy˚֖l޼_=TO79t}w9}nwVRTh :P' SSv%E8{Xp!/X1cǠ $'B`$r 8wy6}ǎDtڛDpX|qιree#^/ZN¨ѣ zUgã>'! "Fa򗿌sȑ#Kubd[uw~֯;v@[G;T6:S+Xɴ+oUf"bagq&WWciT]ÇvnR ݎP(P8 &AGG'N8Сؿ?jjkIE{m%$k}1i|-+1lذvh36]nT{ '"I[IԪ+xP=if͞?SNEa(((|YՅ岙۶aعc'Nԡ@4~+KDPn5+@[V}3e߻$I(1eJ" ==~~eNC!E)Dr H*Q0pwżsѝJ sGCcht,BPTX˅ &`())AIi JJJPokmEGg:::t  MMMvu)B#УG#rEy΂V9U}>tupG!S5\R$hj~˅+߹_7?ğ>* | 9 ^/Ѧ`CEQTBJ,DdS}ɶdS{ #n=XE^8^+T q?GH&NzLe=o91̈́@}?~Ĭy,!_΁P8`(AR^G?8X楸PxCsQm;S[oq{t+l N} MMD!Hd'q@T軾gk 0VʹCˤֶH@WuV~k7 Z IRk'T@rM/h\X4Hope)b{ܗ\|1} P~y<&3$yTqm01s>̸[GCɾ.w;oU0en_ 5Rc2Fw܎)S N8o7RBn}5| 0dd. kdA+ٯ-I[}`Q?ct|k7Do!2jr6s<̳xG~zaTyRۀFXIo"ysN|_Ä  !3cdG84< IKoeoRǔ8.+%5,,_<.^PJQSS-7chllD}}=r73cѶƌ-7q6VƷʽu}hRɬxp8_Ȋ îؾm֬^ 6D}=}ݑ36Ja)jUK_ˆ# i ahljoOi,^xLc\chC?!---x/gEMm  B^n 9҂7n];֛oλ4c 眍c+D)U9P?~ygק?!1fRuu?ģzG"^T{N5igDtX۸;k I_x/ODf.=fe7))15"@Qa.Zrl6[hz<E1.z5Q]D@CO|k_yŰ9gz$Hefm2|\H@50eT|>~'W!Gb bX>JH  AQЀ} oڤ6fFYg)S诙\f\j H HoъfcD7^W2#Ađ__h:yRR@$(*,LNltN P&8MP=N3cP{g֦KUhI`MXvncԩp\sjQL'o+fzOtbqt;o]wZWԤ{)P:l&L$I ώ_/QԌ]39$xd!ztwtPc8صsG?~|&Bu\YXY `ŖIoQa;aJ69.+=]fO謇N3fΚ;# C8,G8]MT p,]zoGl߶ ޮWtƔ>}דV!uݷuu' 9=|#, ˁ2R/,KxɓxŗХ"3;ʲl9BY|! 46&O$f gNjM9֭_w~(O?^s-jzat9Μ82BHg[j֠$֑>pZZZ? Ǐם~ xeVPJ,O%G\q /׍U$1)[?otwϽKBܳ! eo6ZȽ!4@Fbi]wH`%~(k sfR8p{ 76j+HLl~2 eZ=-n^W_:[Z?RZ<ؾsg[t] pJ $2BԎYٲԺ.\%J6(A)ũfGo1w]c &A/ B{F889P/,"{;5z-ÒRpA<[P/)}c52(R7w<-?.k(U?SJIyemVd.cͧ=5j4ƍgJ)=|_`0E J}V~Thw:DQ:" 1Yv'`%%h>u [6oSaL Ivnݲ?Oƛo!!BŸ6gKו nW '8 pEJu||7$X~e <&8 wtb۶mhj]xx.  JJ[k&clv?NPۢSO;oѣt:&{W?z u'ND}(Uۛݱʦg]dtzB!Nh@{` 1X8TC&KjW YyV:t׭Wo*Λc)Xe_>5k`58|c\8+OȢh"!$8O2BHHţ$D8r#L"Zv@Luck`ҥ+0kl 6 $rS d/>_{o[  Bd϶iy%+R#([ц1}Ry1f%A^ͺˣ JJJ03q`;n`ۣGYՅ&lݺ?ڈMo‰Bqqz{f>5 Z_@K~-nhD%pƘlNJ@'0{Iol hMphֶ6llܴ e#F`ر5{ƍ'%E4.%Fs)9;a}8 J\ ~|! b(Jmsu ܋Ȃ 􈢴EW^]-F@r!3 8ЀM|Q0|pxn"6f^ ܌n_7#ʔҤf"B6 .(K8t h9? u Hz V$kn `6]PFP gB~>Gd97>(L!pRJw}Օ"%GLCӸ34bKIAEQ<( Zg[i(J%9!HEAJ(ylZ_D2 R]w ڎX}BHfs%tB898sݙ̡L _4DV{2{Z8߅*8쮃 %7W@fL m@ٶ:&  }0?fw) eC4~!v׻\H?b$"H +!쯹}B 0>r|Y`Œ1UxPr)c{&`!]mX6ɾ&9sۡS_?xRnwNiunjC.I@?ɞF ͱ?(BH+?K 1z<] 9hF)=+UBLزcͭkߞAn9C6ěI;)$ G!"}P\纗CHnPuҤ| (,aH82!dU_l@NdMfhq}7F;}$l>I/"ߋ>HJ 2?!*zRag;k!YK.M W!ApOhCdH%q4!T;lNcv`*(.W:QjfT$sl@_Ę<d=CH@v8x=ߗ 0C)m-$'ַNo._ I}q$$BLv$P(8{p !84~Nd9bw!a\wUd)9nstA,pz0!"NWBh]Ys^zo)^ΓPPe,.t`KK/F=Pv  )@2+ʒQ ZCo"c1$*Hawn(,!߂ض_t`3U,I Ep(XȘ<)2'#1$Zt-1 P@HKX!Qd; @ CVAd a"fH]4]p&R$@r @@vҵb|IG  p9BGCᐃ16 cp !|O ~ C- D,ns~Cajv[7 e Ƀ/B;^S7_"26 S*vX9E!\_Pv)k^龭 ?s W-!pX UbП0K!w BȒdSPPY>K8p ʈl$>&2chٽ~!3O,2?0$z@ mw8l9G"n(8dݯ!IDAv8ܟYs6ܨ3͂!7$$<w{P~?~f a[Ca.:!I=1/r9! ` p8ob;@9ҫ]O1}|e;M)CŜfeC 8fsnq9=lH&5ObZP? 'A!s>5(+`2BMv:<[v!BHs$_L>>(ziuH F6(@!s6Ms,CC *l{Mr'FGLM1{-#P ~ߌP?',qFrp[69!BH$a͹awTCGƞNLb~\O3;o@Kv(>@ܜ`?*Oe9<16sVeThfs|f9Jҡ˽P sO|Ugқ)rxTIq pO( +`U8/3s6sV9ws2C0 LB}.Jh hEQmM(Bٷ"?zu:L] KK* @wqΝpXC2 9nd7㬀Ιqq.%G BwB}RB(H vBP=#p?aW^Ы>?9Pۤ%} !9pQ.ZC_B41- #_9G!(DL4֩s.*ھd>b熄FAئ>2b!?k?4?@߆0K7sqkh@nC0 mozilla-vpn-client-2.2.0/src/ui/resources/onboarding/000077500000000000000000000000001404202232700225735ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/resources/onboarding/onboarding1.svg000066400000000000000000000023441404202232700255220ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/resources/onboarding/onboarding2.svg000066400000000000000000000024251404202232700255230ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/resources/onboarding/onboarding3.svg000066400000000000000000000021461404202232700255240ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/resources/onboarding/onboarding4.svg000066400000000000000000000011711404202232700255220ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/resources/quick-access.svg000066400000000000000000000061261404202232700235520ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/removeDevice.png000066400000000000000000000227541404202232700236060ustar00rootroot00000000000000PNG  IHDR>a pHYs%%IR$sRGBgAMA a%IDATx} TՕjiZ4ߊQ#Q&eMbr I> bHbQ#3D5F4:{ϵOU?ꮂ^c}NZaaaaaaaCpѡ$|҆C CA #tet tqP,Qsg>kѧ1Nihh'xu]lʊ֡? A80>!Zh5/͍K3L{6knn^}}lBu⥒#ds=}ݗdӒ fDO lcq =V2鶶YdchAu<`X=dt&n 3xu^[5uh!T*>BPD}~̙3hmm}##Z|`tlmߋϿWlOZ[-X\rxP×hhݻwQ3fUX=R6Mtc#bB4,;M$ABal_K/1c\ɽ`[)njgq1[` ϼ2IybpXRO23rwɚX&Lerj:35Z]`wUnv}qǡ X`&W˯M$G)}0ϋ@oJN2j9Bq+ve1+V] B>dDM7Tw饗Hh w ezf=u5,G1 kaԨ:::xWqP),Y*eer*wu瘚1[1^xִI%)V_Gݨ:n'<^WW] _|HʹiB]Yf֧=۴Ϸ jkW!-0:Cf/[8aI*S)^4~U466*LE{On `-n6;rmܙAƏ~O.%|}ԨQ_"\S@*-@\M6]''[0TfrYIb> b@ۂk\sƍq(Q Pj@?1}c=vفE97TuΩw-` ʈB=o=s$DRxR~VûgUk/Ӊ0x}D(DO=c׃%w?K) 4^y_pcxUb'/xJ>K_9s|eܸC;w S"?~&tUWɓoki^[nnܸnwQXq= k57!wB>d .f*gw;wĉfjyѺb4q}~c~v/@]$qNw9aFm]ZPO# y˯D5~{*Z1%Ubo=WUU]L}"Y롸yC?=}aqȘiV׎>,,v }k֬Ȫ;-?G4V-Ɩᕐ8 d@|d',Eaݏ۪ĵ gC L+ >O7o~xE]x,()7pɸ E [la$ oX'-d Z0 ?nX+xO.p X-4Y¢j4իW_ {pbO -,@&c.6CGpKA( Bέlxگ">Vl}]w]uA'm1!Ef`C?4Hwe]cё|[4TyɧqAYI߽_" Qlo޼y7"1jc˜lb=B+&kRNT-tdǫʡ"&Hf3H46fܹ7_-.țOӹvQ`@Y`Lr`6&4 MLz`'AX$Z}9wE:}XxW /M|ƼlxhB|h3rIȤ Pf 7XL0WVTNwIdBhB>hukPdS"BU `!!EMbګ/<UXX SN9㏟'3w:Y{=Zh-TB@͠Lh^3+irc@8gO} %Gl>oEXlٲ=#5:уt2]&3;Kg &}s&Dg]MiiXQ<? {;l„ ߔ{-"[нmNs߄az]NQC~ b_.L"й[4s  #]@Da.xN6 H0Vu 7 E)˗/?fp*öEݟNQ'Ay5W! )@Kp?nǴC2>o(THUWCFC%9y@"E)s=3ۨQ1jmgB!1,ZMm1ּ߹q?9.,(,j-겓6n੢'>[[>P*KP&/XK`js&]4ɤv@4E~A XP+e%{@=UD1>~w⧊ذo/Ojk2`M%ZN eN^ Mm9@6\`OSmxx'OCIo…kǍS!4"q@Y8!~w&G( S쀸`1RN}O%z0a` @Y$%˽"/0Y {|X GJ ќ ZLV/D2 1p>zN }?FN; \d$lEʯy0p(,X33պ(>3\vFO/~ff de Am%"t; ͢FжўOUUx/` x"z晧) RW7.֦π`@QZ 6h&PĎE_  |$x$p?[Hn '0p]~+dCތD{)1pT[(`F5d# `xǻ{>e_Fk@Lc4^x{"M+pKt  }_җ*:&Lu;! |8M7rQ'Z CLuE3m2lr-<40un;Pe6G"\3騛 W K +;Dk3Õ1; &oɁcm䰟PPw݂@t?hȹuVaV o?4<PyX7  JoעE&֎j1~.,fәrLV#1:1, ݆tnB}=D;{%w"#pٰC0nZqaRh؇̙3ot`j0X9cKLڴ~cBڜ`2(X19 R8㖅 X7X,Fr*q_90.xX8@j<ّ0AOfϪd4 ݓS'3f޸ [^B:7l]b&nȚ\vo3^7Ak<ߨg`a C8qbԩS~d΅Z37Fʔڽ<M1I%rA2UA &Gޥ01'@EJ97+o;2 7@3~bmxAJhof6¾%KL3+{(< yc͜n{FMAXNNB2? >D0]8h>K0S~34/ãwΔ)>NJ,[`62 6o$ B۳N{߇;!Uz\(ڿn+ٰH7"[4Õ7@p$dnCpg=Ǣl!昅 ?455!D@ 8㌲?v٩`̎2cyn-1`k7Ϲ /pQ5O\o[n'~K8\Ѫҗ8%Hӝ|rvmsn#@Np^4h3 l<ȁNNIv jZr% 6UWWé r}˖-o+gai@7o{G1H]F"ךjz6IYgd;1W aI.UrAj0"*Xz5 !oV1c0=pmI$w5"_{`O7413Ƥ[s?c:sםBَ2͛7`ЕW^ 6m*/p3g ҥ'=_1Qqz;0L$L3I.PF 8#S;e܏o~SjwW})0@)4P/q.ӻﮬH&O Lidr}^¹g~ 3N$>X/gLjH@Byp]$4սa>%SL); s=?+S-׊+:w' {]nL JpBU?cє |[l&<-4Ќ_ /~^bK:y'dȼm9b0O۾481; jjCАmf8ZI&4:a]w2jz@4kil9Ǚ(Zn r|ND($a&0p[p¾ N8ï7;%OD'[kLqX^`}m2w0ptXi[硐d|JO?t7@ҁr¾Yf,WLAEm5T]r2۪pn9ڍqyL@ ht%f[5ģһV=D[O87CC?Km~<)^c ,`aKA^a6NA;68!'T`TM($C^zi+(}cN;r}2;௷41 n1x3 % N `pD=S瞛6ܗoBR_\U+f:5ط xFgH2Yewnn#$o 9|\PPB_'u!G|L>[a__^swƚ 3@dٶY|`!{pa`1鹝0;2bm Dnݺ>JPW6woGѱ |Crwa8w;Vq2W =)hi)HUP/&]#.¾JOzSEȬ- z.5Pd֋d> "ߟkb)Ϛ!m:-A1 zlWlV?+ٲ ܹ=@t QJ·վs*sN%7utOskf]{C&_mY u f\z?X|Q A @)SN&X4-d[@"53XXh ķNy\LX-f U-D);$F}_<Z4ll!?0:+&ж<]I.crsfkG:{ UG)%DTJTx㍗#HܲUWFCa23Ǜ̷"9(e%^#gxq@iJɽ|ƒ]ve[ZZ>~;.h S8Y!^! Pּ;4`J$pp jjGzCf>s+Q%|6E :i'}y-}Ac.-950קMB2R3qaڿpE3h(+N~A<D\bx}XThf!s6K̞<5Ƣ tҜ0IF6x+AxbH |QQ4my?=n|jDOZ&b`&c=f)<CVkrfLV dLk!`Oc%T6k?475#3lO]~ #6r*DP_]NU0&Dn1h`Ȕ2|쮫PTJO;дum_? *Gs1=S$!*@dv,. Ba9 %DFtfxjBȨp@,ٱvwf͚uEz$cIFnsZC/ ">xvஏPH*M5V_MBxg`?@xWCڬ+4u5v C:/ VQ` Ht0˳ntqb(ʵ(aD`e]&>N8pVjDy</"II( U)UL QNB1EXQ( E)@(ߌ 3]<>  Noww4OS!'W P@RFj 5 R @.0%~ЋantEu:Pcv|{ФIbQ[@rˇF[d^4|T֠090Lyi>#0uK;ha5ՃJk`Ai R ._5}-aIENDB`mozilla-vpn-client-2.2.0/src/ui/resources/removeDevice.svg000066400000000000000000000320051404202232700236070ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/settings-white.svg000066400000000000000000000030351404202232700241510ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/settings.svg000066400000000000000000000030351404202232700230330ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/settings/000077500000000000000000000000001404202232700223115ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/resources/settings/aboutUs.svg000066400000000000000000000016221404202232700244550ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/settings/apps.svg000066400000000000000000000013531404202232700237770ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/settings/feedback.svg000066400000000000000000000017351404202232700245640ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/settings/getHelp.svg000066400000000000000000000021001404202232700244130ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/settings/language.svg000066400000000000000000000062161404202232700246220ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/settings/networkSettings.svg000066400000000000000000000023021404202232700262410ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/settings/notifications.svg000066400000000000000000000027551404202232700257140ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/shield-off.svg000066400000000000000000000013461404202232700232160ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/shield-on.svg000066400000000000000000000013501404202232700230530ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/resources/spinner.svg000066400000000000000000000023711404202232700226530ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/resources/switching.svg000066400000000000000000000015151404202232700231730ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/update-lock.svg000066400000000000000000000023221404202232700234010ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/updateRecommended.svg000066400000000000000000000033651404202232700246260ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/updateRequired.svg000066400000000000000000000055151404202232700241630ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/warning-orange.svg000066400000000000000000000012221404202232700241050ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/warning-white.svg000066400000000000000000000012221404202232700237520ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/src/ui/resources/warning.svg000066400000000000000000000012211404202232700226330ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/settings/000077500000000000000000000000001404202232700202775ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/settings/ViewAppPermissions.qml000066400000000000000000000146031404202232700246250ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtGraphicalEffects 1.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme Item { id: root VPNMenu { id: menu title: qsTrId("vpn.settings.appPermissions") isSettingsView: true } VPNFlickable { id: vpnFlickable readonly property int defaultMargin: 18 property bool vpnIsOff: (VPNController.state === VPNController.StateOff) flickContentHeight: enableAppList.height + enableAppList.anchors.topMargin + (vpnOnAlert.visible ? vpnOnAlert.height : 0) + (disabledList.visible ? disabledList.height : 0) + (enabledList.visible ? enabledList.height : 0) + 100 anchors.top: menu.bottom height: root.height - menu.height width: root.width Component.onCompleted: { VPNAppPermissions.requestApplist(); } VPNCheckBoxAlert { id: vpnOnAlert anchors.top: parent.top anchors.topMargin: Theme.windowMargin * 1 visible: !vpnFlickable.vpnIsOff anchors.leftMargin: Theme.windowMargin width: enableAppList.width //% "VPN must be off to edit App Permissions" //: Associated to a group of settings that require the VPN to be disconnected to change errorMessage: qsTrId("vpn.settings.protectSelectedApps.vpnMustBeOff") } VPNDropShadow { anchors.fill: rect source: rect z: -2 } Rectangle { id: rect anchors.fill: enableAppList anchors.topMargin: -12 anchors.bottomMargin: anchors.topMargin anchors.leftMargin: -Theme.windowMargin anchors.rightMargin: anchors.leftMargin color: Theme.white radius: 4 } RowLayout { id: enableAppList anchors.top: vpnOnAlert.visible ? vpnOnAlert.bottom : parent.top anchors.topMargin: Theme.windowMargin * 1.5 anchors.horizontalCenter: parent.horizontalCenter width: vpnFlickable.width - Theme.windowMargin * 3.5 spacing: Theme.windowMargin ColumnLayout { Layout.fillWidth: true VPNInterLabel { Layout.alignment: Qt.AlignLeft Layout.fillWidth: true //% "Protect specific apps" text: qsTrId("vpn.settings.protectSelectedApps") color: Theme.fontColorDark horizontalAlignment: Text.AlignLeft } VPNTextBlock { Layout.fillWidth: true //% "Choose which apps should use the VPN" text: qsTrId("vpn.settings.protectSelectedApps.description") visible: !!text.length } } VPNSettingsToggle { Layout.preferredHeight: 24 Layout.preferredWidth: 45 checked: (VPNSettings.protectSelectedApps) enabled: vpnFlickable.vpnIsOff toolTipTitle: qsTrId("vpn.settings.protectSelectedApps") onClicked: { if (vpnFlickable.vpnIsOff) { VPNSettings.protectSelectedApps = !VPNSettings.protectSelectedApps } } } } RowLayout { id: allAppsProtectedHint anchors.top: enableAppList.bottom anchors.topMargin: Theme.windowMargin * 2 anchors.left: parent.left anchors.leftMargin: 18 visible: !VPNSettings.protectSelectedApps && vpnFlickable.vpnIsOff Rectangle { color: "transparent" Layout.maximumHeight: infoMessage.lineHeight Layout.preferredWidth: 14 Layout.rightMargin: 8 Layout.alignment: Qt.AlignTop VPNIcon { id: warningIcon source: "../resources/connection-info.svg" sourceSize.height: 14 sourceSize.width: 14 Layout.alignment: Qt.AlignVCenter } } VPNTextBlock { id: infoMessage //% "VPN protects all apps by default" text: qsTrId("vpn.settings.protectSelectedApps.allAppsProtected") Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter verticalAlignment: Text.AlignTop } } VPNExpandableAppList{ id: enabledList anchors.topMargin: 28 anchors.top: enableAppList.bottom //% "Protected" //: Header for the list of apps protected by VPN header: qsTrId("vpn.settings.protected") //% "These apps will use the VPN" //: Description for the list of apps protected by VPN description: qsTrId("vpn.settings.protected.description") listModel: VPNAppPermissions.enabledApps onAction: ()=>{VPNAppPermissions.unprotectAll()} //% "Unprotect All" //: Label for the button to remove protection from all apps //: currently protected. actionText: qsTrId("vpn.settings.unprotectall") dividerVisible: true } VPNExpandableAppList{ id: disabledList anchors.top: enabledList.bottom //% "Unprotected" //: Header for the list of apps not protected by VPN header: qsTrId("vpn.settings.unprotected") //% "These apps will not use the VPN" //: Description for the list of apps not protected by VPN description: qsTrId("vpn.settings.unprotected.description") listModel: VPNAppPermissions.disabledApps onAction: ()=>{VPNAppPermissions.protectAll()} //% "Protect All" //: Label for the button to add protection to all apps //: currently unprotected. actionText: qsTrId("vpn.settings.protectall") } } } mozilla-vpn-client-2.2.0/src/ui/settings/ViewLanguage.qml000066400000000000000000000256341404202232700234020ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme Item { id: container readonly property int defaultMargin: 18 property var useSystemLanguageEnabled: useSystemLanguageToggle.checked VPNMenu { id: menu objectName: "settingsLanguagesBackButton" //% "Language" title: qsTrId("vpn.settings.language") isSettingsView: true onActiveFocusChanged: if (focus) forceFocus = true } FocusScope { id: focusScope property var lastFocusedItemIdx: -1 height: parent.height - menu.height anchors.top: menu.bottom width: parent.width onActiveFocusChanged: { if (focus && lastFocusedItemIdx !== -1) { repeater.itemAt(lastFocusedItemIdx).forceActiveFocus(); } else if (focus) { useSystemLanguageToggle.forceActiveFocus(); } } Accessible.name: menu.title Accessible.role: Accessible.List ButtonGroup { id: radioButtonGroup } VPNFlickable { id: vpnFlickable objectName: "settingsLanguagesView" flickContentHeight: row.y + row.implicitHeight + col.y + col.implicitHeight + (Theme.rowHeight * 2) anchors.fill: parent NumberAnimation on contentY { id: scrollAnimation duration: 200 easing.type: Easing.OutQuad } RowLayout { id: row width: parent.width - (defaultMargin * 2) anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: defaultMargin anchors.rightMargin: defaultMargin anchors.top: parent.top anchors.topMargin: 20 spacing: 12 ColumnLayout { id: labelWrapper spacing: 4 Layout.maximumWidth: parent.width - useSystemLanguageToggle.width - 16 states: [ State { when: useSystemLanguageToggle.checked PropertyChanges { target: labelDescription //% "Mozilla VPN will use your system’s default language." //: Description for the language switcher toggle when //: "Use system language" is enabled. text: qsTrId("vpn.settings.systemLangaugeEnabledSubtitle") } }, State { when: !useSystemLanguageToggle.checked PropertyChanges { target: labelDescription //% "Mozilla VPN will not use the default system language." //: Description for the language switcher toggle when //: "Use system language" is disabled. text: qsTrId("vpn.settings.systemLanguageDisabledSubtitle") } } ] VPNInterLabel { id: label Layout.alignment: Qt.AlignLeft //% "Use system language" //: Title for the language switcher toggle. text: qsTrId("vpn.settings.systemLanguageTitle") color: Theme.fontColorDark horizontalAlignment: Text.AlignLeft Layout.fillWidth: true } VPNTextBlock { id: labelDescription Layout.fillWidth: true } } VPNSettingsToggle { id: useSystemLanguageToggle objectName: "settingsSystemLanguageToggle" toolTipTitle: { if (checked) { //% "Disable to select a different language" //: Tooltip for the language switcher toggle return qsTrId("vpn.settings.systemLanguageEnabled"); } return qsTrId("vpn.settings.systemLanguageTitle"); } onActiveFocusChanged: { if (focus) { forceFocus = true; focusScope.lastFocusedItemIdx = -1; col.scrollDelegateIntoView(useSystemLanguageToggle); } } Layout.preferredHeight: 24 Layout.preferredWidth: 45 width: undefined height: undefined Keys.onDownPressed: if (!checked) repeater.itemAt(0).forceActiveFocus() checked: VPNLocalizer.code === "" onClicked: { checked = !checked; if (checked) { VPNLocalizer.code = ""; } else { VPNLocalizer.code = VPNLocalizer.previousCode; } } } } Rectangle { id: divider height: 1 width: parent.width - 36 anchors.top: row.bottom anchors.topMargin: defaultMargin anchors.horizontalCenter: parent.horizontalCenter color: "#E7E7E7" opacity: 1 } Column { id: col objectName: "languageList" spacing: 20 width: parent.width anchors.top: divider.bottom anchors.topMargin: 20 Component.onCompleted: { if (useSystemLanguageEnabled) { return; } // Scroll vpnFlickable so that the current language is // vertically centered in the view const yCenter = (vpnFlickable.height - menu.height ) / 2 for (let idx = 0; idx < repeater.count; idx++) { const repeaterItem = repeater.itemAt(idx); const repeaterItemYPosition = repeaterItem.mapToItem(vpnFlickable.contentItem, 0, 0).y; if (!repeaterItem.checked || repeaterItemYPosition < yCenter) { continue; } const selectedItemYPosition = repeaterItem.y + (Theme.rowHeight * 3) - yCenter; const destinationY = (selectedItemYPosition + vpnFlickable.height > vpnFlickable.contentHeight) ? vpnFlickable.contentHeight - vpnFlickable.height : selectedItemYPosition; // Prevent edge case negative scrolling if (destinationY < 0) { return; } vpnFlickable.contentY = destinationY; return; } } function scrollDelegateIntoView(item) { if (window.height > vpnFlickable.contentHeight) { return; } const yPosition = item.mapToItem(vpnFlickable.contentItem, 0, 0).y; const approximateDelegateHeight = 50; const ext = approximateDelegateHeight + yPosition; if (yPosition < vpnFlickable.contentY || yPosition > vpnFlickable.contentY + vpnFlickable.height || ext < vpnFlickable.contentY || ext > vpnFlickable.contentY + vpnFlickable.height) { const destinationY = Math.max(0, Math.min(yPosition - vpnFlickable.height + approximateDelegateHeight, vpnFlickable.contentHeight - vpnFlickable.height)); scrollAnimation.to = destinationY; scrollAnimation.start(); } } Repeater { id: repeater model: VPNLocalizer delegate: VPNRadioDelegate { property bool isSelectedLanguage: checked id: del objectName: "language-" + code enabled: !useSystemLanguageEnabled opacity: useSystemLanguageEnabled ? .5 : 1 radioButtonLabelText: localizedLanguage checked: VPNLocalizer.code === code && !useSystemLanguageEnabled onClicked: { VPNLocalizer.code = code; } anchors.left: parent.left anchors.leftMargin: defaultMargin width: parent.width - defaultMargin * 2 //% "%1 %2" //: This string is read by accessibility tools. //: %1 is the language name, %2 is the localized language name. accessibleName: qsTrId("vpn.settings.languageAccessibleName") .arg(language) .arg(localizedLanguage) activeFocusOnTab: !useSystemLanguageEnabled onActiveFocusChanged: { if (focus) { col.scrollDelegateIntoView(del); focusScope.lastFocusedItemIdx = index; } } Keys.onDownPressed: repeater.itemAt(index + 1) ? repeater.itemAt(index + 1).forceActiveFocus() : repeater.itemAt(0).forceActiveFocus() Keys.onUpPressed: repeater.itemAt(index - 1) ? repeater.itemAt(index - 1).forceActiveFocus() : useSystemLanguageToggle.forceActiveFocus() Keys.onBacktabPressed: { if (index === 0) { useSystemLanguageToggle.forceActiveFocus(); return; } menu.forceActiveFocus(); } VPNRadioSublabel { text: language } } } } } } } mozilla-vpn-client-2.2.0/src/ui/settings/ViewNetworkSettings.qml000066400000000000000000000050761404202232700250270ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtGraphicalEffects 1.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme VPNFlickable { id: vpnFlickable property bool vpnIsOff: (VPNController.state === VPNController.StateOff) VPNMenu { id: menu objectName: "settingsNetworkingBackButton" //% "Network settings" title: qsTrId("vpn.settings.networking") isSettingsView: true } VPNCheckBoxRow { id: ipv6 objectName: "settingIpv6Enabled" anchors.top: menu.bottom anchors.topMargin: Theme.windowMargin width: parent.width - Theme.windowMargin //% "IPv6" labelText: qsTrId("vpn.settings.ipv6") //% "Push the internet forward with the latest version of the Internet Protocol" subLabelText: qsTrId("vpn.settings.ipv6.description") isChecked: (VPNSettings.ipv6Enabled) isEnabled: vpnFlickable.vpnIsOff showDivider: vpnFlickable.vpnIsOff onClicked: { if (vpnFlickable.vpnIsOff) { VPNSettings.ipv6Enabled = !VPNSettings.ipv6Enabled } } } VPNCheckBoxRow { id: localNetwork objectName: "settingLocalNetworkAccess" anchors.top: ipv6.bottom anchors.topMargin: Theme.windowMargin width: parent.width - Theme.windowMargin visible: VPNFeatureList.localNetworkAccessSupported //% "Local network access" labelText: qsTrId("vpn.settings.lanAccess") //% "Access printers, streaming sticks and all other devices on your local network" subLabelText: qsTrId("vpn.settings.lanAccess.description") isChecked: (VPNSettings.localNetworkAccess) isEnabled: vpnFlickable.vpnIsOff showDivider: vpnFlickable.vpnIsOff onClicked: { if (vpnFlickable.vpnIsOff) { VPNSettings.localNetworkAccess = !VPNSettings.localNetworkAccess } } } VPNCheckBoxAlert { anchors.top: localNetwork.visible ? localNetwork.bottom : ipv6.bottom visible: !vpnFlickable.vpnIsOff //% "VPN must be off to edit these settings" //: Associated to a group of settings that require the VPN to be disconnected to change errorMessage: qsTrId("vpn.settings.vpnMustBeOff") } } mozilla-vpn-client-2.2.0/src/ui/settings/ViewNotifications.qml000066400000000000000000000055401404202232700244620ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme VPNFlickable { id: vpnFlickable property bool vpnIsOff: (VPNController.state === VPNController.StateOff) VPNMenu { id: menu objectName: "settingsNotificationsBackButton" //% "Notifications" title: qsTrId("vpn.settings.notifications") isSettingsView: true } VPNCheckBoxRow { id: captivePortalAlert objectName: "settingCaptivePortalAlert" anchors.top: menu.bottom anchors.topMargin: Theme.windowMargin width: parent.width - Theme.windowMargin visible: VPNFeatureList.captivePortalNotificationSupported //% "Guest Wi-Fi portal alert" labelText: qsTrId("vpn.settings.guestWifiAlert") //% "Get notified if a guest Wi-Fi portal is blocked due to VPN connection" subLabelText: qsTrId("vpn.settings.guestWifiAlert.description") isChecked: (VPNSettings.captivePortalAlert) isEnabled: vpnFlickable.vpnIsOff showDivider: vpnFlickable.vpnIsOff onClicked: { if (vpnFlickable.vpnIsOff) { VPNSettings.captivePortalAlert = !VPNSettings.captivePortalAlert } } } VPNCheckBoxRow { id: unsecuredNetworkAlert objectName: "settingUnsecuredNetworkAlert" anchors.top: VPNFeatureList.captivePortalNotificationSupported ? captivePortalAlert.bottom : menu.bottom anchors.topMargin: Theme.windowMargin width: parent.width - Theme.windowMargin visible: VPNFeatureList.unsecuredNetworkNotificationSupported //% "Unsecured network alert" labelText: qsTrId("vpn.settings.unsecuredNetworkAlert") //% "Get notified if you connect to an unsecured Wi-Fi network" subLabelText: qsTrId("vpn.settings.unsecuredNetworkAlert.description") isChecked: (VPNSettings.unsecuredNetworkAlert) isEnabled: vpnFlickable.vpnIsOff showDivider: vpnFlickable.vpnIsOff onClicked: { if (vpnFlickable.vpnIsOff) { VPNSettings.unsecuredNetworkAlert = !VPNSettings.unsecuredNetworkAlert } } } VPNCheckBoxAlert { anchors.top: VPNFeatureList.unsecuredNetworkNotificationSupported ? unsecuredNetworkAlert.bottom : captivePortalAlert.bottom visible: !vpnFlickable.vpnIsOff //% "VPN must be off to edit these settings" //: Associated to a group of settings that require the VPN to be disconnected to change errorMessage: qsTrId("vpn.settings.vpnMustBeOff") } } mozilla-vpn-client-2.2.0/src/ui/settings/ViewSettingsMenu.qml000066400000000000000000000140351404202232700242750ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme VPNFlickable { id: vpnFlickable objectName: "settingsView" width: window.width flickContentHeight: settingsList.y + settingsList.height + signOutLink.height + signOutLink.anchors.bottomMargin hideScollBarOnStackTransition: true VPNIconButton { id: iconButton objectName: "settingsCloseButton" onClicked: stackview.pop(StackView.Immediate) anchors.top: parent.top anchors.left: parent.left anchors.topMargin: Theme.windowMargin / 2 anchors.leftMargin: Theme.windowMargin / 2 accessibleName: qsTrId("vpn.connectionInfo.close") Image { id: backImage source: "../resources/close-dark.svg" sourceSize.width: Theme.iconSize fillMode: Image.PreserveAspectFit anchors.centerIn: iconButton } } VPNPanel { id: vpnPanel logoSize: 80 logo: VPNUser.avatar //% "VPN User" readonly property var textVpnUser: qsTrId("vpn.settings.user") logoTitle: VPNUser.displayName ? VPNUser.displayName : textVpnUser logoSubtitle: VPNUser.email anchors.top: parent.top anchors.topMargin: (Math.max(window.safeContentHeight * .08, Theme.windowMargin * 2)) maskImage: true isSettingsView: true } VPNButton { id: manageAccountButton objectName: "manageAccountButton" text: qsTrId("vpn.main.manageAccount") anchors.top: vpnPanel.bottom anchors.topMargin: Theme.vSpacing anchors.horizontalCenter: parent.horizontalCenter onClicked: VPN.openLink(VPN.LinkAccount) } VPNCheckBoxRow { id: startAtBootCheckBox objectName: "settingStartAtBoot" //% "Launch VPN app on Startup" labelText: qsTrId("vpn.settings.runOnBoot") subLabelText: "" isChecked: VPNSettings.startAtBoot isEnabled: true showDivider: true anchors.top: manageAccountButton.bottom anchors.topMargin: Theme.hSpacing * 1.5 anchors.rightMargin: Theme.hSpacing width: vpnFlickable.width - Theme.hSpacing onClicked: VPNSettings.startAtBoot = !VPNSettings.startAtBoot visible: VPNFeatureList.startOnBootSupported } Component { id: getHelpComponent VPNGetHelp { isSettingsView: true } } Component { id: aboutUsComponent VPNAboutUs { isSettingsView: true } } ColumnLayout { id: settingsList spacing: Theme.listSpacing y: Theme.vSpacing + (VPNFeatureList.startOnBootSupported ? startAtBootCheckBox.y + startAtBootCheckBox.height : manageAccountButton.y + manageAccountButton.height) width: parent.width - Theme.windowMargin anchors.horizontalCenter: parent.horizontalCenter VPNSettingsItem { objectName: "settingsNetworking" settingTitle: qsTrId("vpn.settings.networking") imageLeftSrc: "../resources/settings/networkSettings.svg" imageRightSrc: "../resources/chevron.svg" onClicked: settingsStackView.push("../settings/ViewNetworkSettings.qml") } VPNSettingsItem { objectName: "settingsNotifications" settingTitle: qsTrId("vpn.settings.notifications") imageLeftSrc: "../resources/settings/notifications.svg" imageRightSrc: "../resources/chevron.svg" onClicked: settingsStackView.push("../settings/ViewNotifications.qml") visible: VPNFeatureList.captivePortalNotificationSupported || VPNFeatureList.unsecuredNetworkNotificationSupported } VPNSettingsItem { objectName: "settingsLanguages" settingTitle: qsTrId("vpn.settings.language") imageLeftSrc: "../resources/settings/language.svg" imageRightSrc: "../resources/chevron.svg" onClicked: settingsStackView.push("../settings/ViewLanguage.qml") visible: VPNLocalizer.hasLanguages } VPNSettingsItem { //% "App Permissions" settingTitle: qsTrId("vpn.settings.appPermissions") imageLeftSrc: "../resources/settings/apps.svg" imageRightSrc: "../resources/chevron.svg" visible: VPNFeatureList.protectSelectedAppsSupported onClicked: settingsStackView.push("../settings/ViewAppPermissions.qml") } VPNSettingsItem { objectName: "settingsAboutUs" settingTitle: qsTrId("vpn.settings.aboutUs") imageLeftSrc: "../resources/settings/aboutUs.svg" imageRightSrc: "../resources/chevron.svg" onClicked: settingsStackView.push(aboutUsComponent) } VPNSettingsItem { objectName: "settingsGiveFeedback" //% "Give feedback" settingTitle: qsTrId("vpn.settings.giveFeedback") imageLeftSrc: "../resources/settings/feedback.svg" imageRightSrc: "../resources/externalLink.svg" onClicked: VPN.openLink(VPN.LinkFeedback) } VPNSettingsItem { objectName: "settingsGetHelp" settingTitle: qsTrId("vpn.main.getHelp") imageLeftSrc: "../resources/settings/getHelp.svg" imageRightSrc: "../resources/chevron.svg" onClicked: settingsStackView.push(getHelpComponent) } Rectangle { Layout.preferredHeight: fullscreenRequired? Theme.rowHeight * 1.5 : Theme.rowHeight Layout.fillWidth: true color: "transparent" } } VPNSignOut { id: signOutLink objectName: "settingsLogout" onClicked: VPNController.logout() } } mozilla-vpn-client-2.2.0/src/ui/states/000077500000000000000000000000001404202232700177425ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/states/StateAuthenticating.qml000066400000000000000000000006271404202232700244320ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import "../components" VPNLoader { objectName: "authenticatingView" //% "Waiting for sign in and subscription confirmation…" headlineText: qsTrId("vpn.authenticating.waitForSignIn") } mozilla-vpn-client-2.2.0/src/ui/states/StateBackendFailure.qml000066400000000000000000000021311404202232700243120ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import Mozilla.VPN 1.0 import "../components" VPNStackView { id: stackview function handleButtonClick() { VPN.triggerHeartbeat(); } Component.onCompleted:stackview.push( "../views/ViewErrorFullScreen.qml", { //% "Something went wrong…" headlineText: qsTrId("vpn.errors.somethingWentWrong"), //% "Unable to establish a connection at this time. We’re working hard to resolve the issue. Please try again shortly." errorMessage: qsTrId("vpn.errors.unableToEstablishConnection"), //% "Try Again" buttonText: qsTrId("vpn.errors.tryAgain"), buttonObjectName: "heartbeatTryButton", buttonOnClick: stackview.handleButtonClick, signOffLinkVisible: false, getHelpLinkVisible: true } ) } mozilla-vpn-client-2.2.0/src/ui/states/StateDeviceLimit.qml000066400000000000000000000005471404202232700236620ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import "../components" VPNStackView { id: stackview initialItem: "../views/ViewDevices.qml" } mozilla-vpn-client-2.2.0/src/ui/states/StateInitialize.qml000066400000000000000000000006401404202232700235570ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import "../components" Item { VPNStackView { id: stackview anchors.fill: parent initialItem: "../views/ViewInitialize.qml" } } mozilla-vpn-client-2.2.0/src/ui/states/StateMain.qml000066400000000000000000000005441404202232700223450ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import "../components" VPNStackView { id: stackview initialItem: "../views/ViewMain.qml" } mozilla-vpn-client-2.2.0/src/ui/states/StatePostAuthentication.qml000066400000000000000000000032271404202232700253070ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtGraphicalEffects 1.14 import Mozilla.VPN 1.0 import "../themes/themes.js" as Theme import "../components" Item { Component.onCompleted: fade.start() VPNHeadline { id: headline anchors.top: parent.top anchors.topMargin: parent.height * 0.08 anchors.horizontalCenter: parent.horizontalCenter //% "Quick access" text: qsTrId("vpn.postAuthentication..quickAccess") } VPNSubtitle { id: logoSubtitle //% "You can quickly access Mozilla VPN from your status bar." text: qsTrId("vpn.postAuthentication.statusBarIntro") anchors.top: headline.bottom anchors.topMargin: 12 anchors.horizontalCenter: parent.horizontalCenter } Image { source: "../resources/quick-access.svg" sourceSize.height: 120 sourceSize.width: 120 anchors.centerIn: parent } VPNButton { id: button objectName: "postAuthenticationButton" //% "Continue" text: qsTrId("vpn.postAuthentication.continue") anchors.horizontalCenterOffset: 0 anchors.horizontalCenter: parent.horizontalCenter anchors.bottom: parent.bottom anchors.bottomMargin: 32 radius: 5 onClicked: VPN.postAuthenticationCompleted() } PropertyAnimation on opacity { id: fade from: 0 to: 1 duration: 1000 } } mozilla-vpn-client-2.2.0/src/ui/states/StateSubscriptionBlocked.qml000066400000000000000000000024401404202232700254260ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme VPNStackView { id: stackview function handleButtonClick() { VPN.openLink(VPN.LinkSubscriptionsBlocked) } Component.onCompleted:stackview.push( "../views/ViewErrorFullScreen.qml", { //% "Error confirming subscription…" headlineText: qsTrId("vpn.subscriptionBlocked.title"), //% "Another Firefox Account has already subscribed using this Apple ID." errorMessage:qsTrId("vpn.subscriptionBlocked.anotherFxaSubscribed"), //% "Visit our help center to learn more about managing your subscriptions." errorMessage2: qsTrId("vpn.subscriptionBlocked.visitHelpCenter"), //% "Get Help" buttonText: qsTrId("vpn.subscriptionBlocked.getHelp"), buttonObjectName: "errorGetHelpButton", buttonOnClick: stackview.handleButtonClick, signOffLinkVisible: true, getHelpLinkVisible: false } ) } mozilla-vpn-client-2.2.0/src/ui/states/StateSubscriptionNeeded.qml000066400000000000000000000005271404202232700252530ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 StackView { id: stackview initialItem: "../views/ViewSubscriptionNeeded.qml" } mozilla-vpn-client-2.2.0/src/ui/states/StateSubscriptionValidation.qml000066400000000000000000000005441404202232700261600ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import "../components" VPNLoader { //% "Please wait…" headlineText: qsTrId("vpn.subscription.pleaseWait") footerLinkIsVisible: false } mozilla-vpn-client-2.2.0/src/ui/states/StateUpdateRequired.qml000066400000000000000000000004431404202232700244020ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 Loader { id: loader source: "../views/ViewUpdate.qml" } mozilla-vpn-client-2.2.0/src/ui/themes/000077500000000000000000000000001404202232700177245ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/themes/themes.js000066400000000000000000000107001404202232700215450ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ .pragma library const bgColor = "#F9F9FA"; const bgColor30 = "#4DF9F9FA"; const bgColor80 = "#CCF9F9FA"; const bgColorTransparent = "#00F9F9FA"; const blue = "#0060DF"; const blueHovered = "#0250BB"; const bluePressed = "#054096"; const blueDisabled = "#a3c0f3"; const blueFocusOutline = "#4d0a84ff"; const blueFocusBorder = "#0a84ff"; const divider = "#0C0C0D0A" const green = "#3FE1B0"; const grey = "#CACACA"; const greyHovered = "#E6E6E6"; const greyPressed = "#C2C2C2"; const ink = "#321C64"; const orange = "#FFA436"; const red = "#FF4F5E"; const redHovered = "#E22850"; const redPressed = "#C50042"; const redfocusOutline = "#66C50042"; const white = "#FFFFFF" const fontColor = "#6D6D6E"; const fontColorDark = "#3D3D3D"; const fontFamily = "Metropolis"; const fontBoldFamily = "MetropolisSemiBold"; const fontInterFamily = "InterUI"; const fontSize = 15; const fontSizeLarge = 22; const fontSizeSmall = 13; const fontSizeSmallest = 11; const fontWeightBold = 600; const iconSize = 16; const labelLineHeight = 22; const cityListTopMargin = 18; const controllerInterLineHeight = 18; const hSpacing = 20; const vSpacing = 24; const listSpacing = 8; const maxTextWidth = 296; const windowMargin = 16; const desktopAppHeight = 520; const desktopAppWidth = 360; const darkFocusBorder = fontColor; const lightFocusBorder = "#d5d3e0"; const blueButton = { "defaultColor" : blue, "buttonHovered": blueHovered, "buttonPressed": bluePressed, "buttonDisabled": blueDisabled, "focusBgColor": blue, "focusOutline": blueFocusOutline, "focusBorder": blueFocusBorder, }; const wasmOptionBtn = { "defaultColor" : "#00eeeeee", "buttonHovered": "#330a84ff", "buttonPressed": "#4d0a84ff", "buttonDisabled": blueDisabled, "focusBgColor": blue, "focusOutline": blueFocusOutline, "focusBorder": blueFocusBorder, }; const clickableRowBlue = { "defaultColor": bgColor, "buttonHovered": "#D4E2F6", "buttonPressed": "#AECBF2", "focusOutline": bgColorTransparent, "focusBorder": blueFocusBorder, }; const iconButtonLightBackground = { "defaultColor": bgColorTransparent, "buttonHovered": greyHovered, "buttonPressed": greyPressed, "focusOutline": bgColorTransparent, "focusBorder": darkFocusBorder, }; const iconButtonDarkBackground = { "defaultColor": "#00321C64", "buttonHovered": "#5b4983", "buttonPressed": "#8477a2", "focusOutline": "#005b4983", "focusBorder": lightFocusBorder, }; const linkButton = { "defaultColor" : bgColorTransparent, "buttonHovered": bgColorTransparent, "buttonPressed": bgColorTransparent, "focusOutline": bgColorTransparent, "focusBorder": darkFocusBorder, }; const popupButtonCancel = { "defaultColor": grey, "buttonHovered": "#CCCCCC", "buttonPressed": greyPressed, "focusOutline": greyPressed, "focusBorder": darkFocusBorder }; const redButton = { "defaultColor" : red, "buttonHovered": redHovered, "buttonPressed": redPressed, "focusOutline": redfocusOutline, "focusBorder": redPressed, }; const removeDeviceBtn = { "defaultColor": bgColorTransparent, "buttonHovered": "#FFDFE7", "buttonPressed": "#FFBDC5", "focusOutline": bgColorTransparent, "focusBorder": red, }; const vpnToggleConnected = { "defaultColor": "#3FE1B0", "buttonHovered": "#3AD4B3", "buttonPressed": "#1CC5A0", "focusOutline": bgColor30, "focusBorder": lightFocusBorder, }; const vpnToggleDisconnected = { "defaultColor": "#9E9E9E", "buttonHovered": fontColor, "buttonPressed": fontColorDark, "focusOutline": "transparent", "focusBorder": darkFocusBorder, }; const cornerRadius = 4; const focusBorderWidth = 2; // In milliseconds, the animation of a single device removal const removeDeviceAnimation = 300; const rowHeight = 40; const settingsMaxContentHeight = 740; const maxHorizontalContentWidth = 460; const uiState = { "stateDefault": "state-default", "stateHovered": "state-hovered", "statePressed": "state-pressed", "stateFocused": "state-focused", "stateLoading": "state-loading", }; const greyLink = { "defaultColor": "#B30C0C0D", "buttonHovered": "#CC0C0C0D", "buttonPressed": "#FF0C0C0D", "focusOutline": "#FF0C0C0D", "focusBorder": "#000000" }; mozilla-vpn-client-2.2.0/src/ui/views/000077500000000000000000000000001404202232700175745ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/ui/views/ViewDevices.qml000066400000000000000000000115751404202232700225350ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtGraphicalEffects 1.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme Item { id: root property var isModalDialogOpened: removePopup.visible property var wasmView height: window.safeContentHeight width: window.width VPNMenu { id: menu objectName: "deviceListBackButton" //% "My devices" title: qsTrId("vpn.devices.myDevices") accessibleIgnored: isModalDialogOpened } VPNFlickable { id: vpnFlickable anchors.top: menu.bottom height: root.height - menu.height width: root.width interactive: true flickContentHeight: maxDevicesReached.height + content.height + col.height contentHeight: maxDevicesReached.height + content.height + col.height contentWidth: window.width state: VPN.state !== VPN.StateDeviceLimit ? "active" : "deviceLimit" Component.onCompleted: { if (wasmView) { state = "deviceLimit" } } states: [ State { name: "active" // normal mode PropertyChanges { target: menu rightTitle: qsTrId("vpn.devices.activeVsMaxDeviceCount").arg(VPNDeviceModel.activeDevices).arg(VPNUser.maxDevices) } PropertyChanges { target: col visible: false } PropertyChanges { target: menu btnDisabled: false } }, State { name: "deviceLimit" PropertyChanges { target: menu rightTitle: qsTrId("vpn.devices.activeVsMaxDeviceCount").arg(VPNDeviceModel.activeDevices + 1).arg(VPNUser.maxDevices) } PropertyChanges { target: col visible: true } PropertyChanges { target: menu btnDisabled: true } } ] VPNDevicesListHeader { id: maxDevicesReached width: root.width } ColumnLayout { id: content width: vpnFlickable.width anchors.top: maxDevicesReached.bottom anchors.topMargin: Theme.windowMargin / 2 spacing: Theme.windowMargin / 2 Repeater { id: deviceList model: VPNDeviceModel Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true delegate: VPNDeviceListItem{} } VPNVerticalSpacer { Layout.preferredHeight: 1 } ColumnLayout { id: col spacing: 0 Layout.preferredWidth: vpnFlickable.width Layout.alignment: Qt.AlignHCenter VPNVerticalSpacer { Layout.preferredHeight: Theme.windowMargin * 2 Layout.preferredWidth: vpnFlickable.width - Theme.windowMargin * 2 Layout.alignment: Qt.AlignHCenter Rectangle { id: divider height: 1 width: parent.width anchors.centerIn: parent color: "#e7e7e7" } } VPNLinkButton { id: getHelpLink labelText: qsTrId("vpn.main.getHelp") Layout.alignment: Qt.AlignHCenter onClicked: stackview.push(getHelpComponent) Layout.preferredHeight: Theme.rowHeight } VPNSignOut { id: signOff anchors.bottom: undefined anchors.horizontalCenter: undefined anchors.bottomMargin: undefined Layout.alignment: Qt.AlignHCenter Layout.preferredHeight: Theme.rowHeight onClicked: { VPNController.logout(); } } } } } VPNRemoveDevicePopup { id: removePopup function initializeAndOpen(name) { removePopup.deviceName = name; removePopup.open(); } } Component.onCompleted: VPN.refreshDevices() Component { id: getHelpComponent VPNGetHelp { isSettingsView: false } } } mozilla-vpn-client-2.2.0/src/ui/views/ViewErrorFullScreen.qml000066400000000000000000000112011404202232700242110ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme VPNFlickable { property var headlineText property var errorMessage: "" property var errorMessage2: "" property var buttonText property var buttonObjectName property var buttonOnClick property var signOffLinkVisible: false property var getHelpLinkVisible: false id: vpnFlickable Component.onCompleted: { flickContentHeight = col.childrenRect.height + col.anchors.topMargin } ColumnLayout { id: col width: vpnFlickable.width anchors.top: parent.top anchors.topMargin: vpnFlickable.height * 0.08 anchors.horizontalCenter: parent.horizontalCenter spacing: 32 Component.onCompleted: { height = Math.max(window.height, childrenRect.height) } VPNHeadline { id: headline text: headlineText Layout.preferredHeight: paintedHeight Layout.preferredWidth: col.width - (Theme.windowMargin * 2) Layout.maximumWidth: 500 } ColumnLayout { spacing: 24 Layout.alignment: Qt.AlignHCenter Rectangle { id: warningIconWrapper Layout.preferredHeight: 48 Layout.preferredWidth: 48 Layout.alignment: Qt.AlignHCenter; color: Theme.red radius: height / 2 Image { source: "../resources/warning-white.svg" antialiasing: true sourceSize.height: 20 sourceSize.width: 20 anchors.centerIn: parent } } ColumnLayout { spacing: Theme.windowMargin Layout.alignment: Qt.AlignHCenter VPNTextBlock { id: copyBlock1 Layout.preferredWidth: Theme.maxTextWidth Layout.preferredHeight: paintedHeight Layout.alignment: Qt.AlignHCenter horizontalAlignment: Text.AlignHCenter font.pixelSize: Theme.fontSize lineHeight: 22 text: errorMessage } VPNTextBlock { id: copyBlock2 Layout.preferredWidth: Theme.maxTextWidth horizontalAlignment: Text.AlignHCenter Layout.preferredHeight: paintedHeight Layout.alignment: Qt.AlignHCenter font.pixelSize: Theme.fontSize lineHeight: 22 text: errorMessage2 } } } ColumnLayout { spacing: Theme.windowMargin Layout.fillWidth: true Layout.preferredWidth: parent.width Layout.alignment: Qt.AlignHCenter VPNButton { id: btn objectName: buttonObjectName text: buttonText Layout.preferredHeight: Theme.rowHeight loaderVisible: false onClicked: buttonOnClick() } VPNFooterLink { id: getHelpLink visible: getHelpLinkVisible Layout.preferredHeight: Theme.rowHeight Layout.alignment: Qt.AlignHCenter labelText: qsTrId("vpn.main.getHelp") anchors.horizontalCenter: undefined anchors.bottom: undefined anchors.bottomMargin: undefined onClicked: stackview.push(getHelpComponent) } VPNSignOut { id: signOff visible: signOffLinkVisible Layout.preferredHeight: Theme.rowHeight Layout.alignment: Qt.AlignHCenter anchors.horizontalCenter: undefined anchors.bottom: undefined anchors.bottomMargin: undefined height: undefined onClicked: { VPNController.logout(); } } } Component { id: getHelpComponent VPNGetHelp { isSettingsView: false } } VPNVerticalSpacer { Layout.preferredHeight: Theme.windowMargin * 2 } } } mozilla-vpn-client-2.2.0/src/ui/views/ViewInitialize.qml000066400000000000000000000031611404202232700232440ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme Item { id: viewInitialize VPNHeaderLink { id: headerLink objectName: "getHelpLink" labelText: qsTrId("vpn.main.getHelp") onClicked: stackview.push(getHelpComponent) } Component { id: getHelpComponent VPNGetHelp { isSettingsView: false } } VPNPanel { logo: "../resources/logo.svg" logoTitle: qsTrId("vpn.main.productName") //% "A fast, secure and easy to use VPN. Built by the makers of Firefox." logoSubtitle: qsTrId("vpn.main.productDescription") logoSize: 80 height: parent.height - (getStarted.height + getStarted.anchors.bottomMargin + learnMore.height + learnMore.anchors.bottomMargin) } VPNButton { id: getStarted objectName: "getStarted" anchors.bottom: learnMore.top anchors.bottomMargin: 24 //% "Get started" text: qsTrId("vpn.main.getStarted") anchors.horizontalCenterOffset: 0 anchors.horizontalCenter: parent.horizontalCenter radius: 5 onClicked: VPN.authenticate() } VPNFooterLink { id: learnMore objectName: "learnMoreLink" //% "Learn more" labelText: qsTrId("vpn.main.learnMore") onClicked: stackview.push("ViewOnboarding.qml") } } mozilla-vpn-client-2.2.0/src/ui/views/ViewLogs.qml000066400000000000000000000066421404202232700220560ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ // IMPORTANT: this file is used only for mobile builds. import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme Item { id: logs width: window.width height: window.safeContentHeight Component.onCompleted: VPNCloseEventHandler.addView(logs) VPNMenu { id: menu isMainView: true //% "View Logs" title: qsTrId("vpn.viewlogs.title") } ScrollView { id: logScrollView height: logs.height - menu.height - copyClearWrapper.height width: logs.width anchors.top: menu.bottom anchors.horizontalCenter: logs.horizontalCenter contentWidth: width - (Theme.windowMargin * 2) clip: true topInset: Theme.rowHeight bottomInset: Theme.rowHeight * 2 leftPadding: Theme.windowMargin rightPadding: Theme.windowMargin ScrollBar.horizontal.policy: ScrollBar.AlwaysOff ScrollBar.vertical.policy: ScrollBar.AlwaysOn VPNTextBlock { id: logText y: Theme.windowMargin font.pixelSize: 11 lineHeight: 16 width: parent.width Connections { target: VPN function onLogsReady(logs) { logText.text = logs; } } Component.onCompleted: { VPN.retrieveLogs(); } } } Rectangle { id: div width: parent.width height: 1 color: Theme.divider Layout.alignment: Qt.AlignRight anchors.bottom: copyClearWrapper.top } Rectangle { id: copyClearWrapper color: Theme.bgColor height: Theme.rowHeight * 1.5 anchors.bottom: parent.bottom width: logs.width RowLayout { spacing: 0 height: parent.height width: copyClearWrapper.width VPNLogsButton { //% "Copy" buttonText: qsTrId("vpn.logs.copy") iconSource: "../resources/copy.svg" onClicked: { VPN.storeInClipboard(logText.text); //% "Copied!" buttonText = qsTrId("vpn.logs.copied"); } } Rectangle { Layout.preferredHeight: parent.height Layout.preferredWidth: 1 color: Theme.divider Layout.alignment: Qt.AlignRight } VPNLogsButton { //% "Clear" buttonText: qsTrId("vpn.logs.clear") iconSource: "../resources/delete.svg" onClicked: { VPN.cleanupLogs(); logText.text = ""; } } } Rectangle { id: divider height: 1 width: parent.width anchors.bottom: parent.bottom color: "#0C0C0D0A" } } Connections { function onGoBack(item) { if (item === logs) mainStackView.pop(); } target: VPNCloseEventHandler } } mozilla-vpn-client-2.2.0/src/ui/views/ViewMain.qml000066400000000000000000000147361404202232700220410ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtGraphicalEffects 1.14 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme VPNFlickable { id: mainView flickContentHeight: { let flickHeight = Theme.desktopAppHeight - 1 if (alertBox.visible) { flickHeight += alertBox.height + Theme.windowMargin; } if (mobileHeader.visible) { flickHeight += mobileHeader.height; } return flickHeight; } states: [ State { when: window.fullscreenRequired() PropertyChanges { target: mobileHeader visible: true } PropertyChanges { target: mainContent y: alertBox.visible ? alertBox.height + Theme.windowMargin + mobileHeader.height : mobileHeader.height } PropertyChanges { target: alertBox y: alertBox.visible ? mobileHeader.height + Theme.windowMargin : 0 } }, State { when: !window.fullscreenRequired() PropertyChanges { target: mobileHeader visible: false } PropertyChanges { target: mainContent y: alertBox.visible ? alertBox.height + Theme.windowMargin : 0 } PropertyChanges { target: alertBox y: Theme.windowMargin } } ] Item { id: mobileHeader height: Theme.rowHeight * 1.5 width: parent.width anchors.top: parent.top anchors.topMargin: Theme.windowMargin / 2 visible: window.fullscreenRequired() Row { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter spacing: 6 VPNIcon { source: VPNStatusIcon.iconUrl sourceSize.height: 20 sourceSize.width: 20 antialiasing: true } VPNBoldLabel { //% "Mozilla VPN" text: qsTrId("MozillaVPN") color: "#000000" anchors.verticalCenter: parent.verticalCenter } } } VPNAlert { id: alertBox state: VPN.updateRecommended ? "recommended" : "" alertType: "update" alertColor: Theme.blueButton visible: state === "recommended" onVisibleChanged: if (visible) showAlert.start(); //% "New version is available." alertText: qsTrId("vpn.updates.newVersionAvailable") //% "Update now" alertLinkText: qsTrId("vpn.updates.updateNow") width: parent.width - (Theme.windowMargin * 2) PropertyAnimation { id: showAlert target: alertBox property: "opacity" from: 0 to: 1 duration: 200 } SequentialAnimation { id: closeAlert ParallelAnimation { PropertyAnimation { target: alertBox property: "opacity" to: 0 duration: 100 } PropertyAnimation { target: mainView property: "flickContentHeight" to: mainView.flickContentHeight - alertBox.height - Theme.windowMargin duration: 200 } PropertyAnimation { target: mainContent property: "y" to : { if (mobileHeader.visible) { return mobileHeader.height } return 0 } duration: 200 } } PropertyAction { target: alertBox property: "visible" value: "false" } } } Item { id: mainContent width: parent.width VPNDropShadow { anchors.fill: box source: box } VPNControllerView { id: box } VPNControllerNav { function handleClick() { stackview.push("ViewServers.qml") } id: serverInfo objectName: "serverListButton" //% "Select location" //: Select the Location of the VPN server titleText: qsTrId("vpn.servers.selectLocation") //% "current location - %1" //: Accessibility description for current location of the VPN server descriptionText: qsTrId("vpn.servers.currentLocation").arg(VPNLocalizer.translateServerCity(VPNCurrentServer.countryCode, VPNCurrentServer.city)) subtitleText: VPNLocalizer.translateServerCity(VPNCurrentServer.countryCode, VPNCurrentServer.city) imgSource: "../resources/flags/" + VPNCurrentServer.countryCode.toUpperCase() + ".png" anchors.top: box.bottom anchors.topMargin: 30 disableRowWhen: (VPNController.state !== VPNController.StateOn && VPNController.state !== VPNController.StateOff) || box.connectionInfoVisible } VPNControllerNav { function handleClick() { stackview.push("ViewDevices.qml") } objectName: "deviceListButton" anchors.top: serverInfo.bottom anchors.topMargin: 22 //% "%1 of %2" //: Example: You have "x of y" devices in your account, where y is the limit of allowed devices. subtitleText: qsTrId("vpn.devices.activeVsMaxDeviceCount").arg(VPNDeviceModel.activeDevices + (VPN.state !== VPN.StateDeviceLimit ? 0 : 1)).arg(VPNUser.maxDevices) imgSource: "../resources/devices.svg" imgIsVector: true imgSize: 24 //% "My devices" titleText: qsTrId("vpn.devices.myDevices") disableRowWhen: box.connectionInfoVisible } Behavior on y { PropertyAnimation { duration: 200 easing.type: Easing.OutCurve } } } } mozilla-vpn-client-2.2.0/src/ui/views/ViewOnboarding.qml000066400000000000000000000123761404202232700232350ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtGraphicalEffects 1.14 import Mozilla.VPN 1.0 import QtQuick.Controls 2.14 import "../components" import "../themes/themes.js" as Theme Item { id: onboardingPanel property real panelHeight: window.height - (nextPanel.height + nextPanel.anchors.bottomMargin + progressIndicator.height + progressIndicator.anchors.bottomMargin) property real panelWidth: window.width width: window.width anchors.horizontalCenter: window.horizontalCenter SwipeView { id: swipeView currentIndex: 0 height: onboardingPanel.panelHeight width: onboardingPanel.width anchors.horizontalCenter: onboardingPanel.horizontalCenter ListModel { id: onboardingModel ListElement { image: "../resources/onboarding/onboarding1.svg" //% "Device-level encryption" headline: qsTrId("vpn.onboarding.headline.1") //% "Encrypt your traffic so that it can’t be read by your ISP or eavesdroppers." subtitle: qsTrId("vpn.onboarding.subtitle.1") } ListElement { image: "../resources/onboarding/onboarding2.svg" //: The + after the number stands for “more than”. If you change the number of countries here, please update ViewSubscriptionNeeded.qml too. //% "Servers in 30+ countries" headline: qsTrId("vpn.onboarding.headline.2") //% "Pick a server in any country you want and hide your location to throw off trackers." subtitle: qsTrId("vpn.onboarding.subtitle.2") } ListElement { image: "../resources/onboarding/onboarding3.svg" //% "No bandwidth restrictions" headline: qsTrId("vpn.onboarding.headline.3") //% "Stream, download, and game without limits, monthly caps or ISP throttling." subtitle: qsTrId("vpn.onboarding.subtitle.3") } ListElement { image: "../resources/onboarding/onboarding4.svg" //% "No online activity logs" headline: qsTrId("vpn.onboarding.headline.4") //% "We are committed to not monitoring or logging your browsing or network history." subtitle: qsTrId("vpn.onboarding.subtitle.4") } } Repeater { id: repeater model: onboardingModel anchors.fill: parent Loader { height: onboardingPanel.panelHeight active: SwipeView.isCurrentItem sourceComponent: VPNPanel { logo: image logoTitle: headline logoSubtitle: subtitle height: nextPanel.y Component.onCompleted: fade.start() PropertyAnimation on opacity { id: fade from: 0 to: 1 duration: 800 } } } } } VPNHeaderLink { objectName: "skipOnboarding" //% "Skip" labelText: qsTrId("vpn.onboarding.skip") onClicked: stackview.pop() visible: swipeView.currentIndex !== swipeView.count - 1 } VPNButton { id: nextPanel objectName: "onboardingNext" //% "Next" readonly property var textNext: qsTrId("vpn.onboarding.next") text: swipeView.currentIndex === swipeView.count - 1 ? qsTrId("vpn.main.getStarted") : textNext anchors.horizontalCenterOffset: 0 anchors.horizontalCenter: onboardingPanel.horizontalCenter anchors.bottom: progressIndicator.top anchors.bottomMargin: 28 radius: Theme.cornerRadius onClicked: swipeView.currentIndex < swipeView.count - 1 ? swipeView.currentIndex++ : VPN.authenticate() } PageIndicator { id: progressIndicator interactive: false count: swipeView.count currentIndex: swipeView.currentIndex anchors.bottom: parent.bottom anchors.bottomMargin: Math.min(window.height * 0.08, 60) - 4 anchors.horizontalCenter: parent.horizontalCenter spacing: Theme.windowMargin / 2 delegate: Rectangle { id: circle color: index === swipeView.currentIndex ? Theme.blue : Theme.greyPressed height: 6 width: 6 radius: 6 Behavior on color { ColorAnimation { duration: 400 } } } } Component.onCompleted: VPNCloseEventHandler.addView(onboardingPanel) Connections { target: VPNCloseEventHandler function onGoBack(item) { if (item !== onboardingPanel) { return; } if (swipeView.currentIndex === 0) { stackview.pop(); return; } swipeView.currentIndex--; } } } mozilla-vpn-client-2.2.0/src/ui/views/ViewServers.qml000066400000000000000000000100551404202232700225740ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import QtQml.Models 2.2 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme Item { id: root VPNMenu { id: menu objectName: "serverListBackButton" title: qsTrId("vpn.servers.selectLocation") onActiveFocusChanged: if (focus) forceFocus = true } FocusScope { id: focusScope property var lastFocusedItemIdx height: parent.height - menu.height anchors.top: menu.bottom width: parent.width onActiveFocusChanged: if (focus && lastFocusedItemIdx) repeater.itemAt(lastFocusedItemIdx).forceActiveFocus() Accessible.name: menu.title Accessible.role: Accessible.List ButtonGroup { id: radioButtonGroup } VPNFlickable { id: vpnFlickable objectName: "serverCountryView" flickContentHeight: serverList.y + serverList.implicitHeight + (Theme.rowHeight * 2) anchors.fill: parent NumberAnimation on contentY { id: scrollAnimation duration: 200 easing.type: Easing.OutQuad } Rectangle { id: verticalSpacer height: Theme.windowMargin / 2 width: parent.width color: "transparent" } Column { id: serverList objectName: "serverCountryList" spacing: 14 width: parent.width anchors.top: verticalSpacer.bottom Component.onCompleted: { // Scroll vpnFlickable so that the current server city is // vertically centered in the view const serverListYCenter = vpnFlickable.height / 2; for (let idx = 0; idx < repeater.count; idx++) { const countryItem = repeater.itemAt(idx); const countryItemYPosition = countryItem.mapToItem(vpnFlickable.contentItem, 0, 0).y; if (!countryItem.cityListVisible || countryItemYPosition < serverListYCenter) { continue; } const currentCityYPosition = countryItem.y + (Theme.rowHeight * 2) + (54 * countryItem.currentCityIndex) - serverListYCenter; const destinationY = (currentCityYPosition + vpnFlickable.height > vpnFlickable.contentHeight) ? vpnFlickable.contentHeight - vpnFlickable.height : currentCityYPosition; vpnFlickable.contentY = destinationY; return; } } function scrollDelegateIntoView(item) { if (window.height > vpnFlickable.contentHeight) { return; } const yPosition = item.mapToItem(vpnFlickable.contentItem, 0, 0).y; const approximateDelegateHeight = 60; const ext = approximateDelegateHeight + yPosition; if (yPosition < vpnFlickable.contentY || yPosition > vpnFlickable.contentY + vpnFlickable.height || ext < vpnFlickable.contentY || ext > vpnFlickable.contentY + vpnFlickable.height) { const destinationY = Math.max(0, Math.min(yPosition - vpnFlickable.height + approximateDelegateHeight, vpnFlickable.contentHeight - vpnFlickable.height)); scrollAnimation.to = destinationY; scrollAnimation.start(); } } Repeater { id: repeater model: VPNServerCountryModel delegate: VPNServerCountry{} } } } } } mozilla-vpn-client-2.2.0/src/ui/views/ViewSettings.qml000066400000000000000000000006521404202232700227450ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../components" VPNStackView { id: settingsStackView initialItem: "../settings/ViewSettingsMenu.qml" } mozilla-vpn-client-2.2.0/src/ui/views/ViewSubscriptionNeeded.qml000066400000000000000000000156741404202232700247500ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme VPNFlickable { id: vpnFlickable property var wasmView: false Component.onCompleted: { if (wasmView) { vpnPanel.logoTitle = qsTrId("vpn.subscription.title").arg("5.99") } } width: window.width flickContentHeight: footerContent.height + spacer1.height + vpnPanel.height + featureListBackground.height + (Theme.windowMargin * 4) VPNHeaderLink { id: headerLink labelText: qsTrId("vpn.main.getHelp") onClicked: stackview.push(getHelpComponent) } Component { id: getHelpComponent VPNGetHelp { isSettingsView: false } } Item { id: spacer1 width: parent.width height: (Math.max(window.safeContentHeight * .04, Theme.windowMargin * 2)) } VPNPanel { id: vpnPanel //: “/mo” stands for “per month”. %1 is replaced by the cost (including currency). //% "Subscribe for %1/mo" logoTitle: qsTrId("vpn.subscription.title").arg(VPNIAP.priceValue) //% "30-day money-back guarantee" logoSubtitle: qsTrId("vpn.subscription.moneyBackGuarantee") anchors.top: spacer1.bottom logo: "../resources/logo.svg" logoSize: 48 } VPNDropShadow { anchors.fill: featureListBackground source: featureListBackground z: -2 } Rectangle { id: featureListBackground anchors.fill: featureList anchors.topMargin: -(Theme.windowMargin * 2) anchors.bottomMargin: -(Theme.windowMargin * 2) color: Theme.white radius: 4 z: -1 } ColumnLayout { id: featureList anchors.horizontalCenter: parent.horizontalCenter anchors.top: vpnPanel.bottom anchors.topMargin: Theme.windowMargin * 4 width: Math.min(vpnFlickable.width - Theme.windowMargin * 2, Theme.maxHorizontalContentWidth) spacing: Theme.vSpacing VPNCallout { // "Device level encryption" - String defined in ViewOnboarding.qml calloutTitle: qsTrId("vpn.onboarding.headline.1") //% "We encrypt your entire device." calloutSubtitle: qsTrId("vpn.subscription.featureSubtitle2") calloutImage: "../resources/onboarding/onboarding1.svg" } VPNCallout { // Servers in 30+ countries - String defined in ViewOnboarding.qml calloutTitle: qsTrId("vpn.onboarding.headline.2") //% "Protect your access to the web." calloutSubtitle: qsTrId("vpn.subscription.featureSubtitle3") calloutImage: "../resources/onboarding/onboarding2.svg" } VPNCallout { //% "Connect up to %1 devices" //: %1 is the number of devices. //: Note: there is currently no support for proper plurals calloutTitle: qsTrId("vpn.subscription.featureTitle4").arg(VPNUser.maxDevices) //% "We won’t restrict your bandwidth." calloutSubtitle: qsTrId("vpn.subscription.featureSubtitle4") calloutImage: "../resources/onboarding/onboarding3.svg" } VPNCallout { //% "No activity logs" calloutTitle: qsTrId("vpn.subscription.featureTitle1") //% "We’re Mozilla. We’re on your side." calloutSubtitle: qsTrId("vpn.subscription.featureSubtitle1") calloutImage: "../resources/onboarding/onboarding4.svg" } } Item { id: spacer2 anchors.top: featureListBackground.bottom height: Math.max(Theme.windowMargin * 2, (window.safeContentHeight - flickContentHeight)) width: vpnFlickable.width } ColumnLayout { id: footerContent anchors.top: spacer2.bottom anchors.horizontalCenter: parent.horizontalCenter width: Math.min(vpnFlickable.width, Theme.maxHorizontalContentWidth) spacing: 0 VPNButton { id: subscribeNow //% "Subscribe now" text: qsTrId("vpn.updates.subscribeNow") Layout.alignment: Qt.AlignHCenter loaderVisible: false onClicked: VPNIAP.subscribe() } Rectangle { // vertical spacer color: "transparent" Layout.preferredHeight: Theme.windowMargin Layout.fillWidth: true } GridLayout { id: grid Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true columnSpacing: 0 Component.onCompleted: columns = (termsOfService.width > subscribeNow.width / 2 || privacyNotice.width > subscribeNow.width / 2) ? 1 : 3; VPNGreyLink { id: termsOfService // Terms of Service - string definted in VPNAboutUs.qml labelText: qsTrId("vpn.aboutUs.tos") Layout.alignment: grid.columns > 1 ? Qt.AlignRight : Qt.AlignHCenter textAlignment: grid.columns > 1 ? Text.AlignRight : Text.AlignHCenter onClicked: VPN.openLink(VPN.LinkTermsOfService) } Rectangle { id: centerDivider width: 4 height: 4 radius: 2 Layout.alignment: Qt.AlignHCenter color: Theme.greyLink.defaultColor visible: (grid.columns > 1) opacity: .8 } VPNGreyLink { id: privacyNotice // Privacy Notice - string defined in VPNAboutUs.qml labelText: qsTrId("vpn.aboutUs.privacyNotice") onClicked: VPN.openLink(VPN.LinkPrivacyNotice) textAlignment: grid.columns > 1 ? Text.AlignLeft : Text.AlignHCenter Layout.alignment: grid.columns > 1 ? Qt.AlignLeft : Qt.AlignHCenter } } Rectangle { // vertical spacer color: "transparent" Layout.preferredHeight: Theme.windowMargin * 1.5 Layout.fillWidth: true } Rectangle { // vertical spacer color: "transparent" Layout.preferredHeight: Theme.windowMargin * .5 Layout.fillWidth: true } VPNSignOut { anchors.bottom: undefined anchors.bottomMargin: undefined anchors.horizontalCenter: undefined Layout.alignment: Qt.AlignHCenter onClicked: { VPNController.logout(); } } Rectangle { Layout.fillWidth: true Layout.preferredHeight: Theme.windowMargin * 2 color: "transparent" } } } mozilla-vpn-client-2.2.0/src/ui/views/ViewUpdate.qml000066400000000000000000000136351404202232700223740ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import QtQuick 2.5 import QtQuick.Controls 2.14 import QtQuick.Layouts 1.14 import Mozilla.VPN 1.0 import "../components" import "../themes/themes.js" as Theme VPNFlickable { id: vpnFlickable flickContentHeight: vpnPanel.height + alertWrapperBackground.height + footerContent.height + (Theme.windowMargin * 4) state: VPN.updateRecommended ? "recommended" : "required" states: [ State { name: "recommended" PropertyChanges { target: vpnPanel //% "Update recommended" logoTitle: qsTrId("vpn.updates.updateRecomended") //% "Please update the app before you continue to use the VPN" logoSubtitle: qsTrId("vpn.updates.updateRecomended.description") logo: "../resources/updateRecommended.svg" } PropertyChanges { target: signOff visible: false } PropertyChanges { target: footerLink onClicked: { // Let's hide the alert. VPN.hideUpdateRecommendedAlert(); stackview.pop(StackView.Immediate); } } }, State { name: "required" PropertyChanges { target: vpnPanel //% "Update required" logoTitle: qsTrId("vpn.updates.updateRequired") //% "We detected and fixed a serious bug. You must update your app." logoSubtitle: qsTrId("vpn.updates.updateRequire.reason") logo: "../resources/updateRequired.svg" } PropertyChanges { target: signOff visible: VPN.userAuthenticated } PropertyChanges { target: footerLink onClicked: { VPN.openLink(VPN.LinkAccount); } } } ] Item { id: spacer1 height: Math.max(Theme.windowMargin * 2, ( window.safeContentHeight - flickContentHeight ) / 2) width: vpnFlickable.width } VPNPanel { id: vpnPanel property var childRectHeight: vpnPanel.childrenRect.height anchors.top: spacer1.bottom width: Math.min(vpnFlickable.width - Theme.windowMargin * 4, Theme.maxHorizontalContentWidth) logoSize: 80 } Item { id: spacer2 anchors.top: vpnPanel.bottom height: Math.max(Theme.windowMargin * 2, (window.safeContentHeight -flickContentHeight ) / 2) width: vpnFlickable.width } VPNDropShadow { anchors.fill: alertWrapperBackground source: alertWrapperBackground } Rectangle { id: alertWrapperBackground anchors.fill: alertWrapper color: Theme.white radius: 8 anchors.topMargin: -Theme.windowMargin anchors.bottomMargin: -Theme.windowMargin anchors.leftMargin: -Theme.windowMargin anchors.rightMargin: -Theme.windowMargin } ColumnLayout { id: alertWrapper anchors.top: spacer2.bottom anchors.topMargin: Theme.windowMargin anchors.horizontalCenter: parent.horizontalCenter width: Math.min(vpnFlickable.width - (Theme.windowMargin * 4), Theme.maxHorizontalContentWidth) RowLayout { id: insecureConnectionAlert Layout.minimumHeight: 40 Layout.fillHeight: true Layout.fillWidth: true spacing: 16 Image { source: "../resources/connection-info-dark.svg" sourceSize.width: 20 sourceSize.height: 20 antialiasing: true } VPNTextBlock { id: alertUpdateRecommendedText font.family: Theme.fontInterFamily font.pixelSize: Theme.fontSizeSmall color: Theme.fontColorDark Layout.fillWidth: true Layout.alignment: Qt.AlignVCenter //% "Your connection will not be secure while you update." text: qsTrId("vpn.updates.updateConnectionInsecureWarning") } } } ColumnLayout { id: footerContent anchors.top: alertWrapperBackground.bottom anchors.horizontalCenter: parent.horizontalCenter width: Math.min(parent.width, Theme.maxHorizontalContentWidth) spacing: Theme.windowMargin * 1.25 Rectangle { Layout.fillWidth: true Layout.preferredHeight: Theme.windowMargin / 2 color: "transparent" } VPNButton { id: updateBtn text: qsTrId("vpn.updates.updateNow") loaderVisible: VPN.updating radius: 4 onClicked: VPN.update() } VPNLinkButton { id: footerLink //% "Not now" readonly property var textNotNow: qsTrId("vpn.updates.notNow") //% "Manage account" readonly property var textManageAccount: qsTrId("vpn.main.manageAccount") Layout.alignment: Qt.AlignHCenter labelText: (vpnFlickable.state === "recommended") ? textNotNow : textManageAccount } VPNSignOut { id: signOff anchors.bottom: undefined anchors.bottomMargin: undefined anchors.horizontalCenter: undefined Layout.alignment: Qt.AlignHCenter onClicked: { VPNController.logout(); } } Rectangle { Layout.fillWidth: true Layout.preferredHeight: Theme.windowMargin * 2 color: "transparent" } } } mozilla-vpn-client-2.2.0/src/update/000077500000000000000000000000001404202232700173045ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/src/update/balrog.cpp000066400000000000000000000421311404202232700212570ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "balrog.h" #include "constants.h" #include "errorhandler.h" #include "inspector/inspectorwebsocketconnection.h" #include "leakdetector.h" #include "logger.h" #include "mozillavpn.h" #include "networkrequest.h" #include #include #include #include #include #include #include #include typedef struct { const char* p; size_t n; } gostring_t; typedef void (*logFunc)(int level, const char* msg); #if defined(MVPN_WINDOWS) # include "windows.h" # include "platforms/windows/windowscommons.h" constexpr const char* BALROG_WINDOWS_UA = "WINNT_x86_64"; typedef void BalrogSetLogger(logFunc func); typedef unsigned char BalrogValidateSignature(gostring_t publicKey, gostring_t signature, gostring_t data); #elif defined(MVPN_MACOS) # define EXPORT __attribute__((visibility("default"))) extern "C" { EXPORT void balrogSetLogger(logFunc func); EXPORT unsigned char balrogValidateSignature(gostring_t publicKey, gostring_t signature, gostring_t data); } constexpr const char* BALROG_MACOS_UA = "Darwin_x86"; #else # error Platform not supported yet #endif constexpr const char* BALROG_CERT_SUBJECT_CN = "aus.content-signature.mozilla.org"; namespace { Logger logger(LOG_NETWORKING, "Balrog"); void balrogLogger(int level, const char* msg) { Q_UNUSED(level); logger.log() << "BalrogGo:" << msg; } QString appVersion() { #ifdef MVPN_INSPECTOR return InspectorWebSocketConnection::appVersionForUpdate(); #else return APP_VERSION; #endif } } // namespace Balrog::Balrog(QObject* parent, bool downloadAndInstall) : Updater(parent), m_downloadAndInstall(downloadAndInstall) { MVPN_COUNT_CTOR(Balrog); logger.log() << "Balrog created"; } Balrog::~Balrog() { MVPN_COUNT_DTOR(Balrog); logger.log() << "Balrog released"; } // static QString Balrog::userAgent() { #if defined(MVPN_WINDOWS) return BALROG_WINDOWS_UA; #elif defined(MVPN_MACOS) return BALROG_MACOS_UA; #else # error Unsupported platform #endif } void Balrog::start() { QString url = QString(Constants::BALROG_URL).arg(appVersion()).arg(userAgent()); logger.log() << "URL:" << url; NetworkRequest* request = NetworkRequest::createForGetUrl(this, url, 200); connect(request, &NetworkRequest::requestFailed, [this](QNetworkReply::NetworkError error, const QByteArray&) { logger.log() << "Request failed" << error; deleteLater(); }); connect(request, &NetworkRequest::requestCompleted, [this, request](const QByteArray& data) { logger.log() << "Request completed"; if (!fetchSignature(request, data)) { logger.log() << "Ignore failure."; deleteLater(); } }); } bool Balrog::fetchSignature(NetworkRequest* initialRequest, const QByteArray& dataUpdate) { Q_ASSERT(initialRequest); QByteArray header = initialRequest->rawHeader("Content-Signature"); if (header.isEmpty()) { logger.log() << "Content-Signature missing"; return false; } QByteArray x5u; QByteArray signatureBlob; QCryptographicHash::Algorithm algorithm = QCryptographicHash::Sha256; for (const QByteArray& item : header.split(';')) { QByteArray entry = item.trimmed(); int pos = entry.indexOf('='); if (pos == -1) { logger.log() << "Invalid entry:" << item; return false; } QByteArray key = entry.left(pos); if (key == "x5u") { x5u = entry.remove(0, pos + 1); } else if (key == "p384ecdsa") { algorithm = QCryptographicHash::Sha384; signatureBlob = entry.remove(0, pos + 1); } else if (key == "p256ecdsa") { algorithm = QCryptographicHash::Sha256; signatureBlob = entry.remove(0, pos + 1); } else if (key == "p521ecdsa") { algorithm = QCryptographicHash::Sha512; signatureBlob = entry.remove(0, pos + 1); } } if (x5u.isEmpty() || signatureBlob.isEmpty()) { logger.log() << "No p256ecdsa/p384ecdsa or x5u found"; return false; } logger.log() << "Fetching URL:" << x5u; NetworkRequest* x5uRequest = NetworkRequest::createForGetUrl(this, x5u, 200); connect(x5uRequest, &NetworkRequest::requestFailed, [this](QNetworkReply::NetworkError error, const QByteArray&) { logger.log() << "Request failed" << error; deleteLater(); }); connect(x5uRequest, &NetworkRequest::requestCompleted, [this, signatureBlob, algorithm, dataUpdate](const QByteArray& data) { logger.log() << "Request completed"; if (!checkSignature(data, signatureBlob, algorithm, dataUpdate)) { deleteLater(); } }); return true; } bool Balrog::checkSignature(const QByteArray& signature, const QByteArray& signatureBlob, QCryptographicHash::Algorithm algorithm, const QByteArray& data) { logger.log() << "Checking the signature"; QList list; QByteArray cert; for (const QByteArray& line : signature.split('\n')) { cert.append(line); cert.append('\n'); if (line != "-----END CERTIFICATE-----") { continue; } QSslCertificate ssl(cert, QSsl::Pem); if (ssl.isNull()) { logger.log() << "Invalid certificate" << cert; return false; } list.append(ssl); cert.clear(); } if (list.isEmpty()) { logger.log() << "No certificates found"; return false; } logger.log() << "Found certificates:" << list.length(); // TODO: do we care about OID extensions? // Qt5.15 doesn't implement the certificate validation (yet?) #ifndef MVPN_MACOS QList errors = QSslCertificate::verify(list); for (const QSslError& error : errors) { if (error.error() != QSslError::SelfSignedCertificateInChain) { logger.log() << "Chain validation failed:" << error.errorString(); return false; } } #endif logger.log() << "Validating root certificate"; const QSslCertificate& rootCert = list.constLast(); QByteArray rootCertHash = rootCert.digest(QCryptographicHash::Sha256).toHex(); if (rootCertHash != Constants::BALROG_ROOT_CERT_FINGERPRINT) { logger.log() << "Invalid root certificate fingerprint" << rootCertHash; return false; } const QSslCertificate& leaf = list.constFirst(); logger.log() << "Validating cert subject"; QStringList cnList = leaf.subjectInfo("CN"); if (cnList.isEmpty() || cnList[0] != BALROG_CERT_SUBJECT_CN) { logger.log() << "Invalid CN:" << cnList; return false; } logger.log() << "Validate public key"; QSslKey leafPublicKey = leaf.publicKey(); if (leafPublicKey.isNull()) { logger.log() << "Empty public key"; return false; } logger.log() << "Validate the signature"; if (!validateSignature(leafPublicKey.toPem(), data, algorithm, signatureBlob)) { logger.log() << "Invalid signature"; return false; } logger.log() << "Fetch resource"; if (!processData(data)) { logger.log() << "Fetch has failed"; return false; } return true; } bool Balrog::validateSignature(const QByteArray& publicKey, const QByteArray& data, QCryptographicHash::Algorithm algorithm, const QByteArray& signature) { // The algortihm is detected by the length of the signature Q_UNUSED(algorithm); #if defined(MVPN_WINDOWS) static HMODULE balrogDll = nullptr; static BalrogSetLogger* balrogSetLogger = nullptr; static BalrogValidateSignature* balrogValidateSignature = nullptr; if (!balrogDll) { // This process will be used by the wireguard tunnel. No need to call // FreeLibrary. balrogDll = LoadLibrary(TEXT("balrog.dll")); if (!balrogDll) { WindowsCommons::windowsLog("Failed to load tunnel.dll"); return false; } } if (!balrogSetLogger) { balrogSetLogger = (BalrogSetLogger*)GetProcAddress(balrogDll, "balrogSetLogger"); if (!balrogSetLogger) { WindowsCommons::windowsLog("Failed to get balrogSetLogger function"); return false; } } if (!balrogValidateSignature) { balrogValidateSignature = (BalrogValidateSignature*)GetProcAddress( balrogDll, "balrogValidateSignature"); if (!balrogValidateSignature) { WindowsCommons::windowsLog( "Failed to get balrogValidateSignature function"); return false; } } #endif balrogSetLogger(balrogLogger); QByteArray publicKeyCopy = publicKey; gostring_t publicKeyGo{publicKeyCopy.constData(), (size_t)publicKeyCopy.length()}; QByteArray signatureCopy = signature; gostring_t signatureGo{signatureCopy.constData(), (size_t)signatureCopy.length()}; QByteArray dataCopy = data; gostring_t dataGo{dataCopy.constData(), (size_t)dataCopy.length()}; unsigned char verify = balrogValidateSignature(publicKeyGo, signatureGo, dataGo); if (!verify) { logger.log() << "Verification failed"; return false; } return true; } bool Balrog::processData(const QByteArray& data) { QJsonDocument json = QJsonDocument::fromJson(data); if (!json.isObject()) { logger.log() << "A valid JSON object expected"; return false; } QJsonObject obj = json.object(); // This is not a download operation. Let's emit signals if needed. if (!m_downloadAndInstall) { bool required = obj.value("required").toBool(); if (required) { // Even if we are geoip-restricted, we want to force the update when // required. emit updateRequired(); deleteLater(); return true; } // Let's see if we can fetch the content. If we are unable to fetch the // content, we don't push for a recommended update. QString url = obj.value("url").toString(); if (url.isEmpty()) { logger.log() << "Invalid URL in the JSON document"; return false; } NetworkRequest* request = NetworkRequest::createForGetUrl(this, url); connect(request, &NetworkRequest::requestFailed, [this](QNetworkReply::NetworkError error, const QByteArray&) { logger.log() << "Request failed" << error; deleteLater(); }); connect(request, &NetworkRequest::requestHeaderReceived, [this](NetworkRequest* request) { Q_ASSERT(request); logger.log() << "Request header received"; // We want to proceed only if the status code is 200. The request // will be aborted, but the signal emitted. if (request->statusCode() == 200) { emit updateRecommended(); } logger.log() << "Abort request for status code" << request->statusCode(); request->abort(); }); connect(request, &NetworkRequest::requestCompleted, [this](const QByteArray&) { logger.log() << "Request completed"; deleteLater(); }); return true; } QString url = obj.value("url").toString(); if (url.isEmpty()) { logger.log() << "Invalid URL in the JSON document"; return false; } QString hashFunction = obj.value("hashFunction").toString(); if (hashFunction.isEmpty()) { logger.log() << "No hashFunction item"; return false; } QString hashValue = obj.value("hashValue").toString(); if (hashValue.isEmpty()) { logger.log() << "No hashValue item"; return false; } NetworkRequest* request = NetworkRequest::createForGetUrl(this, url); // No timeout for this request. request->disableTimeout(); connect( request, &NetworkRequest::requestFailed, [this, request](QNetworkReply::NetworkError error, const QByteArray&) { logger.log() << "Request failed" << error; propagateError(request, error); deleteLater(); }); connect(request, &NetworkRequest::requestCompleted, [this, hashValue, hashFunction, url](const QByteArray& data) { logger.log() << "Request completed"; if (!computeHash(url, data, hashValue, hashFunction)) { logger.log() << "Ignore failure."; deleteLater(); } }); return true; } bool Balrog::computeHash(const QString& url, const QByteArray& data, const QString& hashValue, const QString& hashFunction) { logger.log() << "Compute the hash"; if (hashFunction != "sha512") { logger.log() << "Invalid hash function"; return false; } QCryptographicHash hash(QCryptographicHash::Sha512); hash.addData(data); QByteArray hashHex = hash.result().toHex(); if (hashHex != hashValue) { logger.log() << "Hash doesn't match"; return false; } return saveFileAndInstall(url, data); } bool Balrog::saveFileAndInstall(const QString& url, const QByteArray& data) { logger.log() << "Savel the file and install it"; int pos = url.lastIndexOf("/"); if (pos == -1) { logger.log() << "The URL seems to be without /."; return false; } QString fileName = url.right(url.length() - pos - 1); logger.log() << "Filename:" << fileName; if (!m_tmpDir.isValid()) { logger.log() << "Cannot create a temporary directory"; return false; } QDir dir(m_tmpDir.path()); QString tmpFile = dir.filePath(fileName); QFile file(tmpFile); if (!file.open(QIODevice::ReadWrite)) { logger.log() << "Unable to create a file in the temporary folder"; return false; } qint64 written = file.write(data); if (written != data.length()) { logger.log() << "Unable to write the whole configuration file"; return false; } file.close(); return install(tmpFile); } bool Balrog::install(const QString& filePath) { logger.log() << "Install the package:" << filePath; QString logFile = m_tmpDir.filePath("msiexec.log"); #if defined(MVPN_WINDOWS) QStringList arguments; arguments << "/qb!-" << "REBOOT=ReallySuppress" << "/i" << QDir::toNativeSeparators(filePath) << "/lv" << QDir::toNativeSeparators(logFile); QProcess* process = new QProcess(this); process->start("msiexec.exe", arguments); connect(process, QOverload::of(&QProcess::finished), [this, process, logFile](int exitCode, QProcess::ExitStatus) { logger.log() << "Installation completed - exitCode:" << exitCode; logger.log() << "Stdout:" << Qt::endl << qUtf8Printable(process->readAllStandardOutput()) << Qt::endl; logger.log() << "Stderr:" << Qt::endl << qUtf8Printable(process->readAllStandardError()) << Qt::endl; QFile log(logFile); if (!log.open(QIODevice::ReadOnly | QIODevice::Text)) { logger.log() << "Unable to read the msiexec log file"; } else { logger.log() << "Log file:" << Qt::endl << log.readAll(); } if (exitCode != 0) { deleteLater(); return; } // We leak the object because the installer will restart the // app and we need to keep the temporary folder alive during the // whole process. }); #endif #if defined(MVPN_MACOS) QStringList arguments; arguments << filePath; QProcess* process = new QProcess(this); process->start("open", arguments); connect(process, QOverload::of(&QProcess::finished), [this, process](int exitCode, QProcess::ExitStatus) { logger.log() << "Installation completed - exitCode:" << exitCode; logger.log() << "Stdout:" << Qt::endl << qUtf8Printable(process->readAllStandardOutput()) << Qt::endl; logger.log() << "Stderr:" << Qt::endl << qUtf8Printable(process->readAllStandardError()) << Qt::endl; if (exitCode != 0) { deleteLater(); return; } // We leak the object because the installer will restart the // app and we need to keep the temporary folder alive during the // whole process. exit(0); }); #endif return true; } void Balrog::propagateError(NetworkRequest* request, QNetworkReply::NetworkError error) { Q_ASSERT(request); MozillaVPN* vpn = MozillaVPN::instance(); Q_ASSERT(vpn); // 451 Unavailable For Legal Reasons if (request->statusCode() == 451) { logger.log() << "Geo IP restriction detected"; vpn->errorHandle(ErrorHandler::GeoIpRestrictionError); return; } vpn->errorHandle(ErrorHandler::toErrorType(error)); } mozilla-vpn-client-2.2.0/src/update/balrog.h000066400000000000000000000030221404202232700207200ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef BALROG_H #define BALROG_H #include "updater.h" #include #include #include class NetworkRequest; class Balrog final : public Updater { Q_DISABLE_COPY_MOVE(Balrog) public: Balrog(QObject* parent, bool downloadAndInstall); ~Balrog(); void start() override; private: static QString userAgent(); bool processData(const QByteArray& data); bool fetchSignature(NetworkRequest* request, const QByteArray& data); bool checkSignature(const QByteArray& signature, const QByteArray& signatureBlob, QCryptographicHash::Algorithm algorithm, const QByteArray& data); bool validateSignature(const QByteArray& publicKey, const QByteArray& data, QCryptographicHash::Algorithm algorithm, const QByteArray& signature); bool computeHash(const QString& url, const QByteArray& data, const QString& hashValue, const QString& hashFunction); bool saveFileAndInstall(const QString& url, const QByteArray& data); bool install(const QString& filePath); void propagateError(NetworkRequest* request, QNetworkReply::NetworkError error); private: QTemporaryDir m_tmpDir; bool m_downloadAndInstall; }; #endif // BALROG_H mozilla-vpn-client-2.2.0/src/update/updater.cpp000066400000000000000000000017411404202232700214570ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "updater.h" #include "logger.h" #include "leakdetector.h" #include "versionapi.h" #ifdef MVPN_BALROG # include "balrog.h" #endif namespace { Logger logger(LOG_NETWORKING, "Updater"); } // static Updater* Updater::create(QObject* parent, bool downloadAndInstall) { #ifdef MVPN_BALROG if (!downloadAndInstall) { return new Balrog(parent, false); } return new Balrog(parent, true); #endif if (!downloadAndInstall) { return new VersionApi(parent); } logger.log() << "No download and install supported for this platform."; return nullptr; } Updater::Updater(QObject* parent) : QObject(parent) { MVPN_COUNT_CTOR(Updater); logger.log() << "Updater created"; } Updater::~Updater() { MVPN_COUNT_DTOR(Updater); logger.log() << "Updater released"; } mozilla-vpn-client-2.2.0/src/update/updater.h000066400000000000000000000011111404202232700211130ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef UPDATER_H #define UPDATER_H #include class Updater : public QObject { Q_OBJECT Q_DISABLE_COPY_MOVE(Updater) public: static Updater* create(QObject* parent, bool downloadAndInstall); Updater(QObject* parent); virtual ~Updater(); virtual void start() = 0; signals: void updateRequired(); void updateRecommended(); }; #endif // UPDATER_H mozilla-vpn-client-2.2.0/src/update/versionapi.cpp000066400000000000000000000104541404202232700221730ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "versionapi.h" #include "leakdetector.h" #include "logger.h" #include "networkrequest.h" #include #include #include namespace { Logger logger(LOG_NETWORKING, "VersionApi"); } VersionApi::VersionApi(QObject* parent) : Updater(parent) { MVPN_COUNT_CTOR(VersionApi); logger.log() << "VersionApi created"; } VersionApi::~VersionApi() { MVPN_COUNT_DTOR(VersionApi); logger.log() << "VersionApi released"; } void VersionApi::start() { NetworkRequest* request = NetworkRequest::createForVersions(this); connect(request, &NetworkRequest::requestFailed, [](QNetworkReply::NetworkError error, const QByteArray&) { logger.log() << "Request failed" << error; }); connect(request, &NetworkRequest::requestCompleted, [this](const QByteArray& data) { logger.log() << "Request completed"; if (!processData(data)) { logger.log() << "Ignore failure."; } }); connect(request, &QObject::destroyed, this, &QObject::deleteLater); } bool VersionApi::processData(const QByteArray& data) { QJsonDocument json = QJsonDocument::fromJson(data); if (!json.isObject()) { logger.log() << "A valid JSON object expected"; return false; } QJsonObject obj = json.object(); QString platformKey = #if defined(MVPN_IOS) "ios" #elif defined(MVPN_MACOS) "macos" #elif defined(MVPN_LINUX) "linux" #elif defined(MVPN_ANDROID) "android" #elif defined(MVPN_WINDOWS) "windows" #elif defined(UNIT_TEST) || defined(MVPN_DUMMY) "dummy" #else # error "Unsupported platform" #endif ; if (!obj.contains(platformKey)) { logger.log() << "No key" << platformKey; return false; } QJsonValue platformDataValue = obj.value(platformKey); if (!platformDataValue.isObject()) { logger.log() << "Platform object not available"; return false; } QJsonObject platformData = platformDataValue.toObject(); QString latestVersion; QString minimumVersion; QString currentVersion(APP_VERSION); QJsonValue latestValue = platformData.value("latest"); if (!latestValue.isObject()) { logger.log() << "Platform.latest object not available"; } else { QJsonObject latestData = latestValue.toObject(); QJsonValue latestVersionValue = latestData.value("version"); if (!latestVersionValue.isString()) { logger.log() << "Platform.latest.version string not available"; } else { latestVersion = latestVersionValue.toString(); } } QJsonValue minimumValue = platformData.value("minimum"); if (!minimumValue.isObject()) { logger.log() << "Platform.minimum object not available"; } else { QJsonObject minimumData = minimumValue.toObject(); QJsonValue minimumVersionValue = minimumData.value("version"); if (!minimumVersionValue.isString()) { logger.log() << "Platform.minimum.version string not available"; } else { minimumVersion = minimumVersionValue.toString(); } } logger.log() << "Latest version:" << latestVersion; logger.log() << "Minimum version:" << minimumVersion; logger.log() << "Current version:" << currentVersion; if (compareVersions(currentVersion, minimumVersion) == -1) { logger.log() << "update required"; emit updateRequired(); return true; } if (compareVersions(currentVersion, latestVersion) == -1) { logger.log() << "Update recommended."; emit updateRecommended(); } return true; } // static int VersionApi::compareVersions(const QString& a, const QString& b) { if (a == b) { return 0; } if (a.isEmpty()) { return 1; } if (b.isEmpty()) { return -1; } QList aParts; for (const QString& part : a.split(".")) aParts.append(part.toInt()); while (aParts.length() < 3) aParts.append(0); QList bParts; for (const QString& part : b.split(".")) bParts.append(part.toInt()); while (bParts.length() < 3) bParts.append(0); // Major version number. for (uint32_t i = 0; i < 3; ++i) { if (aParts[i] != bParts[i]) { return aParts[i] < bParts[i] ? -1 : 1; } } return 0; } mozilla-vpn-client-2.2.0/src/update/versionapi.h000066400000000000000000000015211404202232700216330ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef VERSIONAPI_H #define VERSIONAPI_H #include "updater.h" class VersionApi final : public Updater { Q_DISABLE_COPY_MOVE(VersionApi) public: VersionApi(QObject* parent); ~VersionApi(); void start() override; // compare 2 version strings and return: // - -1 if the first one is lower than the second one or if the second one is // empty. // - 0 if they are equal // - 1 if the first one is greater than the second one or if the first one is // empty. static int compareVersions(const QString& a, const QString& b); private: [[nodiscard]] bool processData(const QByteArray& data); }; #endif // VERSIONAPI_H mozilla-vpn-client-2.2.0/src/urlopener.cpp000066400000000000000000000012041404202232700205360ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "urlopener.h" #include "logger.h" #include "inspector/inspectorwebsocketconnection.h" #include #include namespace { Logger logger(LOG_MAIN, "UrlOpener"); } // static void UrlOpener::open(const QUrl& url) { #ifdef MVPN_INSPECTOR InspectorWebSocketConnection::setLastUrl(url.toString()); if (InspectorWebSocketConnection::stealUrls()) { return; } #endif QDesktopServices::openUrl(url); } mozilla-vpn-client-2.2.0/src/urlopener.h000066400000000000000000000005451404202232700202120ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef URLOPENER_H #define URLOPENER_H class QUrl; class UrlOpener final { public: static void open(const QUrl& url); }; #endif // URLOPENER_H mozilla-vpn-client-2.2.0/src/wgquickprocess.cpp000066400000000000000000000117101404202232700215770ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "wgquickprocess.h" #include "../../src/logger.h" #include #include #include namespace { Logger logger( #if defined(MVPN_LINUX) LOG_LINUX #elif defined(MVPN_MACOS_DAEMON) LOG_MACOS #elif defined(MVPN_WINDOWS) LOG_WINDOWS #endif , "WgQuickProcess"); } // namespace // static bool WgQuickProcess::createConfigFile( const QString& configFile, const QString& privateKey, const QString& deviceIpv4Address, const QString& deviceIpv6Address, const QString& serverIpv4Gateway, const QString& serverIpv6Gateway, const QString& serverPublicKey, const QString& serverIpv4AddrIn, const QString& serverIpv6AddrIn, const QString& allowedIPAddressRanges, int serverPort, bool ipv6Enabled) { Q_UNUSED(serverIpv6AddrIn); #define VALIDATE(x) \ if (x.contains("\n")) return false; VALIDATE(privateKey); VALIDATE(deviceIpv4Address); VALIDATE(deviceIpv6Address); VALIDATE(serverIpv4Gateway); VALIDATE(serverIpv6Gateway); VALIDATE(serverPublicKey); VALIDATE(serverIpv4AddrIn); VALIDATE(serverIpv6AddrIn); VALIDATE(allowedIPAddressRanges); #undef VALIDATE QByteArray content; content.append("[Interface]\nPrivateKey = "); content.append(privateKey.toUtf8()); content.append("\nAddress = "); content.append(deviceIpv4Address.toUtf8()); if (ipv6Enabled) { content.append(", "); content.append(deviceIpv6Address.toUtf8()); } content.append("\nDNS = "); content.append(serverIpv4Gateway.toUtf8()); if (ipv6Enabled) { content.append(", "); content.append(serverIpv6Gateway.toUtf8()); } content.append("\n\n[Peer]\nPublicKey = "); content.append(serverPublicKey.toUtf8()); content.append("\nEndpoint = "); content.append(serverIpv4AddrIn.toUtf8()); content.append(QString(":%1").arg(serverPort).toUtf8()); /* In theory, we should use the ipv6 endpoint, but wireguard doesn't seem * to be happy if there are 2 endpoints. if (ipv6Enabled) { content.append("\nEndpoint = ["); content.append(serverIpv6AddrIn); content.append(QString("]:%1").arg(serverPort)); } */ content.append( QString("\nAllowedIPs = %1\n").arg(allowedIPAddressRanges).toUtf8()); #ifdef QT_DEBUG logger.log() << content; #endif QFile file(configFile); if (!file.open(QIODevice::WriteOnly)) { qWarning("Unable to create a file in the temporary folder"); return false; } qint64 written = file.write(content); if (written != content.length()) { qWarning("Unable to write the whole configuration file"); return false; } file.close(); return true; } // static bool WgQuickProcess::run( Daemon::Op op, const QString& privateKey, const QString& deviceIpv4Address, const QString& deviceIpv6Address, const QString& serverIpv4Gateway, const QString& serverIpv6Gateway, const QString& serverPublicKey, const QString& serverIpv4AddrIn, const QString& serverIpv6AddrIn, const QString& allowedIPAddressRanges, int serverPort, bool ipv6Enabled) { QTemporaryDir tmpDir; if (!tmpDir.isValid()) { qWarning("Cannot create a temporary directory"); return false; } QDir dir(tmpDir.path()); QString configFile(dir.filePath(QString("%1.conf").arg(WG_INTERFACE))); if (!createConfigFile(configFile, privateKey, deviceIpv4Address, deviceIpv6Address, serverIpv4Gateway, serverIpv6Gateway, serverPublicKey, serverIpv4AddrIn, serverIpv6AddrIn, allowedIPAddressRanges, serverPort, ipv6Enabled)) { logger.log() << "Failed to create the config file"; return false; } QStringList arguments; arguments.append(op == Daemon::Up ? "up" : "down"); arguments.append(configFile); QString app = scriptPath(); logger.log() << "Start:" << app << " - arguments:" << arguments; QProcess wgQuickProcess; wgQuickProcess.start(app, arguments); if (!wgQuickProcess.waitForFinished(-1)) { logger.log() << "Error occurred:" << wgQuickProcess.errorString(); return false; } logger.log() << "Execution finished" << wgQuickProcess.exitCode(); logger.log() << "wg-quick stdout:" << Qt::endl << qUtf8Printable(wgQuickProcess.readAllStandardOutput()) << Qt::endl; logger.log() << "wg-quick stderr:" << Qt::endl << qUtf8Printable(wgQuickProcess.readAllStandardError()) << Qt::endl; return wgQuickProcess.exitCode() == 0; } // static QString WgQuickProcess::scriptPath() { #if defined(MVPN_LINUX) QDir appPath(MVPN_DATA_PATH); return appPath.filePath("helper.sh"); #elif defined(MVPN_MACOS_DAEMON) QDir appPath(QCoreApplication::applicationDirPath()); appPath.cdUp(); appPath.cd("Resources"); appPath.cd("utils"); return appPath.filePath("helper.sh"); #endif return QString(); } mozilla-vpn-client-2.2.0/src/wgquickprocess.h000066400000000000000000000024021404202232700212420ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef WGQUICKPROCESS_H #define WGQUICKPROCESS_H #include "daemon/daemon.h" #include class WgQuickProcess final { Q_DISABLE_COPY_MOVE(WgQuickProcess) public: static bool run( Daemon::Op op, const QString& privateKey, const QString& deviceIpv4Address, const QString& deviceIpv6Address, const QString& serverIpv4Gateway, const QString& serverIpv6Gateway, const QString& serverPublicKey, const QString& serverIpv4AddrIn, const QString& serverIpv6AddrIn, const QString& allowedIPAddressRanges, int serverPort, bool ipv6Enabled); static bool createConfigFile( const QString& configFile, const QString& privateKey, const QString& deviceIpv4Address, const QString& deviceIpv6Address, const QString& serverIpv4Gateway, const QString& serverIpv6Gateway, const QString& serverPublicKey, const QString& serverIpv4AddrIn, const QString& serverIpv6AddrIn, const QString& allowedIPAddressRanges, int serverPort, bool ipv6Enabled); static QString scriptPath(); }; #endif // WGQUICKPROCESS_H mozilla-vpn-client-2.2.0/tests/000077500000000000000000000000001404202232700163755ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/tests/functional/000077500000000000000000000000001404202232700205375ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/tests/functional/firefox.js000066400000000000000000000022751404202232700225450ustar00rootroot00000000000000const assert = require('assert'); const firefox = require('selenium-webdriver/firefox'); const webdriver = require('selenium-webdriver'); module.exports = class FirefoxHelper { static async createDriver() { require('dotenv').config(); const options = new firefox.Options(); if (process.env.HEADLESS) { options.headless(); } const driver = await new webdriver.Builder() .forBrowser('firefox') .setFirefoxOptions(options) .build(); return driver; } static async waitForURL(driver, url) { await driver.setContext('content'); // I'm sure there is something better than this, but this is the only // solution to monitor the tab loading so far. return await new Promise(resolve => { const check = async () => { const handles = await driver.getAllWindowHandles(); for (let handle of handles) { await driver.switchTo().window(handle); const t = await driver.getCurrentUrl(); if (t.includes(url)) { resolve(handle); return; } } setTimeout(check, 500); }; check(); }); } }; mozilla-vpn-client-2.2.0/tests/functional/helper.js000066400000000000000000000224331404202232700223600ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const assert = require('assert'); const websocket = require('websocket').w3cwebsocket; const FirefoxHelper = require('./firefox.js'); const webdriver = require('selenium-webdriver'), By = webdriver.By, Keys = webdriver.Key, until = webdriver.until; let client; let waitReadCallback = null; let _lastNotification = { title: null, message: null, }; module.exports = { async connect(hostname = '127.0.0.1') { await this.waitForCondition(async () => { return await new Promise(resolve => { client = new websocket(`ws://${hostname}:8765/`, ''); client.onopen = async () => { const json = await this._writeCommand('stealurls'); assert( json.type === 'stealurls' && !('error' in json), `Invalid answer: ${json.error}`); resolve(true); }; client.onclose = () => this._resolveWaitRead({}); client.onerror = () => resolve(false); client.onmessage = data => { const json = JSON.parse(data.data); // Ignoring logs. if (json.type === 'log') return; // Store the last notification if (json.type === 'notification') { _lastNotification.title = json.title; _lastNotification.message = json.message; return; } assert(waitReadCallback, 'No waiting callback?'); this._resolveWaitRead(json); }; }); }); }, disconnect() { client.close(); }, async activate() { const json = await this._writeCommand('activate'); assert( json.type === 'activate' && !('error' in json), `Invalid answer: ${json.error}`); }, async deactivate() { const json = await this._writeCommand('deactivate'); assert( json.type === 'deactivate' && !('error' in json), `Invalid answer: ${json.error}`); }, async reset() { const json = await this._writeCommand('reset'); assert( json.type === 'reset' && !('error' in json), `Invalid answer: ${json.error}`); }, async forceHeartbeatFailure() { const json = await this._writeCommand('force_heartbeat_failure'); assert( json.type === 'force_heartbeat_failure' && !('error' in json), `Invalid answer: ${json.error}`); }, async forceUnsecuredNetworkAlert() { const json = await this._writeCommand('force_unsecured_network'); assert( json.type === 'force_unsecured_network' && !('error' in json), `Invalid answer: ${json.error}`); }, async forceCaptivePortalDetection() { const json = await this._writeCommand('force_captive_portal_detection'); assert( json.type === 'force_captive_portal_detection' && !('error' in json), `Invalid answer: ${json.error}`); }, async quit() { const json = await this._writeCommand('quit'); assert( !('type' in json) || (json.type === 'quit' && !('error' in json)), `Invalid answer: ${json.error}`); }, async hasElement(id) { const json = await this._writeCommand(`has ${id}`); assert( json.type === 'has' && !('error' in json), `Invalid answer: ${json.error}`); return json.value || false; }, async waitForElement(id) { return this.waitForCondition(async () => { return await this.hasElement(id); }); }, async clickOnElement(id) { assert(await this.hasElement(id), 'Clicking on an non-existing element?!?'); const json = await this._writeCommand(`click ${id}`); assert( json.type === 'click' && !('error' in json), `Invalid answer: ${json.error}`); }, async clickOnNotification() { const json = await this._writeCommand('click_notification'); assert( json.type === 'click_notification' && !('error' in json), `Invalid answer: ${json.error}`); }, async getElementProperty(id, property) { assert( await this.hasElement(id), 'Property checks must be done on existing elements'); const json = await this._writeCommand(`property ${id} ${property}`); assert( json.type === 'property' && !('error' in json), `Invalid answer: ${json.error}`); return json.value || ''; }, async setElementProperty(id, property, type, value) { assert( await this.hasElement(id), 'Property checks must be done on existing elements'); const json = await this._writeCommand( `set_property ${id} ${property} ${type} ${value}`); assert( json.type === 'set_property' && !('error' in json), `Invalid answer: ${json.error}`); }, async waitForElementProperty(id, property, value) { assert( await this.hasElement(id), 'Property checks must be done on existing elements'); try { return this.waitForCondition(async () => { const real = await this.getElementProperty(id, property); return real === value; }); } catch (e) { const real = await this.getElementProperty(id, property); throw new Error(`Timeout for waitForElementProperty - property: ${ property} - value: ${real} - expected: ${value}`); } }, async getLastUrl() { const json = await this._writeCommand('lasturl'); assert( json.type === 'lasturl' && !('error' in json), `Invalid answer: ${json.error}`); return json.value || ''; }, async waitForCondition(condition) { for (let i = 0; i < 50; ++i) { if (await condition()) return; await new Promise(resolve => setTimeout(resolve, 200)); } throw new Error('Timeout for waitForCondition'); }, wait() { return new Promise(resolve => setTimeout(resolve, 500)); }, async authenticate(driver, resetting = true) { if (resetting) await this.reset(); await this.waitForElement('getHelpLink'); await this.waitForElementProperty('getHelpLink', 'visible', 'true'); assert(await this.getElementProperty('getStarted', 'visible') === 'true'); assert( await this.getElementProperty('learnMoreLink', 'visible') === 'true'); await this.clickOnElement('getStarted'); await this.waitForCondition(async () => { const url = await this.getLastUrl(); return url.includes('/api/v2/vpn/login'); }); await this.wait(); await this.waitForElement('authenticatingView'); await this.waitForElementProperty('authenticatingView', 'visible', 'true'); const url = await this.getLastUrl(); await driver.setContext('content'); await driver.navigate().to(url); await FirefoxHelper.waitForURL( driver, 'https://accounts.stage.mozaws.net/oauth/'); const emailField = await driver.findElement(By.className('email')); assert.ok(!!emailField); await emailField.sendKeys(process.env.ACCOUNT_EMAIL); let buttonElm = await driver.findElement(By.id('submit-btn')); assert.ok(!!buttonElm); buttonElm.click(); await FirefoxHelper.waitForURL( driver, 'https://accounts.stage.mozaws.net/oauth/signin'); const passwordField = await driver.findElement(By.id('password')); assert.ok(!!passwordField); passwordField.sendKeys(process.env.ACCOUNT_PASSWORD); buttonElm = await driver.findElement(By.id('submit-btn')); assert.ok(!!buttonElm); await buttonElm.click(); await FirefoxHelper.waitForURL( driver, 'https://stage-vpn.guardian.nonprod.cloudops.mozgcp.net/vpn/client/login/success'); await this.wait(); }, async logout() { const json = await this._writeCommand('logout'); assert( json.type === 'logout' && !('error' in json), `Invalid answer: ${json.error}`); }, async setSetting(key, value) { const json = await this._writeCommand(`set_setting ${key} ${value}`); assert( json.type === 'set_setting' && !('error' in json), `Invalid answer: ${json.error}`); }, async getSetting(key) { const json = await this._writeCommand(`setting ${key}`); assert( json.type === 'setting' && !('error' in json), `Invalid answer: ${json.error}`); return json.value; }, lastNotification() { return _lastNotification; }, resetLastNotification() { _lastNotification.title = null; _lastNotification.message = null; }, async languages() { const json = await this._writeCommand('languages'); assert( json.type === 'languages' && !('error' in json), `Invalid answer: ${json.error}`); return json.value; }, async servers() { const json = await this._writeCommand('servers'); assert( json.type === 'servers' && !('error' in json), `Invalid answer: ${json.error}`); return json.value; }, async screenCapture() { const json = await this._writeCommand('screen_capture'); assert( json.type === 'screen_capture' && !('error' in json), `Invalid answer: ${json.error}`); return json.value; }, // Internal methods. _writeCommand(command) { return new Promise(resolve => { waitReadCallback = resolve; client.send(`${command}`); }); }, _resolveWaitRead(json) { if (waitReadCallback) { const wr = waitReadCallback; waitReadCallback = null; wr(json); } }, }; mozilla-vpn-client-2.2.0/tests/functional/localizeServers.js000066400000000000000000000274711404202232700242640ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const assert = require('assert'); const util = require('util'); const vpn = require('./helper.js'); const FirefoxHelper = require('./firefox.js'); const fetch = require('node-fetch'); const WBK = require('wikibase-sdk'); const fs = require('fs'); const webdriver = require('selenium-webdriver'), By = webdriver.By, Keys = webdriver.Key, until = webdriver.until; const exec = util.promisify(require('child_process').exec); describe('Server list', function() { let driver; let servers; let currentCountryCode; let currentCity; let wbk; let translations = []; let languages = new Map(); let serverOutputFile; let serverApiFile; let serverTemplateFile; const countryIDs = new Map(); countryIDs.set('Netherlands', 'http://www.wikidata.org/entity/Q55'); const cityIDs = new Map(); cityIDs.set('Frankfurt', 'http://www.wikidata.org/entity/Q1794'); cityIDs.set('Melbourne', 'http://www.wikidata.org/entity/Q3141'); cityIDs.set('Dallas, TX', 'http://www.wikidata.org/entity/Q16557'); cityIDs.set('Denver, CO', 'http://www.wikidata.org/entity/Q16554'); cityIDs.set('Miami, FL', 'http://www.wikidata.org/entity/Q8652'); cityIDs.set('Phoenix, AZ', 'http://www.wikidata.org/entity/Q16556'); cityIDs.set('Salt Lake City, UT', 'http://www.wikidata.org/entity/Q23337'); this.timeout(1000000); function retrieveJson(url) { return fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:86.0) Gecko/20100101 Firefox/86.0', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', } }) .then(r => r.json()); } before(async () => { await vpn.connect(); driver = await FirefoxHelper.createDriver(); wbk = WBK({ instance: 'https://www.wikidata.org', sparqlEndpoint: 'https://query.wikidata.org/sparql', }) }); beforeEach(() => {}); afterEach(() => {}); after(async () => { await driver.quit(); vpn.disconnect(); }); it('validate env', async () => { serverOutputFile = process.env.SERVER_OUTPUT; assert(!!serverOutputFile); serverApiFile = process.env.SERVER_API; assert(!!serverApiFile); serverTemplateFile = process.env.SERVER_TEMPLATE; assert(!!serverTemplateFile); }); it('authenticate', async () => await vpn.authenticate(driver)); it('Post authentication view', async () => { await vpn.waitForElement('postAuthenticationButton'); await vpn.clickOnElement('postAuthenticationButton'); await vpn.wait(); }); it('Retrieve the list of languages', async () => { for (let language of await vpn.languages()) { languages.set(language.toLowerCase().replace('_', '-'), language); } }); it('retrieve list of servers and the current one', async () => { servers = await vpn.servers(); currentCountryCode = await vpn.getSetting('current-server-country-code'); currentCity = await vpn.getSetting('current-server-city'); }); it('localize countries and cities', async () => { for (let server of servers) { const translation = await localizeCountry(server); if (translation) translations.push(translation); } }); it('write files', async () => { console.log('Write the API localized file'); fs.writeFileSync(serverApiFile, JSON.stringify(translations, null, ' ')); console.log('Merge the template file'); const template = JSON.parse(fs.readFileSync(serverTemplateFile)); assert(Array.isArray(template)); for (let country of template) { mergeCountry(country); } console.log('Sorting data'); for (let country of translations) { if ('languages' in country) { country.languages = Object.keys(country.languages).sort().reduce((obj, key) => { obj[key] = country.languages[key]; return obj; }, {}); } if (Array.isArray(country.cities)) { for (let city of country.cities) { if ('languages' in city) { city.languages = Object.keys(city.languages).sort().reduce((obj, key) => { obj[key] = city.languages[key]; return obj; }, {}); } } } } console.log('Merge the final file'); fs.writeFileSync(serverOutputFile, JSON.stringify(translations, null, ' ')); }); function mergeCountry(country) { assert(country.countryCode); for (let c of translations) { if (c.countryCode === country.countryCode) { return mergeCountryReal(c, country); } } console.log('Adding country', country.countryCode); translations.push(country); } function mergeCountryReal(country, template) { console.log('Merge country', template.countryCode); if ('languages' in template) { for (let language of Object.keys(template.languages)) { country.languages[language] = template.languages[language]; } } if (Array.isArray(template.cities)) { for (let city of template.cities) { mergeCity(country, city); } } } function mergeCity(country, template) { assert(template.city); for (let c of country.cities) { if (c.city === template.city) { return mergeCityReal(c, template); } } console.log('Adding city', template.city); country.cities.push(template); } function mergeCityReal(city, template) { console.log('Merge city', template.city); if ('languages' in template) { for (let language of Object.keys(template.languages)) { city.languages[language] = template.languages[language]; } } } async function localizeCountry(server) { console.log('Localize country', server.name); let countryUrl; if (countryIDs.has(server.name)) { countryUrl = countryIDs.get(server.name); } else { const sparql = `SELECT ?country WHERE { # wdt:P31 is "instance-of" # wd:Q6256 is "country" { ?country wdt:P31 wd:Q6256 ; { ?country rdfs:label "${ server.name}"@en . } UNION { ?country skos:altLabel "${ server.name}"@en . } } UNION { ?country wdt:P31 wd:Q3624078 ; { ?country rdfs:label "${ server.name}"@en . } UNION { ?country skos:altLabel "${ server.name}"@en . } } UNION # wd:Q515 is "city" { ?country wdt:P31 wd:Q515 ; { ?country rdfs:label "${ server.name}"@en . } UNION { ?country skos:altLabel "${ server.name}"@en . } } }`; const url = wbk.sparqlQuery(sparql) const result = await retrieveJson(url); if (!('head' in result)) throw new Error('Invalid SPARQL result (no head)'); if (!('results' in result)) throw new Error('Invalid SPARQL result (no results)'); if (!('bindings' in result.results)) throw new Error('Invalid SPARQL result (no results/bindings)'); if (result.results.bindings.length === 0) { throw new Error('No results'); } countryUrl = result.results.bindings[0].country.value; } console.log(' - WikiData Resource:', countryUrl); const sparql = `SELECT ?countryName (lang(?countryName) as ?lang) WHERE { <${countryUrl}> rdfs:label ?countryName . }`; const url = wbk.sparqlQuery(sparql) const result = await retrieveJson(url); if (!('head' in result)) throw new Error('Invalid SPARQL result (no head)'); if (!('results' in result)) throw new Error('Invalid SPARQL result (no results)'); if (!('bindings' in result.results)) throw new Error('Invalid SPARQL result (no results/bindings)'); if (result.results.bindings.length === 0) { throw new Error('No results'); } const translation = {countryCode: server.code, languages: {}, cities: []}; for (let lang of result.results.bindings) { const langCode = lang.countryName['xml:lang']; const value = lang.countryName['value']; if (languages.has(langCode) && value != server.name) { translation.languages[languages.get(langCode)] = value; } } for (let city of server.cities) { if (city.name === server.name) { continue; } const cityData = await localizeCity(countryUrl, city); if (cityData) translation.cities.push(cityData); } return translation; } async function localizeCity(countryUrl, city) { console.log(' - Localize city', city.name); const translation = {city: city.name, languages: {}}; let cityUrl; if (cityIDs.has(city.name)) { cityUrl = cityIDs.get(city.name); } else { const sparql = `SELECT ?city WHERE { # wdt:P31 is "instance-of" # wd:Q515 is "city" { ?city wdt:P31 wd:Q515 ; wdt:P17 <${countryUrl}> ; { ?city rdfs:label "${ city.name}"@en . } UNION { ?city skos:altLabel "${city.name}"@en . } } UNION { ?city wdt:P31 ?bigcity ; wdt:P17 <${countryUrl}> ; { ?city rdfs:label "${ city.name}"@en . } UNION { ?city skos:altLabel "${city.name}"@en . } ?bigcity wdt:P279 wd:Q515 . } UNION { ?city wdt:P31 ?metropolis ; wdt:P17 <${countryUrl}> ; { ?city rdfs:label "${ city.name}"@en . } UNION { ?city skos:altLabel "${city.name}"@en . } ?metropolis wdt:P279 ?bigcity . ?bigcity wdt:P279 wd:Q515 . } }`; const url = wbk.sparqlQuery(sparql) const result = await retrieveJson(url); if (!('head' in result)) throw new Error('Invalid SPARQL result (no head)'); if (!('results' in result)) throw new Error('Invalid SPARQL result (no results)'); if (!('bindings' in result.results)) throw new Error('Invalid SPARQL result (no results/bindings)'); if (result.results.bindings.length === 0) { throw new Error('No results'); } cityUrl = result.results.bindings[0].city.value; } console.log(' - WikiData Resource:', cityUrl); const sparql = `SELECT ?cityName (lang(?cityName) as ?lang) WHERE { <${cityUrl}> rdfs:label ?cityName . }`; const url = wbk.sparqlQuery(sparql) const result = await retrieveJson(url); if (!('head' in result)) throw new Error('Invalid SPARQL result (no head)'); if (!('results' in result)) throw new Error('Invalid SPARQL result (no results)'); if (!('bindings' in result.results)) throw new Error('Invalid SPARQL result (no results/bindings)'); if (result.results.bindings.length === 0) { throw new Error('No results'); } for (let lang of result.results.bindings) { const langCode = lang.cityName['xml:lang']; const value = lang.cityName['value']; if (languages.has(langCode) && value != city.name) { translation.languages[languages.get(langCode)] = lang.cityName['value']; } } return translation; } it('Logout', async () => { await vpn.logout(); await vpn.wait(); }); it('quit the app', async () => await vpn.quit()); }); mozilla-vpn-client-2.2.0/tests/functional/testAuthentication.js000066400000000000000000000104311404202232700247530ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const assert = require('assert'); const util = require('util'); const vpn = require('./helper.js'); const FirefoxHelper = require('./firefox.js'); const webdriver = require('selenium-webdriver'), By = webdriver.By, Keys = webdriver.Key, until = webdriver.until; const exec = util.promisify(require('child_process').exec); describe('User authentication', function() { let driver; this.timeout(200000); before(async () => { await vpn.connect(); driver = await FirefoxHelper.createDriver(); }); beforeEach(() => {}); afterEach(() => {}); after(async () => { await driver.quit(); vpn.disconnect(); }); it('reset the app', async () => await vpn.reset()); it('wait for the main view', async () => { assert(await vpn.getLastUrl() === ''); await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); assert(await vpn.getElementProperty('getStarted', 'visible') === 'true'); assert(await vpn.getElementProperty('learnMoreLink', 'visible') === 'true'); }); it('Start and abort the authentication (initial view)', async () => { await vpn.clickOnElement('getStarted'); await vpn.waitForCondition(async () => { const url = await vpn.getLastUrl(); return url.includes('/api/v2/vpn/login'); }); // This is to make humans happy. await vpn.wait(); await vpn.waitForElement('authenticatingView'); await vpn.waitForElementProperty('authenticatingView', 'visible', 'true'); await vpn.waitForElement('cancelFooterLink'); await vpn.waitForElementProperty('cancelFooterLink', 'visible', 'true'); await vpn.clickOnElement('cancelFooterLink'); // This is to make humans happy. await vpn.wait(); await vpn.waitForElement('getStarted'); await vpn.waitForElementProperty('getStarted', 'visible', 'true'); }); it('Start and abort the authentication (onboarding view)', async () => { assert(await vpn.getElementProperty('learnMoreLink', 'visible') === 'true'); await vpn.clickOnElement('learnMoreLink'); await vpn.waitForElement('skipOnboarding'); await vpn.waitForElementProperty('skipOnboarding', 'visible', 'true'); // This is needed just for humans. The UI is already in the other state // before completing the animation. await vpn.wait(); while (true) { assert(await vpn.hasElement('onboardingNext')); assert( await vpn.getElementProperty('onboardingNext', 'visible') === 'true'); assert( await vpn.getElementProperty('onboardingNext', 'visible') === 'true'); if (await vpn.getElementProperty('onboardingNext', 'text') === 'Next') { await vpn.clickOnElement('onboardingNext'); // This is needed just for humans. The UI is already in the other state // before completing the animation. await vpn.wait(); continue; } break; } await vpn.clickOnElement('onboardingNext'); await vpn.waitForCondition(async () => { const url = await vpn.getLastUrl(); return url.includes('/api/v2/vpn/login'); }); // This is to make humans happy. await vpn.wait(); await vpn.waitForElement('authenticatingView'); await vpn.waitForElementProperty('authenticatingView', 'visible', 'true'); await vpn.waitForElement('cancelFooterLink'); await vpn.waitForElementProperty('cancelFooterLink', 'visible', 'true'); await vpn.clickOnElement('cancelFooterLink'); // This is to make humans happy. await vpn.wait(); await vpn.waitForElement('getStarted'); await vpn.waitForElementProperty('getStarted', 'visible', 'true'); }); it('Start and complete the authentication', async () => { await vpn.authenticate(driver, false); }); it('Post authentication view', async () => { await vpn.waitForElement('postAuthenticationButton'); await vpn.clickOnElement('postAuthenticationButton'); // This is to make humans happy. await vpn.wait(); }); it('Logout', async () => { await vpn.logout(); await vpn.wait(); }); it('quit the app', async () => await vpn.quit()); }); mozilla-vpn-client-2.2.0/tests/functional/testCaptivePortal.js000066400000000000000000000127151404202232700245600ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const assert = require('assert'); const util = require('util'); const vpn = require('./helper.js'); const FirefoxHelper = require('./firefox.js'); describe('Captive portal', function() { let driver; this.timeout(100000); before(async () => { await vpn.connect(); driver = await FirefoxHelper.createDriver(); }); beforeEach(() => {}); afterEach(() => {}); after(async () => { await driver.quit(); vpn.disconnect(); }); it('reset the app', async () => await vpn.reset()); it('Enable captive-portal-alert feature', async () => { await vpn.setSetting('captive-portal-alert', 'false'); assert(await vpn.getSetting('captive-portal-alert') === 'false'); await vpn.setSetting('captive-portal-alert', 'true'); assert(await vpn.getSetting('captive-portal-alert') === 'true'); }); it('Captive portal during the main view', async () => { assert(await vpn.getLastUrl() === ''); await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); await vpn.forceCaptivePortalDetection(); await vpn.wait(); // No notifications during the main view. assert(vpn.lastNotification().title === null); }); it('Captive portal during the authentication', async () => { await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); await vpn.clickOnElement('getStarted'); await vpn.waitForCondition(async () => { const url = await vpn.getLastUrl(); return url.includes('/api/v2/vpn/login'); }); await vpn.wait(); await vpn.waitForElement('authenticatingView'); await vpn.waitForElementProperty('authenticatingView', 'visible', 'true'); await vpn.forceCaptivePortalDetection(); await vpn.wait(); // No notifications during the main view. assert(vpn.lastNotification().title === null); await vpn.waitForElement('cancelFooterLink'); await vpn.waitForElementProperty('cancelFooterLink', 'visible', 'true'); await vpn.clickOnElement('cancelFooterLink'); await vpn.wait(); await vpn.waitForElement('getStarted'); await vpn.waitForElementProperty('getStarted', 'visible', 'true'); }); it('authenticate', async () => await vpn.authenticate(driver, false)); it('Captive portal in the Post authentication view', async () => { await vpn.waitForElement('postAuthenticationButton'); await vpn.forceCaptivePortalDetection(); await vpn.wait(); // Notifications are not OK yet. assert(vpn.lastNotification().title === null); await vpn.clickOnElement('postAuthenticationButton'); await vpn.wait(); }); it('Captive portal in the Controller view', async () => { await vpn.waitForElement('controllerTitle'); await vpn.waitForElementProperty('controllerTitle', 'visible', 'true'); assert( await vpn.getElementProperty('controllerTitle', 'text') === 'VPN is off'); await vpn.forceCaptivePortalDetection(); await vpn.wait(); // Notifications are not OK yet. assert(vpn.lastNotification().title === null); }); it('Activate the VPN', async () => { await vpn.activate(); await vpn.waitForCondition(async () => { let connectingMsg = await vpn.getElementProperty('controllerTitle', 'text'); return connectingMsg === 'Connecting…'; }); assert( await vpn.getElementProperty('controllerSubTitle', 'text') === 'Masking connection and location'); await vpn.forceCaptivePortalDetection(); await vpn.wait(); // Notifications are not OK when connecting. assert(vpn.lastNotification().title === null); }); it('Captive portal when connected', async () => { await vpn.waitForCondition(async () => { return await vpn.getElementProperty('controllerTitle', 'text') === 'VPN is on'; }); await vpn.waitForCondition(() => { return vpn.lastNotification().title === 'VPN Connected'; }); vpn.resetLastNotification(); await vpn.forceCaptivePortalDetection(); await vpn.wait(); // Notifications are OK now. assert(vpn.lastNotification().title === 'Guest Wi-Fi portal blocked'); vpn.resetLastNotification(); assert(vpn.lastNotification().title === null); }); it('Clicking the notification', async () => { await vpn.clickOnNotification(); await vpn.waitForCondition(async () => { let connectingMsg = await vpn.getElementProperty('controllerTitle', 'text'); return connectingMsg === 'Disconnecting…'; }); await vpn.waitForCondition(() => { return vpn.lastNotification().title === 'VPN Disconnected'; }); }); it('Wait for recovering', async () => { await vpn.waitForCondition(() => { return vpn.lastNotification().title === 'Guest Wi-Fi portal detected'; }); await vpn.clickOnNotification(); await vpn.waitForCondition(async () => { let connectingMsg = await vpn.getElementProperty('controllerTitle', 'text'); return connectingMsg === 'Connecting…'; }); await vpn.waitForCondition(() => { return vpn.lastNotification().title === 'VPN Connected'; }); }); it('Logout', async () => { await vpn.logout(); await vpn.wait(); }); it('quit the app', async () => await vpn.quit()); }); mozilla-vpn-client-2.2.0/tests/functional/testConnection.js000066400000000000000000000075721404202232700241070ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const assert = require('assert'); const util = require('util'); const vpn = require('./helper.js'); const FirefoxHelper = require('./firefox.js'); const webdriver = require('selenium-webdriver'), By = webdriver.By, Keys = webdriver.Key, until = webdriver.until; const exec = util.promisify(require('child_process').exec); describe('Connectivity', function() { let driver; this.timeout(200000); before(async () => { await vpn.connect(); driver = await FirefoxHelper.createDriver(); }); beforeEach(() => {}); afterEach(() => {}); after(async () => { await driver.quit(); vpn.disconnect(); }); it('authenticate', async () => await vpn.authenticate(driver)); it('Post authentication view', async () => { await vpn.waitForElement('postAuthenticationButton'); await vpn.clickOnElement('postAuthenticationButton'); await vpn.wait(); }); it('check the ui', async () => { await vpn.waitForElement('controllerTitle'); await vpn.waitForElementProperty('controllerTitle', 'visible', 'true'); assert( await vpn.getElementProperty('controllerTitle', 'text') === 'VPN is off'); await vpn.waitForElementProperty('controllerSubTitle', 'visible', 'true'); assert( await vpn.getElementProperty('controllerSubTitle', 'text') === 'Turn on to protect your privacy'); await vpn.waitForElementProperty('controllerToggle', 'visible', 'true'); }); it('connecting', async () => { // TODO: investigate why the click doesn't work on github. // await vpn.clickOnElement('controllerToggle'); await vpn.activate(); await vpn.waitForCondition(async () => { let connectingMsg = await vpn.getElementProperty('controllerTitle', 'text'); return connectingMsg === 'Connecting…'; }); assert( await vpn.getElementProperty('controllerSubTitle', 'text') === 'Masking connection and location'); }); it('connected', async () => { await vpn.waitForCondition(async () => { return await vpn.getElementProperty('controllerTitle', 'text') == 'VPN is on'; }); assert((await vpn.getElementProperty('controllerSubTitle', 'text')) .startsWith('Secure and private ')); await vpn.waitForCondition(() => { return vpn.lastNotification().title === 'VPN Connected'; }); assert(vpn.lastNotification().title === 'VPN Connected'); assert(vpn.lastNotification().message.startsWith('Connected to ')); vpn.wait(); }); it('disconnecting', async () => { // TODO: investigate why the click doesn't work on github. // await vpn.clickOnElement('controllerToggle'); await vpn.deactivate(); await vpn.waitForCondition(async () => { return await vpn.getElementProperty('controllerTitle', 'text') === 'Disconnecting…'; }); assert( await vpn.getElementProperty('controllerSubTitle', 'text') === 'Unmasking connection and location'); vpn.wait(); }); it('disconnected', async () => { await vpn.waitForCondition(async () => { return await vpn.getElementProperty('controllerTitle', 'text') === 'VPN is off'; }); assert( await vpn.getElementProperty('controllerSubTitle', 'text') === 'Turn on to protect your privacy'); await vpn.waitForCondition(() => { return vpn.lastNotification().title === 'VPN Disconnected'; }); assert(vpn.lastNotification().title === 'VPN Disconnected'); assert(vpn.lastNotification().message.startsWith('Disconnected from ')); vpn.wait(); }); it('Logout', async () => { await vpn.logout(); await vpn.wait(); }); it('quit the app', async () => await vpn.quit()); }); mozilla-vpn-client-2.2.0/tests/functional/testDevices.js000066400000000000000000000040251404202232700233600ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const assert = require('assert'); const util = require('util'); const vpn = require('./helper.js'); const FirefoxHelper = require('./firefox.js'); const webdriver = require('selenium-webdriver'), By = webdriver.By, Keys = webdriver.Key, until = webdriver.until; const exec = util.promisify(require('child_process').exec); describe('Devices', function() { let driver; this.timeout(1000000); before(async () => { await vpn.connect(); driver = await FirefoxHelper.createDriver(); }); beforeEach(() => {}); afterEach(() => {}); after(async () => { await driver.quit(); vpn.disconnect(); }); it('authenticate', async () => await vpn.authenticate(driver)); it('Post authentication view', async () => { await vpn.waitForElement('postAuthenticationButton'); await vpn.clickOnElement('postAuthenticationButton'); await vpn.wait(); }); it('opening the device list', async () => { await vpn.waitForElement('deviceListButton'); await vpn.waitForElementProperty('deviceListButton', 'visible', 'true'); await vpn.wait(); await vpn.clickOnElement('deviceListButton'); await vpn.wait(); await vpn.waitForElement('deviceListBackButton'); await vpn.waitForElementProperty('deviceListBackButton', 'visible', 'true'); await vpn.clickOnElement('deviceListBackButton'); await vpn.wait(); await vpn.waitForElement('deviceListButton'); await vpn.waitForElementProperty('deviceListButton', 'visible', 'true'); await vpn.wait(); }); // TODO: test the device title X of Y // TODO: check the device entries in the list // TODO: check the 'remove icon' visibility // TODO: remove a device // TODO: max number of devices it('Logout', async () => { await vpn.logout(); await vpn.wait(); }); it('quit the app', async () => await vpn.quit()); }); mozilla-vpn-client-2.2.0/tests/functional/testHeartbeat.js000066400000000000000000000162211404202232700236760ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const assert = require('assert'); const util = require('util'); const vpn = require('./helper.js'); const FirefoxHelper = require('./firefox.js'); describe('Backend failure', function() { let driver; async function backendFailureAndRestore() { await vpn.forceHeartbeatFailure(); await vpn.waitForElement('heartbeatTryButton'); await vpn.waitForElementProperty('heartbeatTryButton', 'visible', 'true'); await vpn.wait(); await vpn.clickOnElement('heartbeatTryButton'); await vpn.wait(); } this.timeout(100000); before(async () => { await vpn.connect(); driver = await FirefoxHelper.createDriver(); }); beforeEach(() => {}); afterEach(() => {}); after(async () => { await driver.quit(); vpn.disconnect(); }); it('reset the app', async () => await vpn.reset()); it('Backend failure during the main view', async () => { assert(await vpn.getLastUrl() === ''); await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); await backendFailureAndRestore(); await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); await vpn.wait(); }); it('Backend failure in the help menu', async () => { await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); await vpn.clickOnElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'false'); await vpn.waitForElement('getHelpBack'); await vpn.waitForElementProperty('getHelpBack', 'visible', 'true'); await backendFailureAndRestore(); await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); }); it('Backend failure in the onboarding (aborting in each phase)', async () => { let onboardingView = 0; let onboarding = true; while (onboarding) { assert( await vpn.getElementProperty('learnMoreLink', 'visible') === 'true'); await vpn.clickOnElement('learnMoreLink'); await vpn.waitForElement('skipOnboarding'); await vpn.waitForElementProperty('skipOnboarding', 'visible', 'true'); await vpn.wait(); for (let i = 0; i < onboardingView; ++i) { assert(await vpn.hasElement('onboardingNext')); assert( await vpn.getElementProperty('onboardingNext', 'visible') === 'true'); await vpn.clickOnElement('onboardingNext'); await vpn.wait(); } assert( await vpn.getElementProperty('onboardingNext', 'visible') === 'true'); onboarding = await vpn.getElementProperty('onboardingNext', 'text') === 'Next'; await backendFailureAndRestore(); await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); await vpn.wait(); ++onboardingView; } }); it('BackendFailure during the authentication', async () => { await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); await vpn.clickOnElement('getStarted'); await vpn.waitForCondition(async () => { const url = await vpn.getLastUrl(); return url.includes('/api/v2/vpn/login'); }); await vpn.wait(); await vpn.waitForElement('authenticatingView'); await vpn.waitForElementProperty('authenticatingView', 'visible', 'true'); await backendFailureAndRestore(); await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); await vpn.wait(); }); it('authenticate', async () => await vpn.authenticate(driver, false)); it('BackendFailure in the Post authentication view', async () => { await vpn.waitForElement('postAuthenticationButton'); await backendFailureAndRestore(); await vpn.waitForElement('controllerTitle'); await vpn.waitForElementProperty('controllerTitle', 'visible', 'true'); }); it('BackendFailure in the Controller view', async () => { await vpn.waitForElement('controllerTitle'); await vpn.waitForElementProperty('controllerTitle', 'visible', 'true'); assert( await vpn.getElementProperty('controllerTitle', 'text') === 'VPN is off'); await backendFailureAndRestore(); await vpn.waitForElement('controllerTitle'); await vpn.waitForElementProperty('controllerTitle', 'visible', 'true'); assert( await vpn.getElementProperty('controllerTitle', 'text') === 'VPN is off'); }); it('BackendFailure when connecting', async () => { await vpn.activate(); await vpn.waitForCondition(async () => { let connectingMsg = await vpn.getElementProperty('controllerTitle', 'text'); return connectingMsg === 'Connecting…'; }); assert( await vpn.getElementProperty('controllerSubTitle', 'text') === 'Masking connection and location'); await backendFailureAndRestore(); await vpn.waitForElement('controllerTitle'); await vpn.waitForElementProperty('controllerTitle', 'visible', 'true'); assert( await vpn.getElementProperty('controllerTitle', 'text') === 'VPN is off'); }); it('connecting', async () => { await vpn.activate(); await vpn.waitForCondition(async () => { let connectingMsg = await vpn.getElementProperty('controllerTitle', 'text'); return connectingMsg === 'Connecting…'; }); }); it('BackendFailure when connected', async () => { await vpn.waitForCondition(async () => { return await vpn.getElementProperty('controllerTitle', 'text') === 'VPN is on'; }); await backendFailureAndRestore(); await vpn.waitForElement('controllerTitle'); await vpn.waitForElementProperty('controllerTitle', 'visible', 'true'); assert( await vpn.getElementProperty('controllerTitle', 'text') === 'VPN is off'); }); it('connecting', async () => { await vpn.activate(); await vpn.waitForCondition(async () => { let connectingMsg = await vpn.getElementProperty('controllerTitle', 'text'); return connectingMsg === 'Connecting…'; }); }); it('connected', async () => { await vpn.waitForCondition(async () => { return await vpn.getElementProperty('controllerTitle', 'text') === 'VPN is on'; }); vpn.wait(); }); it('disconnecting', async () => { await vpn.deactivate(); await vpn.waitForCondition(async () => { return await vpn.getElementProperty('controllerTitle', 'text') === 'Disconnecting…'; }); await backendFailureAndRestore(); await vpn.waitForElement('controllerTitle'); await vpn.waitForElementProperty('controllerTitle', 'visible', 'true'); assert( await vpn.getElementProperty('controllerTitle', 'text') === 'VPN is off'); }); it('Logout', async () => { await vpn.logout(); await vpn.wait(); }); it('quit the app', async () => await vpn.quit()); }); mozilla-vpn-client-2.2.0/tests/functional/testOnboarding.js000066400000000000000000000107421404202232700240630ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const assert = require('assert'); const util = require('util'); const vpn = require('./helper.js'); describe('Initial view and onboarding', function() { this.timeout(100000); before(async () => { await vpn.connect(); }); beforeEach(() => {}); afterEach(() => {}); after(async () => { vpn.disconnect(); }); it('reset the app', async () => await vpn.reset()); it('wait for the main view', async () => { assert(await vpn.getLastUrl() === ''); await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); assert(await vpn.getElementProperty('getStarted', 'visible') === 'true'); assert(await vpn.getElementProperty('learnMoreLink', 'visible') === 'true'); }); it('Open the help menu', async () => { await vpn.clickOnElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'false'); await vpn.waitForElement('getHelpBack'); await vpn.waitForElementProperty('getHelpBack', 'visible', 'true'); }); it('Open some links', async () => { await vpn.waitForElement('getHelpBackList'); await vpn.waitForElementProperty('getHelpBackList', 'visible', 'true'); await vpn.waitForElement('getHelpBackList/getHelpBackList-0'); await vpn.waitForElementProperty( 'getHelpBackList/getHelpBackList-0', 'visible', 'true'); await vpn.waitForElement('getHelpBackList/getHelpBackList-1'); await vpn.waitForElementProperty( 'getHelpBackList/getHelpBackList-1', 'visible', 'true'); await vpn.waitForElement('getHelpBackList/getHelpBackList-2'); await vpn.waitForElementProperty( 'getHelpBackList/getHelpBackList-2', 'visible', 'true'); await vpn.clickOnElement('getHelpBackList/getHelpBackList-0'); await vpn.waitForCondition(async () => { const url = await vpn.getLastUrl(); return url.startsWith('file://') && url.includes('mozillavpn') && url.endsWith('.txt'); }); await vpn.clickOnElement('getHelpBackList/getHelpBackList-1'); await vpn.waitForCondition(async () => { const url = await vpn.getLastUrl(); return url.endsWith('/r/vpn/support'); }); await vpn.clickOnElement('getHelpBackList/getHelpBackList-2'); await vpn.waitForCondition(async () => { const url = await vpn.getLastUrl(); return url.endsWith('/r/vpn/contact'); }); }); it('Go back to the main view', async () => { await vpn.clickOnElement('getHelpBack'); await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); // This is needed just for humans. The UI is already in the other state // before completing the animation. await vpn.wait(); }); it('Complete the onboarding (aborting in each phase)', async () => { let onboardingView = 0; while (true) { assert( await vpn.getElementProperty('learnMoreLink', 'visible') === 'true'); await vpn.clickOnElement('learnMoreLink'); await vpn.waitForElement('skipOnboarding'); await vpn.waitForElementProperty('skipOnboarding', 'visible', 'true'); // This is needed just for humans. The UI is already in the other state // before completing the animation. await vpn.wait(); for (let i = 0; i < onboardingView; ++i) { assert(await vpn.hasElement('onboardingNext')); assert( await vpn.getElementProperty('onboardingNext', 'visible') === 'true'); await vpn.clickOnElement('onboardingNext'); // This is needed just for humans. The UI is already in the other state // before completing the animation. await vpn.wait(); } assert( await vpn.getElementProperty('onboardingNext', 'visible') === 'true'); if (await vpn.getElementProperty('onboardingNext', 'text') !== 'Next') { break; } await vpn.clickOnElement('skipOnboarding'); await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); // This is needed just for humans. The UI is already in the other state // before completing the animation. await vpn.wait(); ++onboardingView; } assert(onboardingView, 4); }); it('quit the app', async () => await vpn.quit()); }); mozilla-vpn-client-2.2.0/tests/functional/testScreenCapture.js000066400000000000000000000325331404202232700245460ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const assert = require('assert'); const fs = require('fs'); const util = require('util'); const vpn = require('./helper.js'); const FirefoxHelper = require('./firefox.js'); const dir = '/tmp/screencapture'; describe('Take screenshots for each view', function() { let languages = []; let driver; let servers; this.timeout(2000000); async function singleScreenCapture(name, language) { const data = await vpn.screenCapture(); const buffer = Buffer.from(data, 'base64'); fs.writeFileSync(`${dir}/${name}_${language}.png`, buffer); } async function screenCapture(name, cb = null) { for (let language of languages) { await vpn.setSetting('language-code', language); if (cb) await cb(); // we need to give time to the app to retranslate the UI. If the number // is too slow we have the UI in funny states (part in 1 language, part // in another language, ...). But if the number is too high, the // "connecting" state is faster and we do not take all the screen // captures for all the languages. await new Promise(r => setTimeout(r, 30)); await singleScreenCapture(name, language); } } before(async () => { await vpn.connect(); driver = await FirefoxHelper.createDriver(); if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } }); beforeEach(() => {}); afterEach(() => {}); after(async () => { await driver.quit(); vpn.disconnect(); }); it('Retrieve the list of languages', async () => { const codes = await vpn.languages(); for (let c of codes) { if (c !== 'en') languages.push(c); } // English at the end. languages.push('en'); }); it('reset the app', async () => await vpn.reset()); it('initial view', async () => { await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); await vpn.wait(); await screenCapture('initialize'); }); it('heartbeat', async () => { await screenCapture('heartbeat', async () => { await vpn.reset(); await vpn.forceHeartbeatFailure(); await vpn.waitForElement('heartbeatTryButton'); await vpn.waitForElementProperty('heartbeatTryButton', 'visible', 'true'); }); await vpn.wait(); await vpn.clickOnElement('heartbeatTryButton'); await vpn.wait(); await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); }); it('help view', async () => { await vpn.clickOnElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'false'); await vpn.waitForElement('getHelpBack'); await vpn.waitForElementProperty('getHelpBack', 'visible', 'true'); await screenCapture('help'); }); it('Go back to the main view', async () => { await vpn.clickOnElement('getHelpBack'); await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); await vpn.wait(); }); it('Onboarding', async () => { assert(await vpn.getElementProperty('learnMoreLink', 'visible') === 'true'); await vpn.clickOnElement('learnMoreLink'); await vpn.wait(); await vpn.waitForElement('skipOnboarding'); await vpn.waitForElementProperty('skipOnboarding', 'visible', 'true'); let onboardingView = 0; while (true) { await screenCapture(`onboarding_${++onboardingView}`); assert(await vpn.hasElement('onboardingNext')); assert( await vpn.getElementProperty('onboardingNext', 'visible') === 'true'); await vpn.setSetting('language-code', 'en'); if (await vpn.getElementProperty('onboardingNext', 'text') !== 'Next') { break; } await vpn.clickOnElement('onboardingNext'); await vpn.wait(); } }); it('Authenticating', async () => { await vpn.clickOnElement('onboardingNext'); await vpn.wait(); await vpn.waitForCondition(async () => { const url = await vpn.getLastUrl(); return url.includes('/api/v2/vpn/login'); }); await screenCapture('authenticating'); }); it('authenticate', async () => await vpn.authenticate(driver)); it('post authentication view', async () => { await vpn.waitForElement('postAuthenticationButton'); await vpn.wait(); await screenCapture('post_authenticate'); await vpn.clickOnElement('postAuthenticationButton'); await vpn.wait(); }); it('main view', async () => { await screenCapture('vpn_off'); }); it('retrieve list of servers and the current one', async () => { servers = await vpn.servers(); }); it('server view', async () => { await vpn.waitForElement('serverListButton'); await vpn.waitForElementProperty('serverListButton', 'visible', 'true'); await vpn.clickOnElement('serverListButton'); await vpn.wait(); for (let language of languages) { await vpn.setSetting('language-code', language); // Let's open all the server items for (let server of servers) { const countryId = 'serverCountryList/serverCountry-' + server.code; await vpn.waitForElement(countryId); await vpn.waitForElementProperty(countryId, 'visible', 'true'); await vpn.setElementProperty( 'serverCountryView', 'contentY', 'i', parseInt(await vpn.getElementProperty(countryId, 'y'))); await new Promise(r => setTimeout(r, 30)); if (await vpn.getElementProperty(countryId, 'cityListVisible') === 'false') { await vpn.clickOnElement(countryId); await new Promise(r => setTimeout(r, 30)); } } const contentHeight = parseInt( await vpn.getElementProperty('serverCountryView', 'contentHeight')) const height = parseInt(await vpn.getElementProperty('serverCountryView', 'height')); await vpn.setElementProperty('serverCountryView', 'contentY', 'i', 0); await singleScreenCapture('server', language); let contentY = 0; let scrollId = 0; while (true) { if (contentHeight <= (contentY + height)) { break; } contentY += height; await vpn.setElementProperty( 'serverCountryView', 'contentY', 'i', contentY); await singleScreenCapture(`server_${++scrollId}`, language); } } await vpn.waitForElement('serverListBackButton'); await vpn.waitForElementProperty('serverListBackButton', 'visible', 'true'); await vpn.clickOnElement('serverListBackButton'); await vpn.wait(); await vpn.waitForElement('serverListButton'); await vpn.waitForElementProperty('serverListButton', 'visible', 'true'); await vpn.wait(); }); it('connecting', async () => { await vpn.activate(); await vpn.waitForCondition(async () => { let connectingMsg = await vpn.getElementProperty('controllerTitle', 'text'); return connectingMsg === 'Connecting…'; }); await screenCapture('vpn_connecting'); }); it('connected', async () => { await vpn.waitForCondition(async () => { return await vpn.getElementProperty('controllerTitle', 'text') == 'VPN is on'; }); await screenCapture('vpn_on'); }); it('connection info', async () => { await vpn.waitForElement('connectionInfoButton'); await vpn.clickOnElement('connectionInfoButton'); await vpn.wait(); await screenCapture('connection_info'); await vpn.waitForElement('connectionInfoBackButton'); await vpn.clickOnElement('connectionInfoBackButton'); await vpn.wait(); }); it('settings', async () => { await vpn.waitForElement('settingsButton'); await vpn.clickOnElement('settingsButton'); await vpn.wait(); await screenCapture('settings'); const contentHeight = parseInt(await vpn.getElementProperty('settingsView', 'contentHeight')) const height = parseInt(await vpn.getElementProperty('settingsView', 'height')); let contentY = parseInt(await vpn.getElementProperty('settingsView', 'contentY')); let scrollId = 0; while (true) { if (contentHeight <= (contentY + height)) { break; } contentY += height; await vpn.setElementProperty('settingsView', 'contentY', 'i', contentY); await screenCapture(`settings_${++scrollId}`); } }); it('settings / networking', async () => { await vpn.waitForElement('settingsNetworking'); await vpn.waitForElementProperty('settingsNetworking', 'visible', 'true'); await vpn.setElementProperty( 'settingsView', 'contentY', 'i', parseInt(await vpn.getElementProperty('settingsNetworking', 'y'))); await vpn.wait(); await vpn.clickOnElement('settingsNetworking'); await vpn.wait(); await screenCapture('settings_networking'); await vpn.clickOnElement('settingsNetworkingBackButton'); await vpn.wait(); await vpn.waitForElement('manageAccountButton'); await vpn.waitForElementProperty('manageAccountButton', 'visible', 'true'); }); it('settings / notifications', async () => { await vpn.waitForElement('settingsNotifications'); await vpn.waitForElementProperty( 'settingsNotifications', 'visible', 'true'); await vpn.setElementProperty( 'settingsView', 'contentY', 'i', parseInt(await vpn.getElementProperty('settingsNotifications', 'y'))); await vpn.wait(); await vpn.clickOnElement('settingsNotifications'); await vpn.wait(); await screenCapture('settings_notification'); await vpn.clickOnElement('settingsNotificationsBackButton'); await vpn.wait(); await vpn.waitForElement('manageAccountButton'); await vpn.waitForElementProperty('manageAccountButton', 'visible', 'true'); }); it('settings / languages', async () => { await vpn.waitForElement('settingsLanguages'); await vpn.waitForElementProperty('settingsLanguages', 'visible', 'true'); await vpn.setElementProperty( 'settingsView', 'contentY', 'i', parseInt(await vpn.getElementProperty('settingsLanguages', 'y'))); await vpn.wait(); await vpn.clickOnElement('settingsLanguages'); await vpn.wait(); await screenCapture('settings_languages'); const contentHeight = parseInt( await vpn.getElementProperty('settingsLanguagesView', 'contentHeight')) const height = parseInt( await vpn.getElementProperty('settingsLanguagesView', 'height')); let contentY = parseInt( await vpn.getElementProperty('settingsLanguagesView', 'contentY')); let scrollId = 0; while (true) { if (contentHeight <= (contentY + height)) { break; } contentY += height; await vpn.setElementProperty( 'settingsLanguagesView', 'contentY', 'i', contentY); await screenCapture(`settings_languages_${++scrollId}`); } await vpn.clickOnElement('settingsLanguagesBackButton'); await vpn.wait(); await vpn.waitForElement('manageAccountButton'); await vpn.waitForElementProperty('manageAccountButton', 'visible', 'true'); await vpn.waitForElementProperty( 'manageAccountButton', 'text', 'Manage account'); }); // TODO: app-permission it('settings / about us', async () => { await vpn.waitForElement('settingsAboutUs'); await vpn.waitForElementProperty('settingsAboutUs', 'visible', 'true'); await vpn.setElementProperty( 'settingsView', 'contentY', 'i', parseInt(await vpn.getElementProperty('settingsAboutUs', 'y'))); await vpn.wait(); await vpn.clickOnElement('settingsAboutUs'); await vpn.wait(); await screenCapture('settings_about'); await vpn.clickOnElement('aboutUsBackButton'); await vpn.wait(); await vpn.waitForElement('manageAccountButton'); await vpn.waitForElementProperty('manageAccountButton', 'visible', 'true'); }); it('settings / help', async () => { await vpn.waitForElement('settingsGetHelp'); await vpn.waitForElementProperty('settingsGetHelp', 'visible', 'true'); await vpn.setElementProperty( 'settingsView', 'contentY', 'i', parseInt(await vpn.getElementProperty('settingsGetHelp', 'y'))); await vpn.wait(); await vpn.clickOnElement('settingsGetHelp'); await vpn.wait(); await screenCapture('settings_help'); await vpn.clickOnElement('getHelpBack'); await vpn.wait(); await vpn.waitForElement('settingsGetHelp'); await vpn.waitForElementProperty('settingsGetHelp', 'visible', 'true'); }); it('closing the settings view', async () => { await vpn.setElementProperty('settingsView', 'contentY', 'i', 0); await vpn.wait(); await vpn.clickOnElement('settingsCloseButton'); await vpn.wait(); await vpn.waitForElement('controllerTitle'); await vpn.waitForElementProperty('controllerTitle', 'visible', 'true'); }); it('disconnecting', async () => { await vpn.deactivate(); await vpn.waitForCondition(async () => { return await vpn.getElementProperty('controllerTitle', 'text') === 'Disconnecting…'; }); await screenCapture('vpn_disconnecting'); await vpn.waitForCondition(async () => { return await vpn.getElementProperty('controllerTitle', 'text') === 'VPN is off'; }); }); it('quit the app', async () => await vpn.quit()); }); mozilla-vpn-client-2.2.0/tests/functional/testServers.js000066400000000000000000000204201404202232700234240ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const assert = require('assert'); const util = require('util'); const vpn = require('./helper.js'); const FirefoxHelper = require('./firefox.js'); const webdriver = require('selenium-webdriver'), By = webdriver.By, Keys = webdriver.Key, until = webdriver.until; const exec = util.promisify(require('child_process').exec); describe('Server list', function() { let driver; let servers; let currentCountryCode; let currentCity; this.timeout(1000000); before(async () => { await vpn.connect(); driver = await FirefoxHelper.createDriver(); }); beforeEach(() => {}); afterEach(() => {}); after(async () => { await driver.quit(); vpn.disconnect(); }); it('authenticate', async () => await vpn.authenticate(driver)); it('Post authentication view', async () => { await vpn.waitForElement('postAuthenticationButton'); await vpn.clickOnElement('postAuthenticationButton'); await vpn.wait(); }); it('opening the server list', async () => { await vpn.waitForElement('serverListButton'); await vpn.waitForElementProperty('serverListButton', 'visible', 'true'); await vpn.wait(); await vpn.clickOnElement('serverListButton'); await vpn.wait(); await vpn.waitForElement('serverListBackButton'); await vpn.waitForElementProperty('serverListBackButton', 'visible', 'true'); await vpn.clickOnElement('serverListBackButton'); await vpn.wait(); await vpn.waitForElement('serverListButton'); await vpn.waitForElementProperty('serverListButton', 'visible', 'true'); await vpn.wait(); await vpn.clickOnElement('serverListButton'); await vpn.wait(); }); it('retrieve list of servers and the current one', async () => { servers = await vpn.servers(); currentCountryCode = await vpn.getSetting('current-server-country-code'); currentCity = await vpn.getSetting('current-server-city'); }); it('check the countries and cities', async () => { for (let server of servers) { const countryId = 'serverCountryList/serverCountry-' + server.code; await vpn.waitForElement(countryId); await vpn.waitForElementProperty(countryId, 'visible', 'true'); await vpn.setElementProperty( 'serverCountryView', 'contentY', 'i', parseInt(await vpn.getElementProperty(countryId, 'y'))); await vpn.wait(); if (currentCountryCode === server.code) { assert( await vpn.getElementProperty(countryId, 'cityListVisible') === 'true'); } if (await vpn.getElementProperty(countryId, 'cityListVisible') === 'false') { await vpn.clickOnElement(countryId); } for (let city of server.cities) { const cityId = countryId + '/serverCityList/serverCity-' + city.name.replace(/ /g, '_'); await vpn.waitForElement(cityId); await vpn.waitForElementProperty(cityId, 'visible', 'true'); await vpn.waitForElementProperty( cityId, 'checked', currentCountryCode === server.code && currentCity === city.name ? 'true' : 'false'); } } }); it('pick cities', async () => { for (let server of servers) { const countryId = 'serverCountryList/serverCountry-' + server.code; await vpn.waitForElement(countryId); await vpn.setElementProperty( 'serverCountryView', 'contentY', 'i', parseInt(await vpn.getElementProperty(countryId, 'y'))); await vpn.wait(); if (await vpn.getElementProperty(countryId, 'cityListVisible') === 'false') { await vpn.clickOnElement(countryId); } await vpn.waitForElementProperty(countryId, 'cityListVisible', 'true'); for (let city of server.cities) { const cityId = countryId + '/serverCityList/serverCity-' + city.name.replace(/ /g, '_'); await vpn.waitForElement(cityId); await vpn.setElementProperty( 'serverCountryView', 'contentY', 'i', parseInt(await vpn.getElementProperty(cityId, 'y')) + parseInt(await vpn.getElementProperty(countryId, 'y'))); await vpn.waitForElementProperty(cityId, 'visible', 'true'); const cityName = await vpn.getElementProperty(cityId, 'radioButtonLabelText'); await vpn.wait(); await vpn.clickOnElement(cityId); await vpn.wait(); currentCountryCode = server.code; currentCity = city.name; // Back to the main view. await vpn.waitForElement('serverListButton'); await vpn.waitForElementProperty('serverListButton', 'visible', 'true'); await vpn.waitForElementProperty( 'serverListButton', 'subtitleText', cityName); await vpn.clickOnElement('serverListButton'); await vpn.wait(); // One selected await vpn.waitForElement(cityId); await vpn.waitForElementProperty(cityId, 'checked', 'true'); } } }); it('server switching', async () => { await vpn.waitForElement('serverListBackButton'); await vpn.waitForElementProperty('serverListBackButton', 'visible', 'true'); await vpn.clickOnElement('serverListBackButton'); await vpn.wait(); await vpn.waitForElement('serverListButton'); await vpn.waitForElementProperty('serverListButton', 'visible', 'true'); await vpn.wait(); await vpn.activate(); await vpn.waitForCondition(async () => { return await vpn.getElementProperty('controllerTitle', 'text') == 'VPN is on'; }); let currentCountry = ''; for (let server of servers) { if (server.code === currentCountryCode) { currentCountry = server.name; break; } } assert(currentCountry != ''); assert(vpn.lastNotification().title === 'VPN Connected'); assert( vpn.lastNotification().message === `Connected to ${currentCountry}, ${currentCity}`); await vpn.clickOnElement('serverListButton'); await vpn.wait(); let server; while (true) { server = servers[Math.floor(Math.random() * servers.length)]; if (server.code != currentCountryCode) break; } const countryId = 'serverCountryList/serverCountry-' + server.code; await vpn.waitForElement(countryId); await vpn.setElementProperty( 'serverCountryView', 'contentY', 'i', parseInt(await vpn.getElementProperty(countryId, 'y'))); await vpn.clickOnElement(countryId); let city = server.cities[Math.floor(Math.random() * server.cities.length)]; const cityId = countryId + '/serverCityList/serverCity-' + city.name.replace(/ /g, '_'); await vpn.waitForElement(cityId); await vpn.setElementProperty( 'serverCountryView', 'contentY', 'i', parseInt(await vpn.getElementProperty(cityId, 'y')) + parseInt(await vpn.getElementProperty(countryId, 'y'))); await vpn.wait(); await vpn.clickOnElement(cityId); const previousCountry = currentCountry; const previousCity = currentCity; currentCountryCode = server.code; currentCountry = server.name; currentCity = city.name; await vpn.waitForElement('controllerTitle'); await vpn.waitForElementProperty('controllerTitle', 'visible', 'true'); await vpn.waitForCondition(async () => { let connectingMsg = await vpn.getElementProperty('controllerTitle', 'text'); return connectingMsg === 'Switching…'; }); assert( await vpn.getElementProperty('controllerSubTitle', 'text') === `From ${previousCity} to ${currentCity}`); await vpn.waitForCondition(async () => { return await vpn.getElementProperty('controllerTitle', 'text') == 'VPN is on'; }); assert(vpn.lastNotification().title === 'VPN Switched Servers'); assert( vpn.lastNotification().message === `Switched from ${previousCountry}, ${previousCity} to ${ currentCountry}, ${currentCity}`); }); // TODO: server list disabled when reached the device limit it('Logout', async () => { await vpn.logout(); await vpn.wait(); }); it('quit the app', async () => await vpn.quit()); }); mozilla-vpn-client-2.2.0/tests/functional/testSettings.js000066400000000000000000000324231404202232700236010ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const assert = require('assert'); const util = require('util'); const vpn = require('./helper.js'); const FirefoxHelper = require('./firefox.js'); const webdriver = require('selenium-webdriver'), By = webdriver.By, Keys = webdriver.Key, until = webdriver.until; const exec = util.promisify(require('child_process').exec); describe('Settings', function() { let driver; this.timeout(200000); before(async () => { await vpn.connect(); driver = await FirefoxHelper.createDriver(); }); beforeEach(() => {}); afterEach(() => {}); after(async () => { await driver.quit(); vpn.disconnect(); }); async function checkSetting(objectName, settingKey) { await vpn.waitForElement(objectName); await vpn.waitForElementProperty(objectName, 'visible', 'true'); assert( await vpn.getElementProperty(objectName, 'isChecked') === await vpn.getSetting(settingKey)); await vpn.setSetting(settingKey, true); assert((await vpn.getSetting(settingKey)) === 'true'); assert((await vpn.getElementProperty(objectName, 'isChecked')) === 'true'); await vpn.wait(); await vpn.setSetting(settingKey, false); assert((await vpn.getSetting(settingKey)) === 'false'); assert((await vpn.getElementProperty(objectName, 'isChecked')) === 'false'); await vpn.wait(); } it('authenticate', async () => await vpn.authenticate(driver)); it('Post authentication view', async () => { await vpn.waitForElement('postAuthenticationButton'); await vpn.clickOnElement('postAuthenticationButton'); await vpn.wait(); }); it('Opening and closing the settings view', async () => { await vpn.waitForElement('settingsButton'); await vpn.clickOnElement('settingsButton'); await vpn.wait(); await vpn.waitForElement('settingsCloseButton'); await vpn.waitForElementProperty('settingsCloseButton', 'visible', 'true'); await vpn.clickOnElement('settingsCloseButton'); await vpn.wait(); await vpn.waitForElement('controllerTitle'); await vpn.waitForElementProperty('controllerTitle', 'visible', 'true'); }); it('Checking settings entries', async () => { await vpn.waitForElement('settingsButton'); await vpn.clickOnElement('settingsButton'); await vpn.wait(); await vpn.waitForElement('manageAccountButton'); await vpn.waitForElementProperty('manageAccountButton', 'visible', 'true'); await vpn.clickOnElement('manageAccountButton'); await vpn.waitForCondition(async () => { const url = await vpn.getLastUrl(); return url.endsWith('/r/vpn/account'); }); await checkSetting('settingStartAtBoot', 'start-at-boot'); }); it('Checking the networking settings', async () => { await vpn.waitForElement('settingsNetworking'); await vpn.waitForElementProperty('settingsNetworking', 'visible', 'true'); await vpn.setElementProperty( 'settingsView', 'contentY', 'i', parseInt(await vpn.getElementProperty('settingsNetworking', 'y'))); await vpn.wait(); await vpn.clickOnElement('settingsNetworking'); await vpn.wait(); await checkSetting('settingIpv6Enabled', 'ipv6-enabled'); await checkSetting('settingLocalNetworkAccess', 'local-network-access'); await vpn.clickOnElement('settingsNetworkingBackButton'); await vpn.wait(); await vpn.waitForElement('manageAccountButton'); await vpn.waitForElementProperty('manageAccountButton', 'visible', 'true'); }); it('Checking the notifications settings', async () => { await vpn.waitForElement('settingsNotifications'); await vpn.waitForElementProperty( 'settingsNotifications', 'visible', 'true'); await vpn.setElementProperty( 'settingsView', 'contentY', 'i', parseInt(await vpn.getElementProperty('settingsNotifications', 'y'))); await vpn.wait(); await vpn.clickOnElement('settingsNotifications'); await vpn.wait(); /* TODO: captive-portal disabled await checkSetting('settingCaptivePortalAlert', 'captive-portal-alert'); await checkSetting( 'settingUnsecuredNetworkAlert', 'unsecured-network-alert'); */ await vpn.clickOnElement('settingsNotificationsBackButton'); await vpn.wait(); await vpn.waitForElement('manageAccountButton'); await vpn.waitForElementProperty('manageAccountButton', 'visible', 'true'); }); it('Checking the languages settings', async () => { await vpn.setSetting('language-code', ''); await vpn.waitForElement('settingsLanguages'); await vpn.waitForElementProperty('settingsLanguages', 'visible', 'true'); await vpn.setElementProperty( 'settingsView', 'contentY', 'i', parseInt(await vpn.getElementProperty('settingsLanguages', 'y'))); await vpn.wait(); await vpn.clickOnElement('settingsLanguages'); await vpn.wait(); await vpn.waitForElement('settingsLanguagesBackButton'); await vpn.waitForElementProperty( 'settingsLanguagesBackButton', 'visible', 'true'); await vpn.waitForElement('settingsSystemLanguageToggle'); await vpn.waitForElementProperty( 'settingsSystemLanguageToggle', 'visible', 'true'); await vpn.waitForElementProperty( 'settingsSystemLanguageToggle', 'checked', 'true'); await vpn.clickOnElement('settingsSystemLanguageToggle'); await vpn.waitForElementProperty( 'settingsSystemLanguageToggle', 'checked', 'false'); await vpn.setElementProperty( 'settingsLanguagesView', 'contentY', 'i', parseInt( await vpn.getElementProperty('languageList/language-it', 'y'))); await vpn.wait(); await vpn.waitForElement('languageList/language-it'); await vpn.waitForElementProperty( 'languageList/language-it', 'visible', 'true'); await vpn.clickOnElement('languageList/language-it'); await vpn.wait(); await vpn.clickOnElement('settingsLanguagesBackButton'); await vpn.wait(); await vpn.setElementProperty('settingsView', 'contentY', 'i', 0); await vpn.wait(); await vpn.waitForElement('manageAccountButton'); await vpn.waitForElementProperty('manageAccountButton', 'visible', 'true'); await vpn.waitForElementProperty( 'manageAccountButton', 'text', 'Gestisci account'); await vpn.setElementProperty( 'settingsView', 'contentY', 'i', parseInt(await vpn.getElementProperty('settingsLanguages', 'y'))); await vpn.wait(); await vpn.clickOnElement('settingsLanguages'); await vpn.wait(); await vpn.waitForElement('settingsLanguagesBackButton'); await vpn.waitForElementProperty( 'settingsLanguagesBackButton', 'visible', 'true'); await vpn.waitForElementProperty( 'settingsSystemLanguageToggle', 'checked', 'false'); await vpn.setElementProperty( 'settingsLanguagesView', 'contentY', 'i', parseInt( await vpn.getElementProperty('languageList/language-en', 'y'))); await vpn.wait(); await vpn.waitForElement('languageList/language-en'); await vpn.waitForElementProperty( 'languageList/language-en', 'visible', 'true'); await vpn.clickOnElement('languageList/language-en'); await vpn.wait(); await vpn.clickOnElement('settingsLanguagesBackButton'); await vpn.wait(); await vpn.waitForElement('manageAccountButton'); await vpn.waitForElementProperty('manageAccountButton', 'visible', 'true'); await vpn.waitForElementProperty( 'manageAccountButton', 'text', 'Manage account'); await vpn.clickOnElement('settingsLanguages'); await vpn.wait(); await vpn.waitForElement('settingsLanguagesBackButton'); await vpn.setElementProperty('settingsLanguagesView', 'contentY', 'i', 0); await vpn.wait(); await vpn.waitForElementProperty( 'settingsLanguagesBackButton', 'visible', 'true'); await vpn.waitForElementProperty( 'settingsSystemLanguageToggle', 'checked', 'false'); await vpn.clickOnElement('settingsSystemLanguageToggle'); await vpn.waitForElementProperty( 'settingsSystemLanguageToggle', 'checked', 'true'); await vpn.clickOnElement('settingsLanguagesBackButton'); await vpn.wait(); await vpn.waitForElement('manageAccountButton'); await vpn.waitForElementProperty('manageAccountButton', 'visible', 'true'); await vpn.waitForElementProperty( 'manageAccountButton', 'text', 'Manage account'); }); // TODO: app-permission it('Checking the about us', async () => { await vpn.waitForElement('settingsAboutUs'); await vpn.waitForElementProperty('settingsAboutUs', 'visible', 'true'); await vpn.setElementProperty( 'settingsView', 'contentY', 'i', parseInt(await vpn.getElementProperty('settingsAboutUs', 'y'))); await vpn.wait(); await vpn.clickOnElement('settingsAboutUs'); await vpn.wait(); await vpn.waitForElement('aboutUsBackButton'); await vpn.waitForElementProperty('aboutUsBackButton', 'visible', 'true'); await vpn.waitForElement('aboutUsList'); await vpn.waitForElement('aboutUsList/aboutUsList-tos'); await vpn.waitForElementProperty( 'aboutUsList/aboutUsList-tos', 'visible', 'true'); await vpn.waitForElement('aboutUsList/aboutUsList-privacy'); await vpn.waitForElementProperty( 'aboutUsList/aboutUsList-privacy', 'visible', 'true'); await vpn.waitForElement('aboutUsList/aboutUsList-license'); await vpn.waitForElementProperty( 'aboutUsList/aboutUsList-license', 'visible', 'true'); await vpn.clickOnElement('aboutUsList/aboutUsList-tos'); await vpn.waitForCondition(async () => { const url = await vpn.getLastUrl(); return url.endsWith('/r/vpn/terms'); }); await vpn.clickOnElement('aboutUsList/aboutUsList-privacy'); await vpn.waitForCondition(async () => { const url = await vpn.getLastUrl(); return url.endsWith('/r/vpn/privacy'); }); await vpn.clickOnElement('aboutUsList/aboutUsList-license'); await vpn.waitForCondition(async () => { const url = await vpn.getLastUrl(); return url === 'https://github.com/mozilla-mobile/mozilla-vpn-client/blob/main/LICENSE.md'; }); await vpn.clickOnElement('aboutUsBackButton'); await vpn.wait(); await vpn.waitForElement('manageAccountButton'); await vpn.waitForElementProperty('manageAccountButton', 'visible', 'true'); }); it('Checking the give feedback', async () => { await vpn.waitForElement('settingsGiveFeedback'); await vpn.waitForElementProperty('settingsGiveFeedback', 'visible', 'true'); await vpn.setElementProperty( 'settingsView', 'contentY', 'i', parseInt(await vpn.getElementProperty('settingsGiveFeedback', 'y'))); await vpn.wait(); await vpn.clickOnElement('settingsGiveFeedback'); await vpn.waitForCondition(async () => { const url = await vpn.getLastUrl(); return url.endsWith('/r/vpn/client/feedback'); }); }); it('Checking the get help', async () => { await vpn.waitForElement('settingsGetHelp'); await vpn.waitForElementProperty('settingsGetHelp', 'visible', 'true'); await vpn.setElementProperty( 'settingsView', 'contentY', 'i', parseInt(await vpn.getElementProperty('settingsGetHelp', 'y'))); await vpn.wait(); await vpn.clickOnElement('settingsGetHelp'); await vpn.wait(); await vpn.waitForElement('getHelpBack'); await vpn.waitForElementProperty('getHelpBack', 'visible', 'true'); await vpn.waitForElement('getHelpBackList'); await vpn.waitForElementProperty('getHelpBackList', 'visible', 'true'); await vpn.waitForElement('getHelpBackList/getHelpBackList-0'); await vpn.waitForElementProperty( 'getHelpBackList/getHelpBackList-0', 'visible', 'true'); await vpn.waitForElement('getHelpBackList/getHelpBackList-1'); await vpn.waitForElementProperty( 'getHelpBackList/getHelpBackList-1', 'visible', 'true'); await vpn.waitForElement('getHelpBackList/getHelpBackList-2'); await vpn.waitForElementProperty( 'getHelpBackList/getHelpBackList-2', 'visible', 'true'); await vpn.clickOnElement('getHelpBackList/getHelpBackList-0'); await vpn.waitForCondition(async () => { const url = await vpn.getLastUrl(); return url.startsWith('file://') && url.includes('mozillavpn') && url.endsWith('.txt'); }); await vpn.clickOnElement('getHelpBackList/getHelpBackList-1'); await vpn.waitForCondition(async () => { const url = await vpn.getLastUrl(); return url.endsWith('/r/vpn/support'); }); await vpn.clickOnElement('getHelpBackList/getHelpBackList-2'); await vpn.waitForCondition(async () => { const url = await vpn.getLastUrl(); return url.endsWith('/r/vpn/contact'); }); await vpn.clickOnElement('getHelpBack'); await vpn.waitForElement('settingsGetHelp'); await vpn.waitForElementProperty('settingsGetHelp', 'visible', 'true'); }); it('Checking the logout', async () => { await vpn.waitForElement('settingsLogout'); await vpn.waitForElementProperty('settingsLogout', 'visible', 'true'); }); it('Logout', async () => { await vpn.logout(); await vpn.wait(); }); it('quit the app', async () => await vpn.quit()); }); mozilla-vpn-client-2.2.0/tests/functional/testUnsecuredNetworkAlert.js000066400000000000000000000126371404202232700263050ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const assert = require('assert'); const util = require('util'); const vpn = require('./helper.js'); const FirefoxHelper = require('./firefox.js'); describe('Unsecured network alert', function() { let driver; this.timeout(100000); before(async () => { await vpn.connect(); driver = await FirefoxHelper.createDriver(); }); beforeEach(() => {}); afterEach(() => {}); after(async () => { await driver.quit(); vpn.disconnect(); }); it('reset the app', async () => await vpn.reset()); it('Enable unsecured-network-alert feature', async () => { await vpn.setSetting('unsecured-network-alert', 'false'); assert(await vpn.getSetting('unsecured-network-alert') === 'false'); await vpn.setSetting('unsecured-network-alert', 'true'); assert(await vpn.getSetting('unsecured-network-alert') === 'true'); }); it('Unsecured network alert during the main view', async () => { assert(await vpn.getLastUrl() === ''); await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); await vpn.forceUnsecuredNetworkAlert(); await vpn.wait(); // No notifications during the main view. assert(vpn.lastNotification().title === null); }); it('Unsecured network alert during the authentication', async () => { await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); await vpn.clickOnElement('getStarted'); await vpn.waitForCondition(async () => { const url = await vpn.getLastUrl(); return url.includes('/api/v2/vpn/login'); }); await vpn.wait(); await vpn.waitForElement('authenticatingView'); await vpn.waitForElementProperty('authenticatingView', 'visible', 'true'); await vpn.forceUnsecuredNetworkAlert(); await vpn.wait(); // No notifications during the main view. assert(vpn.lastNotification().title === null); await vpn.waitForElement('cancelFooterLink'); await vpn.waitForElementProperty('cancelFooterLink', 'visible', 'true'); await vpn.clickOnElement('cancelFooterLink'); await vpn.wait(); await vpn.waitForElement('getStarted'); await vpn.waitForElementProperty('getStarted', 'visible', 'true'); }); it('authenticate', async () => await vpn.authenticate(driver, false)); it('Unsecured network alert in the Post authentication view', async () => { await vpn.waitForElement('postAuthenticationButton'); await vpn.forceUnsecuredNetworkAlert(); await vpn.wait(); // Notifications are not OK yet. assert(vpn.lastNotification().title === null); await vpn.clickOnElement('postAuthenticationButton'); await vpn.wait(); }); it('Unsecured network alert in the Controller view', async () => { await vpn.waitForElement('controllerTitle'); await vpn.waitForElementProperty('controllerTitle', 'visible', 'true'); assert( await vpn.getElementProperty('controllerTitle', 'text') === 'VPN is off'); await vpn.forceUnsecuredNetworkAlert(); await vpn.wait(); // Notifications are OK now. assert(vpn.lastNotification().title === 'Unsecured Wi-Fi network detected'); vpn.resetLastNotification(); assert(vpn.lastNotification().title === null); await vpn.waitForElement('controllerTitle'); await vpn.waitForElementProperty('controllerTitle', 'visible', 'true'); assert( await vpn.getElementProperty('controllerTitle', 'text') === 'VPN is off'); }); it('Clicking the notification', async () => { await vpn.clickOnNotification(); await vpn.waitForCondition(async () => { let connectingMsg = await vpn.getElementProperty('controllerTitle', 'text'); return connectingMsg === 'Connecting…'; }); assert( await vpn.getElementProperty('controllerSubTitle', 'text') === 'Masking connection and location'); await vpn.forceUnsecuredNetworkAlert(); await vpn.wait(); // Notifications are not OK when connecting. assert(vpn.lastNotification().title === null); }); it('Unsecured network alert when connected', async () => { await vpn.waitForCondition(async () => { return await vpn.getElementProperty('controllerTitle', 'text') === 'VPN is on'; }); await vpn.waitForCondition(() => { return vpn.lastNotification().title === 'VPN Connected'; }); vpn.resetLastNotification(); await vpn.forceUnsecuredNetworkAlert(); await vpn.wait(); // Notifications are not OK when connected. assert(vpn.lastNotification().title === null); }); it('disconnecting', async () => { await vpn.deactivate(); await vpn.waitForCondition(async () => { return await vpn.getElementProperty('controllerTitle', 'text') === 'Disconnecting…'; }); await vpn.waitForCondition(() => { return vpn.lastNotification().title === 'VPN Disconnected'; }); vpn.resetLastNotification(); await vpn.forceUnsecuredNetworkAlert(); await vpn.wait(); // Notifications are not OK when disconnected. assert(vpn.lastNotification().title === null); }); it('Logout', async () => { await vpn.logout(); await vpn.wait(); }); it('quit the app', async () => await vpn.quit()); }); mozilla-vpn-client-2.2.0/tests/unit/000077500000000000000000000000001404202232700173545ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/tests/unit/helper.h000066400000000000000000000016061404202232700210070ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #ifndef HELPER_H #define HELPER_H #include "../../src/mozillavpn.h" #include "../../src/controller.h" #include #include #include class TestHelper : public QObject { Q_OBJECT public: TestHelper(); public: struct NetworkConfig { enum NetworkStatus { Success, Failure, }; NetworkStatus m_status; QByteArray m_body; NetworkConfig(NetworkStatus status, const QByteArray& body) : m_status(status), m_body(body) {} }; static QVector networkConfig; static MozillaVPN::State vpnState; static Controller::State controllerState; static QVector testList; }; #endif // HELPER_H mozilla-vpn-client-2.2.0/tests/unit/main.cpp000066400000000000000000000015751404202232700210140ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "../../src/leakdetector.h" #include "helper.h" QVector TestHelper::networkConfig; MozillaVPN::State TestHelper::vpnState = MozillaVPN::StateInitialize; Controller::State TestHelper::controllerState = Controller::StateInitializing; QVector TestHelper::testList; TestHelper::TestHelper() { testList.append(this); } int main(int argc, char* argv[]) { #ifdef QT_DEBUG LeakDetector leakDetector; Q_UNUSED(leakDetector); #endif QCoreApplication a(argc, argv); int failures = 0; for (QObject* obj : TestHelper::testList) { int result = QTest::qExec(obj); if (result != 0) { ++failures; } } return failures; } mozilla-vpn-client-2.2.0/tests/unit/moccontroller.cpp000066400000000000000000000036151404202232700227470ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "../../src/controllerimpl.h" #include "../../src/ipaddressrange.h" #include "../../src/mozillavpn.h" #include "helper.h" Controller::Controller() {} Controller::~Controller() = default; void Controller::initialize() {} void Controller::implInitialized(bool, bool, const QDateTime&) {} bool Controller::activate() { return false; } void Controller::activateInternal() {} bool Controller::deactivate() { return false; } void Controller::connected() {} void Controller::disconnected() {} void Controller::timerTimeout() {} void Controller::changeServer(const QString&, const QString&) {} void Controller::logout() {} bool Controller::processNextStep() { return false; } void Controller::setState(State) {} int Controller::time() const { return 42; } void Controller::getBackendLogs(std::function&&) {} void Controller::statusUpdated(const QString&, uint64_t, uint64_t) {} QList Controller::getAllowedIPAddressRanges( const Server& server) { Q_UNUSED(server); return QList(); } Controller::State Controller::state() const { return TestHelper::controllerState; } void Controller::updateRequired() {} void Controller::getStatus( std::function&& a_callback) { std::function callback = std::move(a_callback); callback("127.0.0.1", 0, 0); } void Controller::quit() {} void Controller::connectionConfirmed() {} void Controller::connectionFailed() {} void Controller::heartbeatCompleted() {} void Controller::backendFailure() {} mozilla-vpn-client-2.2.0/tests/unit/mocmozillavpn.cpp000066400000000000000000000061661404202232700227630ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "../../src/mozillavpn.h" #include "../../src/task.h" #include "helper.h" // The singleton. static MozillaVPN* s_instance = nullptr; // static MozillaVPN* MozillaVPN::instance() { if (!s_instance) { s_instance = new MozillaVPN(); } return s_instance; } MozillaVPN::MozillaVPN() {} MozillaVPN::~MozillaVPN() {} MozillaVPN::State MozillaVPN::state() const { return TestHelper::vpnState; } void MozillaVPN::initialize() {} void MozillaVPN::setState(State) {} void MozillaVPN::authenticate() {} void MozillaVPN::openLink(LinkType) {} void MozillaVPN::scheduleTask(Task* task) { connect(task, &Task::completed, task, &Task::deleteLater); task->run(this); } void MozillaVPN::maybeRunTask() {} void MozillaVPN::deleteTasks() {} void MozillaVPN::setToken(const QString&) {} void MozillaVPN::authenticationCompleted(const QByteArray&, const QString&) {} void MozillaVPN::deviceAdded(const QString&, const QString&, const QString&) {} void MozillaVPN::deviceRemoved(const QString&) {} bool MozillaVPN::setServerList(const QByteArray&) { return true; } void MozillaVPN::serversFetched(const QByteArray&) {} void MozillaVPN::removeDevice(const QString&) {} void MozillaVPN::accountChecked(const QByteArray&) {} void MozillaVPN::cancelAuthentication() {} void MozillaVPN::logout() {} void MozillaVPN::setAlert(AlertType) {} void MozillaVPN::errorHandle(ErrorHandler::ErrorType) {} const QList MozillaVPN::servers() const { return QList(); } void MozillaVPN::changeServer(const QString&, const QString&) {} void MozillaVPN::postAuthenticationCompleted() {} void MozillaVPN::setUpdateRecommended(bool) {} void MozillaVPN::setUserAuthenticated(bool) {} void MozillaVPN::startSchedulingPeriodicOperations() {} void MozillaVPN::stopSchedulingPeriodicOperations() {} bool MozillaVPN::writeAndShowLogs(QStandardPaths::StandardLocation) { return true; } bool MozillaVPN::writeLogs(QStandardPaths::StandardLocation, std::function&&) { return true; } void MozillaVPN::viewLogs() {} bool MozillaVPN::modelsInitialized() const { return true; } void MozillaVPN::taskCompleted() {} void MozillaVPN::requestSettings() {} void MozillaVPN::requestAbout() {} void MozillaVPN::requestViewLogs() {} void MozillaVPN::retrieveLogs() {} void MozillaVPN::storeInClipboard(const QString&) {} void MozillaVPN::cleanupLogs() {} void MozillaVPN::serializeLogs(QTextStream*, std::function&&) {} void MozillaVPN::activate() {} void MozillaVPN::deactivate() {} void MozillaVPN::refreshDevices() {} void MozillaVPN::quit() {} void MozillaVPN::update() {} void MozillaVPN::setUpdating(bool) {} MozillaVPN::RemovalDeviceOption MozillaVPN::maybeRemoveCurrentDevice() { return DeviceNotFound; } void MozillaVPN::controllerStateChanged() {} void MozillaVPN::backendServiceRestore() {} void MozillaVPN::heartbeatCompleted(bool) {} void MozillaVPN::triggerHeartbeat() {} mozilla-vpn-client-2.2.0/tests/unit/mocnetworkrequest.cpp000066400000000000000000000057561404202232700236760ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "../../src/leakdetector.h" #include "../../src/timersingleshot.h" #include "helper.h" #include "networkrequest.h" namespace {}; NetworkRequest::NetworkRequest(QObject* parent, int status) : QObject(parent), m_status(status) { MVPN_COUNT_CTOR(NetworkRequest); Q_ASSERT(!TestHelper::networkConfig.isEmpty()); TestHelper::NetworkConfig nc = TestHelper::networkConfig.takeFirst(); TimerSingleShot::create(this, 0, [this, nc]() { deleteLater(); if (nc.m_status == TestHelper::NetworkConfig::Failure) { emit requestFailed(QNetworkReply::NetworkError::HostNotFoundError, ""); } else { Q_ASSERT(nc.m_status == TestHelper::NetworkConfig::Success); emit requestCompleted(nc.m_body); } }); } NetworkRequest::~NetworkRequest() { MVPN_COUNT_DTOR(NetworkRequest); } // static NetworkRequest* NetworkRequest::createForGetUrl(QObject* parent, const QString&, int status) { return new NetworkRequest(parent, status); } // static NetworkRequest* NetworkRequest::createForAuthenticationVerification( QObject* parent, const QString&, const QString&) { return new NetworkRequest(parent, 1234); } // static NetworkRequest* NetworkRequest::createForDeviceCreation(QObject* parent, const QString&, const QString&) { return new NetworkRequest(parent, 1234); } // static NetworkRequest* NetworkRequest::createForDeviceRemoval(QObject* parent, const QString&) { return new NetworkRequest(parent, 1234); } NetworkRequest* NetworkRequest::createForServers(QObject* parent) { return new NetworkRequest(parent, 1234); } NetworkRequest* NetworkRequest::createForVersions(QObject* parent) { return new NetworkRequest(parent, 1234); } NetworkRequest* NetworkRequest::createForAccount(QObject* parent) { return new NetworkRequest(parent, 1234); } NetworkRequest* NetworkRequest::createForIpInfo(QObject* parent) { return new NetworkRequest(parent, 1234); } NetworkRequest* NetworkRequest::createForCaptivePortalDetection( QObject* parent, const QUrl&, const QByteArray&) { return new NetworkRequest(parent, 1234); } NetworkRequest* NetworkRequest::createForCaptivePortalLookup(QObject* parent) { return new NetworkRequest(parent, 1234); } #ifdef MVPN_IOS NetworkRequest* NetworkRequest::createForIOSProducts(QObject* parent) { return new NetworkRequest(parent, 1234); } NetworkRequest* NetworkRequest::createForIOSPurchase(QObject* parent, const QString&) { return new NetworkRequest(parent, 1234); } #endif void NetworkRequest::replyFinished() { QFAIL("Not called!"); } void NetworkRequest::timeout() {} mozilla-vpn-client-2.2.0/tests/unit/testandroidmigration.cpp000066400000000000000000000156421404202232700243220ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "testandroidmigration.h" #include "../../src/platforms/android/androiddatamigration.h" #include "helper.h" void TestAndroidMigration::deviceInfo_data() { QTest::addColumn("json"); QTest::addColumn("privateKey"); QTest::addColumn("publicKey"); QTest::addColumn("name"); QTest::addRow("null") << QByteArray() << "" << "" << ""; QTest::addRow("array") << QByteArray("[]") << "" << "" << ""; QTest::addRow("empty") << QByteArray("{}") << "" << "" << ""; QJsonObject obj; obj.insert("privateKeyBase64", "a"); QJsonObject device; device.insert("pubkey", "b"); device.insert("name", "c"); obj.insert("device", device); QTest::addRow("ok") << QJsonDocument(obj).toJson() << "a" << "b" << "c"; } void TestAndroidMigration::deviceInfo() { QFETCH(QByteArray, json); QFETCH(QString, privateKey); QFETCH(QString, publicKey); QFETCH(QString, name); QString privateKeyValue; QString publicKeyValue; QString nameValue; AndroidDataMigration::importDeviceInfoInternal(json, privateKeyValue, publicKeyValue, nameValue); QCOMPARE(privateKey, privateKeyValue); QCOMPARE(publicKey, publicKeyValue); QCOMPARE(name, nameValue); } void TestAndroidMigration::userInfo_data() { QTest::addColumn("input"); QTest::addColumn("output"); QTest::addRow("null") << QByteArray() << QByteArray(); QTest::addRow("array") << QByteArray("[]") << QByteArray(); QTest::addRow("empty") << QByteArray("{}") << QByteArray(); QJsonObject obj; obj.insert("user", "a"); QTest::addRow("invalid") << QJsonDocument(obj).toJson() << QByteArray(); obj.insert("user", QJsonObject()); QTest::addRow("ok") << QJsonDocument(obj).toJson() << QByteArray("{}"); } void TestAndroidMigration::userInfo() { QFETCH(QByteArray, input); QFETCH(QByteArray, output); QCOMPARE(AndroidDataMigration::importUserInfoInternal(input), output); } void TestAndroidMigration::serverList_data() { QTest::addColumn("input"); QTest::addColumn("output"); QTest::addRow("null") << QByteArray() << QByteArray(); QTest::addRow("array") << QByteArray("[]") << QByteArray(); QTest::addRow("empty") << QByteArray("{}") << QByteArray(); QJsonObject obj; obj.insert("servers", 42); QTest::addRow("invalid servers") << QJsonDocument(obj).toJson() << QByteArray(); obj.insert("servers", QJsonArray()); QTest::addRow("empty servers") << QJsonDocument(obj).toJson() << QByteArray(); QJsonObject item; item["country"] = 42; item["city"] = 42; item["server"] = 42; QJsonArray servers; servers.append(item); obj.insert("servers", servers); QTest::addRow("invalid server") << QJsonDocument(obj).toJson() << QByteArray(); item["country"] = QJsonObject(); item["city"] = QJsonObject(); item["server"] = QJsonObject(); servers.replace(0, item); obj.insert("servers", servers); QTest::addRow("invalid server 2") << QJsonDocument(obj).toJson() << QByteArray(); QJsonObject country; country["code"] = "BD"; // no name here. item["country"] = country; servers.replace(0, item); obj.insert("servers", servers); QTest::addRow("invalid - no named country") << QJsonDocument(obj).toJson() << QByteArray(); country["name"] = "Barad-dûr"; item["country"] = country; servers.replace(0, item); obj.insert("servers", servers); QTest::addRow("invalid - good country - bad city") << QJsonDocument(obj).toJson() << QByteArray(); QJsonObject city; city["code"] = "MD"; // no name here. item["city"] = city; servers.replace(0, item); obj.insert("servers", servers); QTest::addRow("invalid - good country - no named city") << QJsonDocument(obj).toJson() << QByteArray(); city["name"] = "Mount Doom"; item["country"] = country; item["city"] = city; servers.replace(0, item); obj.insert("servers", servers); QJsonArray resultServers; resultServers.append(QJsonObject()); QJsonObject resultCity; resultCity["name"] = city["name"]; resultCity["code"] = city["code"]; resultCity["servers"] = resultServers; QJsonArray resultCities; resultCities.append(resultCity); QJsonObject resultCountry; resultCountry["name"] = country["name"]; resultCountry["code"] = country["code"]; resultCountry["cities"] = resultCities; QJsonArray resultCountries; resultCountries.append(resultCountry); QJsonObject result; result["countries"] = resultCountries; QTest::addRow("invalid - good country - good city") << QJsonDocument(obj).toJson() << QJsonDocument(result).toJson(QJsonDocument::Compact); QJsonObject city2; city2["code"] = "DG"; city2["name"] = "Dol Guldur"; QJsonObject item2; item2["country"] = country; item2["city"] = city2; item2["server"] = QJsonObject(); servers.append(item2); obj.insert("servers", servers); QJsonObject resultCity2; resultCity2["name"] = city2["name"]; resultCity2["code"] = city2["code"]; resultCity2["servers"] = resultServers; resultCities.replace(0, resultCity2); resultCities.append(resultCity); resultCountry["cities"] = resultCities; resultCountries.replace(0, resultCountry); result["countries"] = resultCountries; QTest::addRow("invalid - good country - two good cities") << QJsonDocument(obj).toJson() << QJsonDocument(result).toJson(QJsonDocument::Compact); QJsonObject country2; country2["code"] = "CO"; country2["name"] = "Coruscant"; QJsonObject city3; city3["code"] = "SD"; city3["name"] = "Senate District"; QJsonObject item3; item3["country"] = country2; item3["city"] = city3; item3["server"] = QJsonObject(); servers.append(item3); obj.insert("servers", servers); QJsonObject resultCity3; resultCity3["name"] = city3["name"]; resultCity3["code"] = city3["code"]; resultCity3["servers"] = resultServers; QJsonArray resultCities3; resultCities3.append(resultCity3); QJsonObject resultCountry2; resultCountry2["name"] = country2["name"]; resultCountry2["code"] = country2["code"]; resultCountry2["cities"] = resultCities3; resultCountries.append(resultCountry2); result["countries"] = resultCountries; QTest::addRow("invalid - two good countries - three good cities") << QJsonDocument(obj).toJson() << QJsonDocument(result).toJson(QJsonDocument::Compact); } void TestAndroidMigration::serverList() { QFETCH(QByteArray, input); QFETCH(QByteArray, output); QCOMPARE(AndroidDataMigration::importServerListInternal(input), output); } static TestAndroidMigration s_testAndroidMigration; mozilla-vpn-client-2.2.0/tests/unit/testandroidmigration.h000066400000000000000000000007041404202232700237600ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "helper.h" class TestAndroidMigration final : public TestHelper { Q_OBJECT private slots: void deviceInfo_data(); void deviceInfo(); void userInfo_data(); void userInfo(); void serverList_data(); void serverList(); }; mozilla-vpn-client-2.2.0/tests/unit/testcommandlineparser.cpp000066400000000000000000000045301404202232700244650ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "testcommandlineparser.h" #include "../../src/command.h" #include "../../src/commandlineparser.h" namespace { bool s_executed = false; class CommandDummy final : public Command { public: explicit CommandDummy(QObject* parent) : Command(parent, "ui", "dummy dummy") {} int run(QStringList& tokens) override { s_executed = true; CommandLineParser clp; QList options; return clp.parse(tokens, options, false); } }; Command::RegistrationProxy s_commandDummy; } // namespace void TestCommandLineParser::basic_data() { QTest::addColumn("args"); QTest::addColumn("result"); QTest::addColumn("uiExecuted"); QTest::addRow("-h") << QStringList{"something", "-h"} << 0 << false; QTest::addRow("--help") << QStringList{"something", "--help"} << 0 << false; QTest::addRow("-v") << QStringList{"something", "-v"} << 0 << false; QTest::addRow("--version") << QStringList{"something", "--version"} << 0 << false; QTest::addRow("invalid long option") << QStringList{"something", "--foo"} << 1 << false; QTest::addRow("invalid short option") << QStringList{"something", "-f"} << 1 << false; QTest::addRow("ui implicit") << QStringList{"something"} << 0 << true; QTest::addRow("ui explicit") << QStringList{"something", "ui"} << 0 << true; QTest::addRow("invalid command") << QStringList{"something", "foo"} << 1 << false; QTest::addRow("ui plus -h") << QStringList{"something", "-h", "ui"} << 0 << false; QTest::addRow("ui -h") << QStringList{"something", "ui", "-h"} << 1 << true; } void TestCommandLineParser::basic() { s_executed = false; QFETCH(QStringList, args); int argc = args.length(); char** argv = (char**)malloc(sizeof(char*) * argc); for (int i = 0; i < args.length(); ++i) { argv[i] = strdup(args[i].toLocal8Bit().data()); } QFETCH(int, result); CommandLineParser clp; QCOMPARE(clp.parse(argc, argv), result); QCOMPARE(clp.argc(), argc); QCOMPARE(clp.argv(), argv); QFETCH(bool, uiExecuted); QCOMPARE(s_executed, uiExecuted); } static TestCommandLineParser s_testCommandLineParser; mozilla-vpn-client-2.2.0/tests/unit/testcommandlineparser.h000066400000000000000000000005311404202232700241270ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "helper.h" class TestCommandLineParser : public TestHelper { Q_OBJECT private slots: void basic_data(); void basic(); }; mozilla-vpn-client-2.2.0/tests/unit/testconnectiondataholder.cpp000066400000000000000000000062771404202232700251630ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "testconnectiondataholder.h" #include "../../src/connectiondataholder.h" #include "../../src/constants.h" #include "helper.h" #include #include void TestConnectionDataHolder::checkIpAddressFailure() { ConnectionDataHolder cdh; TestHelper::networkConfig.append(TestHelper::NetworkConfig( TestHelper::NetworkConfig::Failure, QByteArray())); QEventLoop loop; connect(&cdh, &ConnectionDataHolder::ipAddressChecked, [&] { cdh.disable(); loop.exit(); }); cdh.enable(); loop.exec(); } void TestConnectionDataHolder::checkIpAddressSucceess_data() { QTest::addColumn("json"); QTest::addColumn("ipAddress"); QTest::addColumn("signal"); QTest::addRow("invalid") << QByteArray("") << "vpn.connectionInfo.loading" << false; QJsonObject json; QTest::addRow("empty") << QJsonDocument(json).toJson() << "vpn.connectionInfo.loading" << false; json.insert("ip", 42); QTest::addRow("invalid ip") << QJsonDocument(json).toJson() << "vpn.connectionInfo.loading" << false; json.insert("ip", "42"); QTest::addRow("valid ip") << QJsonDocument(json).toJson() << "42" << true; } void TestConnectionDataHolder::checkIpAddressSucceess() { ConnectionDataHolder cdh; QSignalSpy spy(&cdh, &ConnectionDataHolder::ipAddressChanged); QFETCH(QByteArray, json); TestHelper::networkConfig.append( TestHelper::NetworkConfig(TestHelper::NetworkConfig::Success, json)); QEventLoop loop; connect(&cdh, &ConnectionDataHolder::ipAddressChecked, [&] { cdh.disable(); QFETCH(QString, ipAddress); QCOMPARE(cdh.ipAddress(), ipAddress); QFETCH(bool, signal); QCOMPARE(spy.count(), signal ? 1 : 0); loop.exit(); }); cdh.enable(); loop.exec(); } void TestConnectionDataHolder::chart() { ConnectionDataHolder cdh; QSignalSpy spy(&cdh, &ConnectionDataHolder::bytesChanged); TestHelper::networkConfig.append(TestHelper::NetworkConfig( TestHelper::NetworkConfig::Success, QString("{'ip':'42'}").toUtf8())); cdh.add(123, 123); QCOMPARE(spy.count(), 0); QtCharts::QSplineSeries* txSeries = new QtCharts::QSplineSeries(this); QtCharts::QSplineSeries* rxSeries = new QtCharts::QSplineSeries(this); QtCharts::QValueAxis* axisX = new QtCharts::QValueAxis(this); QtCharts::QValueAxis* axisY = new QtCharts::QValueAxis(this); cdh.activate(QVariant::fromValue(txSeries), QVariant::fromValue(rxSeries), QVariant::fromValue(axisX), QVariant::fromValue(axisY)); QCOMPARE(spy.count(), 0); QCOMPARE(txSeries->count(), Constants::CHARTS_MAX_POINTS); QCOMPARE(rxSeries->count(), Constants::CHARTS_MAX_POINTS); QEventLoop loop; connect(&cdh, &ConnectionDataHolder::bytesChanged, [&] { if (spy.count() >= Constants::CHARTS_MAX_POINTS * 2) { loop.exit(); } }); loop.exec(); QCOMPARE(cdh.txBytes(), (uint32_t)0); QCOMPARE(cdh.rxBytes(), (uint32_t)0); } static TestConnectionDataHolder s_testConnectionDataHolder; mozilla-vpn-client-2.2.0/tests/unit/testconnectiondataholder.h000066400000000000000000000011431404202232700246130ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "helper.h" class TestConnectionDataHolder final : public TestHelper { Q_OBJECT private slots: void initTestCase() { TestHelper::controllerState = Controller::StateOn; } void checkIpAddressFailure(); void checkIpAddressSucceess_data(); void checkIpAddressSucceess(); void chart(); void cleanupTestCase() { TestHelper::controllerState = Controller::StateInitializing; } }; mozilla-vpn-client-2.2.0/tests/unit/testipaddress.cpp000066400000000000000000000143101404202232700227350ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "testipaddress.h" #include "../../src/ipaddress.h" #include "helper.h" void TestIpAddress::basic_data() { QTest::addColumn("input"); QTest::addColumn("address"); QTest::addColumn("prefixLength"); QTest::addColumn("netmask"); QTest::addColumn("hostmask"); QTest::addColumn("broadcastAddress"); QTest::addRow("localhost") << "127.0.0.1" << "127.0.0.1" << 32 << "255.255.255.255" << "0.0.0.0" << "127.0.0.1"; QTest::addRow("localhost/32") << "127.0.0.1/32" << "127.0.0.1" << 32 << "255.255.255.255" << "0.0.0.0" << "127.0.0.1"; QTest::addRow("random") << "1.1.1.1" << "1.1.1.1" << 32 << "255.255.255.255" << "0.0.0.0" << "1.1.1.1"; QTest::addRow("world") << "0.0.0.0/0" << "0.0.0.0" << 0 << "0.0.0.0" << "255.255.255.255" << "255.255.255.255"; QTest::addRow("netmask C") << "192.168.1.0/24" << "192.168.1.0" << 24 << "255.255.255.0" << "0.0.0.255" << "192.168.1.255"; QTest::addRow("netmask/8") << "192.0.0.0/8" << "192.0.0.0" << 8 << "255.0.0.0" << "0.255.255.255" << "192.255.255.255"; QTest::addRow("netmask/30") << "192.0.0.0/30" << "192.0.0.0" << 30 << "255.255.255.252" << "0.0.0.3" << "192.0.0.3"; } void TestIpAddress::basic() { QFETCH(QString, input); IPAddress ipAddress = IPAddress::create(input); QFETCH(QString, address); QCOMPARE(ipAddress.address().toString(), address); QFETCH(int, prefixLength); QCOMPARE(ipAddress.prefixLength(), prefixLength); QFETCH(QString, netmask); QCOMPARE(ipAddress.netmask().toString(), netmask); QFETCH(QString, hostmask); QCOMPARE(ipAddress.hostmask().toString(), hostmask); QFETCH(QString, broadcastAddress); QCOMPARE(ipAddress.broadcastAddress().toString(), broadcastAddress); } void TestIpAddress::overlaps_data() { QTest::addColumn("a"); QTest::addColumn("b"); QTest::addColumn("result"); QTest::addRow("self localhost") << "127.0.0.1" << "127.0.0.1" << true; QTest::addRow("world") << "0.0.0.0/0" << "127.0.0.1" << true; QTest::addRow("A") << "1.2.3.0/24" << "127.0.0.1" << false; QTest::addRow("B") << "1.2.3.0/24" << "1.2.2.0/24" << false; QTest::addRow("C") << "1.2.3.0/24" << "1.2.4.3" << false; } void TestIpAddress::overlaps() { QFETCH(QString, a); IPAddress ipAddressA = IPAddress::create(a); QFETCH(QString, b); IPAddress ipAddressB = IPAddress::create(b); QFETCH(bool, result); QCOMPARE(ipAddressA.overlaps(ipAddressB), result); } void TestIpAddress::contains_data() { QTest::addColumn("a"); QTest::addColumn("b"); QTest::addColumn("result"); QTest::addRow("self localhost") << "127.0.0.1" << "127.0.0.1" << true; QTest::addRow("world") << "0.0.0.0/0" << "127.0.0.1" << true; QTest::addRow("A") << "1.2.3.0/24" << "127.0.0.1" << false; QTest::addRow("B") << "1.2.3.0/24" << "1.2.2.0/24" << false; QTest::addRow("C") << "1.2.3.0/24" << "1.2.4.3" << false; } void TestIpAddress::contains() { QFETCH(QString, a); IPAddress ipAddressA = IPAddress::create(a); QFETCH(QString, b); QHostAddress ipAddressB(b); QFETCH(bool, result); QCOMPARE(ipAddressA.contains(ipAddressB), result); } void TestIpAddress::equal_data() { QTest::addColumn("a"); QTest::addColumn("b"); QTest::addColumn("result"); QTest::addRow("self localhost") << "127.0.0.1" << "127.0.0.1" << true; QTest::addRow("world vs localhost") << "0.0.0.0/0" << "127.0.0.1" << false; QTest::addRow("world vs world") << "0.0.0.0/0" << "0.0.0.0/0" << true; } void TestIpAddress::equal() { QFETCH(QString, a); IPAddress ipAddressA = IPAddress::create(a); QFETCH(QString, b); IPAddress ipAddressB = IPAddress::create(b); QFETCH(bool, result); QCOMPARE(ipAddressA == ipAddressB, result); } void TestIpAddress::excludeAddresses_data() { QTest::addColumn("input"); QTest::addColumn("excludeAddresses"); QTest::addColumn("result"); QTest::addRow("world vs localhost") << "0.0.0.0/0" << "127.0.0.1" << "0.0.0.0/2,112.0.0.0/5,120.0.0.0/6,124.0.0.0/7,126.0.0.0/8,127.0.0.0/" "32,127.0.0.128/25,127.0.0.16/28,127.0.0.2/31,127.0.0.32/27,127.0.0.4/" "30,127.0.0.64/26,127.0.0.8/29,127.0.1.0/24,127.0.128.0/17,127.0.16.0/" "20,127.0.2.0/23,127.0.32.0/19,127.0.4.0/22,127.0.64.0/18,127.0.8.0/" "21,127.1.0.0/16,127.128.0.0/9,127.16.0.0/12,127.2.0.0/15,127.32.0.0/" "11,127.4.0.0/14,127.64.0.0/10,127.8.0.0/13,128.0.0.0/1,64.0.0.0/" "3,96.0.0.0/4"; QTest::addRow("world vs rfc1918 (part)") << "0.0.0.0/0" << "10.0.0.0/8" << "0.0.0.0/5,11.0.0.0/8,12.0.0.0/6,128.0.0.0/1,16.0.0.0/4,32.0.0.0/" "3,64.0.0.0/2,8.0.0.0/7"; } void TestIpAddress::excludeAddresses() { QFETCH(QString, input); IPAddress a = IPAddress::create(input); QFETCH(QString, excludeAddresses); IPAddress b = IPAddress::create(excludeAddresses); QStringList list; for (const IPAddress& r : a.excludeAddresses(b)) { list.append(r.toString()); } std::sort(list.begin(), list.end()); QFETCH(QString, result); QVERIFY(list.join(",") == result); } static TestIpAddress s_testIpAddress; mozilla-vpn-client-2.2.0/tests/unit/testipaddress.h000066400000000000000000000010211404202232700223750ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "helper.h" class TestIpAddress final : public TestHelper { Q_OBJECT private slots: void basic_data(); void basic(); void overlaps_data(); void overlaps(); void contains_data(); void contains(); void equal_data(); void equal(); void excludeAddresses_data(); void excludeAddresses(); }; mozilla-vpn-client-2.2.0/tests/unit/testlocalizer.cpp000066400000000000000000000022451404202232700227470ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "testlocalizer.h" #include "../../src/localizer.h" #include "helper.h" #include "settingsholder.h" void TestLocalizer::basic() { SettingsHolder settings; Localizer l; QCOMPARE(Localizer::instance(), &l); l.initialize(); QHash rn = l.roleNames(); QCOMPARE(rn.count(), 3); QCOMPARE(rn[Localizer::LanguageRole], "language"); QCOMPARE(rn[Localizer::LocalizedLanguageRole], "localizedLanguage"); QCOMPARE(rn[Localizer::CodeRole], "code"); QCOMPARE(l.rowCount(QModelIndex()), 0); QCOMPARE(l.data(QModelIndex(), Localizer::LanguageRole), QVariant()); } void TestLocalizer::systemLanguage() { SettingsHolder settings; Localizer l; l.initialize(); l.setCode(""); QCOMPARE(l.code(), ""); QCOMPARE(l.previousCode(), "en"); l.setCode("en"); QCOMPARE(l.code(), "en"); QCOMPARE(l.previousCode(), "en"); l.setCode(""); QCOMPARE(l.code(), ""); QCOMPARE(l.previousCode(), "en"); } static TestLocalizer s_testLocalizer; mozilla-vpn-client-2.2.0/tests/unit/testlocalizer.h000066400000000000000000000005341404202232700224130ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "helper.h" class TestLocalizer final : public TestHelper { Q_OBJECT private slots: void basic(); void systemLanguage(); }; mozilla-vpn-client-2.2.0/tests/unit/testlogger.cpp000066400000000000000000000022021404202232700222330ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "testlogger.h" #include "../../src/logger.h" #include "../../src/loghandler.h" #include "helper.h" void TestLogger::logger() { Logger l("test", "class"); l.log() << "Hello world" << 42 << 'a' << QString("OK") << QByteArray("Array") << QStringList{"A", "B"} << Qt::endl; Logger l2(QStringList{"a", "b"}, "class"); l2.log() << "Hello world" << 42 << 'a' << QString("OK") << QByteArray("Array") << QStringList{"A", "B"} << Qt::endl; } void TestLogger::logHandler() { LogHandler* lh = LogHandler::instance(); qInstallMessageHandler(LogHandler::messageQTHandler); qDebug() << "WOW debug!"; qInfo() << "WOW info!"; qWarning() << "WOW warning!"; qCritical() << "WOW critical!"; { QString buffer; QTextStream out(&buffer); lh->writeLogs(out); } lh->cleanupLogs(); { QString buffer; QTextStream out(&buffer); lh->writeLogs(out); } } static TestLogger s_testLogger; mozilla-vpn-client-2.2.0/tests/unit/testlogger.h000066400000000000000000000005261404202232700217070ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "helper.h" class TestLogger final : public TestHelper { Q_OBJECT private slots: void logger(); void logHandler(); }; mozilla-vpn-client-2.2.0/tests/unit/testmodels.cpp000066400000000000000000001126021404202232700222450ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "testmodels.h" #include "../../src/ipaddressrange.h" #include "../../src/models/device.h" #include "../../src/models/devicemodel.h" #include "../../src/models/keys.h" #include "../../src/models/servercity.h" #include "../../src/models/servercountry.h" #include "../../src/models/servercountrymodel.h" #include "../../src/models/serverdata.h" #include "../../src/models/user.h" #include "../../src/settingsholder.h" #include "helper.h" #include #include #include // Device // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void TestModels::deviceBasic() { Device device; QCOMPARE(device.name(), ""); QCOMPARE(device.createdAt(), QDateTime()); QCOMPARE(device.publicKey(), ""); QCOMPARE(device.ipv4Address(), ""); QCOMPARE(device.ipv6Address(), ""); QVERIFY(!device.isDevice("name")); } void TestModels::deviceCurrentDeviceName() { QVERIFY(!Device::currentDeviceName().isEmpty()); } void TestModels::deviceFromJson_data() { QTest::addColumn("json"); QTest::addColumn("result"); QTest::addColumn("name"); QTest::addColumn("publicKey"); QTest::addColumn("createdAt"); QTest::addColumn("ipv4Address"); QTest::addColumn("ipv6Address"); QJsonObject obj; obj.insert("test", ""); QTest::addRow("null") << QJsonDocument(obj).toJson() << false << "" << "" << QDateTime() << "" << ""; QJsonObject d; obj.insert("test", d); QTest::addRow("empty") << QJsonDocument(obj).toJson() << false << "" << "" << QDateTime() << "" << ""; d.insert("name", "deviceName"); obj.insert("test", d); QTest::addRow("name") << QJsonDocument(obj).toJson() << false << "" << "" << QDateTime() << "" << ""; d.insert("pubkey", "devicePubkey"); obj.insert("test", d); QTest::addRow("pubKey") << QJsonDocument(obj).toJson() << false << "" << "" << QDateTime() << "" << ""; d.insert("created_at", 42); obj.insert("test", d); QTest::addRow("createdAt (invalid)") << QJsonDocument(obj).toJson() << false << "" << "" << QDateTime() << "" << ""; d.insert("created_at", "42"); obj.insert("test", d); QTest::addRow("createdAt (invalid string)") << QJsonDocument(obj).toJson() << false << "" << "" << QDateTime() << "" << ""; d.insert("created_at", "2017-07-24T15:46:29"); obj.insert("test", d); QTest::addRow("createdAt") << QJsonDocument(obj).toJson() << false << "" << "" << QDateTime() << "" << ""; d.insert("ipv4_address", "deviceIpv4"); obj.insert("test", d); QTest::addRow("ipv4Address") << QJsonDocument(obj).toJson() << false << "" << "" << QDateTime() << "" << ""; d.insert("ipv6_address", "deviceIpv6"); obj.insert("test", d); QTest::addRow("ipv6Address") << QJsonDocument(obj).toJson() << true << "deviceName" << "devicePubkey" << QDateTime::fromString("2017-07-24T15:46:29", Qt::ISODate) << "deviceIpv4" << "deviceIpv6"; } void TestModels::deviceFromJson() { QFETCH(QByteArray, json); QJsonDocument doc = QJsonDocument::fromJson(json); Q_ASSERT(doc.isObject()); QJsonObject obj = doc.object(); Q_ASSERT(obj.contains("test")); Device device; QFETCH(bool, result); QCOMPARE(device.fromJson(obj.take("test")), result); QFETCH(QString, name); QCOMPARE(device.name(), name); QFETCH(QString, publicKey); QCOMPARE(device.publicKey(), publicKey); QFETCH(QDateTime, createdAt); QCOMPARE(device.createdAt(), createdAt); QFETCH(QString, ipv4Address); QCOMPARE(device.ipv4Address(), ipv4Address); QFETCH(QString, ipv6Address); QCOMPARE(device.ipv6Address(), ipv6Address); Device deviceB(device); QCOMPARE(deviceB.name(), device.name()); QCOMPARE(deviceB.createdAt(), device.createdAt()); QCOMPARE(deviceB.publicKey(), device.publicKey()); QCOMPARE(deviceB.ipv4Address(), device.ipv4Address()); QCOMPARE(deviceB.ipv6Address(), device.ipv6Address()); Device deviceC; deviceC = device; QCOMPARE(deviceC.name(), device.name()); QCOMPARE(deviceC.createdAt(), device.createdAt()); QCOMPARE(deviceC.publicKey(), device.publicKey()); QCOMPARE(deviceC.ipv4Address(), device.ipv4Address()); QCOMPARE(deviceC.ipv6Address(), device.ipv6Address()); device = device; } // DeviceModel // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void TestModels::deviceModelBasic() { DeviceModel dm; QVERIFY(!dm.initialized()); QVERIFY(!dm.hasDevice("foo")); dm.removeDevice("foo"); QCOMPARE(dm.device("foo"), nullptr); QCOMPARE(dm.activeDevices(), 0); Keys keys; QCOMPARE(dm.currentDevice(&keys), nullptr); QHash rn = dm.roleNames(); QCOMPARE(rn.count(), 3); QCOMPARE(rn[DeviceModel::NameRole], "name"); QCOMPARE(rn[DeviceModel::CurrentOneRole], "currentOne"); QCOMPARE(rn[DeviceModel::CreatedAtRole], "createdAt"); QCOMPARE(dm.rowCount(QModelIndex()), 0); QCOMPARE(dm.data(QModelIndex(), DeviceModel::NameRole), QVariant()); SettingsHolder settingsHolder; QVERIFY(!dm.fromSettings(&keys)); dm.writeSettings(); QVERIFY(!dm.fromSettings(&keys)); } void TestModels::deviceModelFromJson_data() { QTest::addColumn("json"); QTest::addColumn("result"); QTest::addColumn("devices"); QTest::addColumn("deviceName"); QTest::addColumn("currentOne"); QTest::addColumn("createdAt"); QTest::addRow("invalid") << QByteArray("") << false; QTest::addRow("array") << QByteArray("[]") << false; QJsonObject obj; QTest::addRow("empty") << QJsonDocument(obj).toJson() << false; obj.insert("devices", 42); QTest::addRow("invalid devices") << QJsonDocument(obj).toJson() << false; QJsonArray devices; obj.insert("devices", devices); QTest::addRow("good but empty") << QJsonDocument(obj).toJson() << true << 0 << QVariant() << QVariant() << QVariant(); devices.append(42); obj.insert("devices", devices); QTest::addRow("invalid devices") << QJsonDocument(obj).toJson() << false; QJsonObject d; d.insert("name", "deviceName"); d.insert("pubkey", "devicePubkey"); d.insert("created_at", "2017-07-24T15:46:29"); d.insert("ipv4_address", "deviceIpv4"); d.insert("ipv6_address", "deviceIpv6"); devices.replace(0, d); obj.insert("devices", devices); QTest::addRow("good") << QJsonDocument(obj).toJson() << true << 1 << QVariant("deviceName") << QVariant(false) << QVariant(QDateTime::fromString("2017-07-24T15:46:29", Qt::ISODate)); d.insert("name", Device::currentDeviceName()); d.insert("pubkey", "currentDevicePubkey"); d.insert("created_at", "2017-07-24T15:46:29"); d.insert("ipv4_address", "deviceIpv4"); d.insert("ipv6_address", "deviceIpv6"); devices.append(d); obj.insert("devices", devices); QTest::addRow("good - 2 devices") << QJsonDocument(obj).toJson() << true << 2 << QVariant(Device::currentDeviceName()) << QVariant(true) << QVariant(QDateTime::fromString("2017-07-24T15:46:29", Qt::ISODate)); } void TestModels::deviceModelFromJson() { QFETCH(QByteArray, json); QFETCH(bool, result); // fromJson { Keys keys; keys.storeKeys("private", "currentDevicePubkey"); DeviceModel dm; QSignalSpy signalSpy(&dm, &DeviceModel::changed); QCOMPARE(dm.fromJson(&keys, json), result); if (!result) { QVERIFY(!dm.initialized()); QCOMPARE(signalSpy.count(), 0); QCOMPARE(dm.rowCount(QModelIndex()), 0); } else { QVERIFY(dm.initialized()); QCOMPARE(signalSpy.count(), 1); QFETCH(int, devices); QCOMPARE(dm.rowCount(QModelIndex()), devices); QCOMPARE(dm.data(QModelIndex(), DeviceModel::NameRole), QVariant()); QCOMPARE(dm.data(QModelIndex(), DeviceModel::CurrentOneRole), QVariant()); QCOMPARE(dm.data(QModelIndex(), DeviceModel::CreatedAtRole), QVariant()); QModelIndex index = dm.index(0, 0); QFETCH(QVariant, deviceName); QCOMPARE(dm.data(index, DeviceModel::NameRole), deviceName); // We cannot compare the currentOne with the DM because the Keys object // doesn't exist in the MozillaVPN mock object // QFETCH(QVariant, currentOne); // QCOMPARE(dm.data(index, DeviceModel::CurrentOneRole), currentOne); // QCOMPARE(!!dm.currentDevice(&keys), currentOne.toBool()); QFETCH(QVariant, createdAt); QCOMPARE(dm.data(index, DeviceModel::CreatedAtRole), createdAt); QCOMPARE(dm.data(index, DeviceModel::CreatedAtRole + 1), QVariant()); QCOMPARE(dm.activeDevices(), devices); if (devices > 0) { QVERIFY(dm.hasDevice(deviceName.toString())); QVERIFY(dm.device(deviceName.toString()) != nullptr); dm.removeDevice("FOO"); QCOMPARE(dm.activeDevices(), devices); dm.removeDevice(deviceName.toString()); QCOMPARE(dm.activeDevices(), devices - 1); } QVERIFY(dm.fromJson(&keys, json)); } } // fromSettings { SettingsHolder settingsHolder; SettingsHolder::instance()->setDevices(json); Keys keys; keys.storeKeys("private", "currentDevicePubkey"); DeviceModel dm; QSignalSpy signalSpy(&dm, &DeviceModel::changed); QCOMPARE(dm.fromSettings(&keys), result); if (!result) { QVERIFY(!dm.initialized()); QCOMPARE(signalSpy.count(), 0); QCOMPARE(dm.rowCount(QModelIndex()), 0); } else { QVERIFY(dm.initialized()); QCOMPARE(signalSpy.count(), 1); QFETCH(int, devices); QCOMPARE(dm.rowCount(QModelIndex()), devices); QCOMPARE(dm.data(QModelIndex(), DeviceModel::NameRole), QVariant()); QCOMPARE(dm.data(QModelIndex(), DeviceModel::CurrentOneRole), QVariant()); QCOMPARE(dm.data(QModelIndex(), DeviceModel::CreatedAtRole), QVariant()); QModelIndex index = dm.index(0, 0); QFETCH(QVariant, deviceName); QCOMPARE(dm.data(index, DeviceModel::NameRole), deviceName); // We cannot compare the currentOne with the DM because the Keys object // doesn't exist in the MozillaVPN mock object // QFETCH(QVariant, currentOne); // QCOMPARE(dm.data(index, DeviceModel::CurrentOneRole), currentOne); // QCOMPARE(!!dm.currentDevice(&keys), currentOne.toBool()); QFETCH(QVariant, createdAt); QCOMPARE(dm.data(index, DeviceModel::CreatedAtRole), createdAt); QCOMPARE(dm.activeDevices(), devices); Keys keys; keys.storeKeys("private", "currentDevicePubkey"); if (devices > 0) { QVERIFY(dm.hasDevice(deviceName.toString())); QVERIFY(dm.device(deviceName.toString()) != nullptr); dm.removeDevice("FOO"); QCOMPARE(dm.activeDevices(), devices); dm.removeDevice(deviceName.toString()); QCOMPARE(dm.activeDevices(), devices - 1); } } } } // Keys // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void TestModels::keysBasic() { Keys k; QVERIFY(!k.initialized()); QCOMPARE(k.privateKey(), ""); k.storeKeys("private", "public"); QVERIFY(k.initialized()); QCOMPARE(k.privateKey(), "private"); QCOMPARE(k.publicKey(), "public"); k.forgetKeys(); QVERIFY(!k.initialized()); QCOMPARE(k.privateKey(), ""); QCOMPARE(k.publicKey(), ""); // Private and public keys in the settings. { SettingsHolder settingsHolder; QCOMPARE(k.fromSettings(), false); SettingsHolder::instance()->setPrivateKey("WOW"); QCOMPARE(k.fromSettings(), false); SettingsHolder::instance()->setPublicKey("WOW2"); QCOMPARE(k.fromSettings(), true); } // No public keys, but we can retrieve it from the devices. { SettingsHolder settingsHolder; QCOMPARE(k.fromSettings(), false); QJsonObject d; d.insert("name", Device::currentDeviceName()); d.insert("pubkey", "devicePubkey"); d.insert("created_at", "2017-07-24T15:46:29"); d.insert("ipv4_address", "deviceIpv4"); d.insert("ipv6_address", "deviceIpv6"); QJsonArray devices; devices.append(d); QJsonObject obj; obj.insert("devices", devices); SettingsHolder::instance()->setDevices(QJsonDocument(obj).toJson()); SettingsHolder::instance()->setPrivateKey("WOW"); QCOMPARE(k.fromSettings(), true); } } // Server // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void TestModels::serverBasic() { Server s; QVERIFY(!s.initialized()); QCOMPARE(s.hostname(), ""); QCOMPARE(s.ipv4AddrIn(), ""); QCOMPARE(s.ipv4Gateway(), ""); QCOMPARE(s.ipv6AddrIn(), ""); QCOMPARE(s.ipv6Gateway(), ""); QCOMPARE(s.publicKey(), ""); QCOMPARE(s.weight(), (uint32_t)0); QCOMPARE(s.choosePort(), (uint32_t)0); } void TestModels::serverFromJson_data() { QTest::addColumn("json"); QTest::addColumn("result"); QTest::addColumn("hostname"); QTest::addColumn("ipv4AddrIn"); QTest::addColumn("ipv4Gateway"); QTest::addColumn("ipv6AddrIn"); QTest::addColumn("ipv6Gateway"); QTest::addColumn("publicKey"); QTest::addColumn("weight"); QTest::addColumn>("ports"); QJsonObject obj; QTest::addRow("empty") << obj << false; obj.insert("hostname", "hostname"); QTest::addRow("hostname") << obj << false; obj.insert("ipv4_addr_in", "ipv4AddrIn"); QTest::addRow("ipv4AddrIn") << obj << false; obj.insert("ipv4_gateway", "ipv4Gateway"); QTest::addRow("ipv4Gateway") << obj << false; obj.insert("ipv6_addr_in", "ipv6AddrIn"); QTest::addRow("ipv6AddrIn") << obj << false; obj.insert("ipv6_gateway", "ipv6Gateway"); QTest::addRow("ipv6Gateway") << obj << false; obj.insert("public_key", "publicKey"); QTest::addRow("publicKey") << obj << false; obj.insert("weight", 1234); QTest::addRow("weight") << obj << false; QJsonArray portRanges; obj.insert("port_ranges", portRanges); QTest::addRow("portRanges") << obj << true << "hostname" << "ipv4AddrIn" << "ipv4Gateway" << "ipv6AddrIn" << "ipv6Gateway" << "publicKey" << 1234 << QList{0}; portRanges.append(42); obj.insert("port_ranges", portRanges); QTest::addRow("portRanges wrong type") << obj << false; QJsonArray portRange; portRanges.replace(0, portRange); obj.insert("port_ranges", portRanges); QTest::addRow("portRanges wrong number") << obj << false; portRange.append("A"); portRange.append("B"); portRanges.replace(0, portRange); obj.insert("port_ranges", portRanges); QTest::addRow("portRanges wrong type") << obj << false; portRange.replace(0, 42); portRange.replace(1, "B"); portRanges.replace(0, portRange); obj.insert("port_ranges", portRanges); QTest::addRow("all good") << obj << false; portRange.replace(0, 42); portRange.replace(1, 42); portRanges.replace(0, portRange); obj.insert("port_ranges", portRanges); QTest::addRow("all good") << obj << true << "hostname" << "ipv4AddrIn" << "ipv4Gateway" << "ipv6AddrIn" << "ipv6Gateway" << "publicKey" << 1234 << QList{42}; portRange.replace(0, 42); portRange.replace(1, 43); portRanges.replace(0, portRange); obj.insert("port_ranges", portRanges); QTest::addRow("all good") << obj << true << "hostname" << "ipv4AddrIn" << "ipv4Gateway" << "ipv6AddrIn" << "ipv6Gateway" << "publicKey" << 1234 << QList{42, 43}; } void TestModels::serverFromJson() { QFETCH(QJsonObject, json); QFETCH(bool, result); Server s; QCOMPARE(s.fromJson(json), result); if (!result) { QVERIFY(!s.initialized()); return; } QVERIFY(s.initialized()); QFETCH(QString, hostname); QCOMPARE(s.hostname(), hostname); QFETCH(QString, ipv4AddrIn); QCOMPARE(s.ipv4AddrIn(), ipv4AddrIn); QFETCH(QString, ipv4Gateway); QCOMPARE(s.ipv4Gateway(), ipv4Gateway); QFETCH(QString, ipv6AddrIn); QCOMPARE(s.ipv6AddrIn(), ipv6AddrIn); QFETCH(QString, ipv6Gateway); QCOMPARE(s.ipv6Gateway(), ipv6Gateway); QFETCH(QString, publicKey); QCOMPARE(s.publicKey(), publicKey); QFETCH(int, weight); QCOMPARE(s.weight(), (uint32_t)weight); QFETCH(QList, ports); Q_ASSERT(ports.length() >= 1); if (ports.length() == 1) { QCOMPARE(s.choosePort(), (uint32_t)ports[0]); } else { QVERIFY(ports.contains(s.choosePort())); } Server sB(s); QCOMPARE(sB.initialized(), s.initialized()); QCOMPARE(sB.hostname(), s.hostname()); QCOMPARE(sB.ipv4AddrIn(), s.ipv4AddrIn()); QCOMPARE(sB.ipv4Gateway(), s.ipv4Gateway()); QCOMPARE(sB.ipv6AddrIn(), s.ipv6AddrIn()); QCOMPARE(sB.ipv6Gateway(), s.ipv6Gateway()); QCOMPARE(sB.publicKey(), s.publicKey()); QCOMPARE(sB.weight(), s.weight()); Server sC; sC = s; QCOMPARE(sC.initialized(), s.initialized()); QCOMPARE(sC.hostname(), s.hostname()); QCOMPARE(sC.ipv4AddrIn(), s.ipv4AddrIn()); QCOMPARE(sC.ipv4Gateway(), s.ipv4Gateway()); QCOMPARE(sC.ipv6AddrIn(), s.ipv6AddrIn()); QCOMPARE(sC.ipv6Gateway(), s.ipv6Gateway()); QCOMPARE(sC.publicKey(), s.publicKey()); QCOMPARE(sC.weight(), s.weight()); s = s; } void TestModels::serverWeightChooser() { QList list; list.append(Server()); const Server& s = Server::weightChooser(list); QCOMPARE(&s, &list[0]); } // ServerCity // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void TestModels::serverCityBasic() { ServerCity sc; QCOMPARE(sc.name(), ""); QCOMPARE(sc.code(), ""); QVERIFY(sc.servers().isEmpty()); } void TestModels::serverCityFromJson_data() { QTest::addColumn("json"); QTest::addColumn("result"); QTest::addColumn("name"); QTest::addColumn("code"); QTest::addColumn("servers"); QJsonObject obj; QTest::addRow("empty") << obj << false; obj.insert("name", "name"); QTest::addRow("name") << obj << false; obj.insert("code", "code"); QTest::addRow("code") << obj << false; obj.insert("servers", "servers"); QTest::addRow("servers invalid 1") << obj << false; QJsonArray servers; obj.insert("servers", servers); QTest::addRow("servers empty") << obj << true << "name" << "code" << 0; servers.append(42); obj.insert("servers", servers); QTest::addRow("servers invalid 2") << obj << false; QJsonObject server; servers.replace(0, server); obj.insert("servers", servers); QTest::addRow("servers invalid 3") << obj << false; server.insert("hostname", "hostname"); server.insert("ipv4_addr_in", "ipv4AddrIn"); server.insert("ipv4_gateway", "ipv4Gateway"); server.insert("ipv6_addr_in", "ipv6AddrIn"); server.insert("ipv6_gateway", "ipv6Gateway"); server.insert("public_key", "publicKey"); server.insert("weight", 1234); QJsonArray portRanges; server.insert("port_ranges", portRanges); servers.replace(0, server); obj.insert("servers", servers); QTest::addRow("servers ok") << obj << true << "name" << "code" << 1; } void TestModels::serverCityFromJson() { QFETCH(QJsonObject, json); QFETCH(bool, result); ServerCity sc; QCOMPARE(sc.fromJson(json), result); if (!result) { QCOMPARE(sc.name(), ""); QCOMPARE(sc.code(), ""); QVERIFY(sc.servers().isEmpty()); return; } QFETCH(QString, name); QCOMPARE(sc.name(), name); QFETCH(QString, code); QCOMPARE(sc.code(), code); QFETCH(int, servers); QCOMPARE(sc.servers().length(), servers); ServerCity scB(sc); QCOMPARE(scB.name(), sc.name()); QCOMPARE(scB.code(), sc.code()); ServerCity scC; scC = sc; QCOMPARE(scC.name(), sc.name()); QCOMPARE(scC.code(), sc.code()); sc = sc; } // ServerCountry // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void TestModels::serverCountryBasic() { ServerCountry sc; QCOMPARE(sc.name(), ""); QCOMPARE(sc.code(), ""); QVERIFY(sc.cities().isEmpty()); } void TestModels::serverCountryFromJson_data() { QTest::addColumn("json"); QTest::addColumn("result"); QTest::addColumn("name"); QTest::addColumn("code"); QTest::addColumn("cities"); QJsonObject obj; QTest::addRow("empty") << obj << false; obj.insert("name", "name"); QTest::addRow("name") << obj << false; obj.insert("code", "code"); QTest::addRow("code") << obj << false; obj.insert("cities", "cities"); QTest::addRow("cities invalid") << obj << false; QJsonArray cities; obj.insert("cities", cities); QTest::addRow("cities empty") << obj << true << "name" << "code" << 0; } void TestModels::serverCountryFromJson() { QFETCH(QJsonObject, json); QFETCH(bool, result); ServerCountry sc; QCOMPARE(sc.fromJson(json), result); if (!result) { QCOMPARE(sc.name(), ""); QCOMPARE(sc.code(), ""); QVERIFY(sc.cities().isEmpty()); return; } QFETCH(QString, name); QCOMPARE(sc.name(), name); QFETCH(QString, code); QCOMPARE(sc.code(), code); QFETCH(int, cities); QCOMPARE(sc.cities().length(), cities); ServerCountry scB(sc); QCOMPARE(scB.name(), sc.name()); QCOMPARE(scB.code(), sc.code()); ServerCountry scC; scC = sc; QCOMPARE(scC.name(), sc.name()); QCOMPARE(scC.code(), sc.code()); sc = sc; } // ServerCountryModel // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void TestModels::serverCountryModelBasic() { ServerCountryModel dm; QVERIFY(!dm.initialized()); SettingsHolder settingsHolder; QVERIFY(!dm.fromSettings()); QHash rn = dm.roleNames(); QCOMPARE(rn.count(), 3); QCOMPARE(rn[ServerCountryModel::NameRole], "name"); QCOMPARE(rn[ServerCountryModel::CodeRole], "code"); QCOMPARE(rn[ServerCountryModel::CitiesRole], "cities"); QCOMPARE(dm.rowCount(QModelIndex()), 0); QCOMPARE(dm.data(QModelIndex(), ServerCountryModel::NameRole), QVariant()); } void TestModels::serverCountryModelFromJson_data() { QTest::addColumn("json"); QTest::addColumn("result"); QTest::addColumn("countries"); QTest::addColumn("name"); QTest::addColumn("code"); QTest::addColumn("cities"); QTest::addRow("invalid") << QByteArray("") << false; QTest::addRow("array") << QByteArray("[]") << false; QJsonObject obj; QTest::addRow("empty") << QJsonDocument(obj).toJson() << false; obj.insert("countries", 42); QTest::addRow("invalid countries") << QJsonDocument(obj).toJson() << false; QJsonArray countries; obj.insert("countries", countries); QTest::addRow("good but empty") << QJsonDocument(obj).toJson() << true << 0 << QVariant() << QVariant() << QVariant(); countries.append(42); obj.insert("countries", countries); QTest::addRow("invalid city") << QJsonDocument(obj).toJson() << false; QJsonObject d; d.insert("name", "serverCountryName"); d.insert("code", "serverCountryCode"); d.insert("cities", QJsonArray()); countries.replace(0, d); obj.insert("countries", countries); QTest::addRow("good but empty cities") << QJsonDocument(obj).toJson() << true << 1 << QVariant("serverCountryName") << QVariant("serverCountryCode") << QVariant(QStringList{}); QJsonArray cities; cities.append(42); d.insert("cities", cities); countries.replace(0, d); obj.insert("countries", countries); QTest::addRow("invalid city object") << QJsonDocument(obj).toJson() << false; QJsonObject city; city.insert("code", "serverCityCode"); city.insert("name", "serverCityName"); city.insert("servers", QJsonArray()); cities.replace(0, city); d.insert("cities", cities); countries.replace(0, d); obj.insert("countries", countries); QTest::addRow("good but empty cities") << QJsonDocument(obj).toJson() << true << 1 << QVariant("serverCountryName") << QVariant("serverCountryCode") << QVariant(QStringList{"serverCityName"}); cities.append(city); d.insert("cities", cities); countries.append(d); obj.insert("countries", countries); QTest::addRow("good") << QJsonDocument(obj).toJson() << true << 2 << QVariant("serverCountryName") << QVariant("serverCountryCode") << QVariant(QStringList{"serverCityName"}); } void TestModels::serverCountryModelFromJson() { QFETCH(QByteArray, json); QFETCH(bool, result); // from json { ServerCountryModel m; QCOMPARE(m.fromJson(json), result); if (!result) { QVERIFY(!m.initialized()); QCOMPARE(m.rowCount(QModelIndex()), 0); } else { QVERIFY(m.initialized()); QFETCH(int, countries); QCOMPARE(m.rowCount(QModelIndex()), countries); QCOMPARE(m.data(QModelIndex(), ServerCountryModel::NameRole), QVariant()); QCOMPARE(m.data(QModelIndex(), ServerCountryModel::CodeRole), QVariant()); QCOMPARE(m.data(QModelIndex(), ServerCountryModel::CitiesRole), QVariant()); QModelIndex index = m.index(0, 0); QFETCH(QVariant, name); QCOMPARE(m.data(index, ServerCountryModel::NameRole), name); QFETCH(QVariant, code); QCOMPARE(m.data(index, ServerCountryModel::CodeRole), code); QFETCH(QVariant, cities); QCOMPARE(m.data(index, ServerCountryModel::CitiesRole), cities); QCOMPARE(m.countryName(code.toString()), name.toString()); QCOMPARE(m.countryName("invalid"), QString()); QVERIFY(m.fromJson(json)); } } // from settings { SettingsHolder settingsHolder; SettingsHolder::instance()->setServers(json); ServerCountryModel m; QCOMPARE(m.fromSettings(), result); if (!result) { QVERIFY(!m.initialized()); QCOMPARE(m.rowCount(QModelIndex()), 0); } else { QVERIFY(m.initialized()); QFETCH(int, countries); QCOMPARE(m.rowCount(QModelIndex()), countries); QCOMPARE(m.data(QModelIndex(), ServerCountryModel::NameRole), QVariant()); QCOMPARE(m.data(QModelIndex(), ServerCountryModel::CodeRole), QVariant()); QCOMPARE(m.data(QModelIndex(), ServerCountryModel::CitiesRole), QVariant()); QModelIndex index = m.index(0, 0); QFETCH(QVariant, name); QCOMPARE(m.data(index, ServerCountryModel::NameRole), name); QFETCH(QVariant, code); QCOMPARE(m.data(index, ServerCountryModel::CodeRole), code); QFETCH(QVariant, cities); QCOMPARE(m.data(index, ServerCountryModel::CitiesRole), cities); QCOMPARE(m.data(index, ServerCountryModel::CitiesRole + 1), QVariant()); QCOMPARE(m.countryName(code.toString()), name.toString()); QCOMPARE(m.countryName("invalid"), QString()); } } } void TestModels::serverCountryModelPick() { QJsonObject server; server.insert("hostname", "hostname"); server.insert("ipv4_addr_in", "ipv4AddrIn"); server.insert("ipv4_gateway", "ipv4Gateway"); server.insert("ipv6_addr_in", "ipv6AddrIn"); server.insert("ipv6_gateway", "ipv6Gateway"); server.insert("public_key", "publicKey"); server.insert("weight", 1234); server.insert("port_ranges", QJsonArray()); QJsonArray servers; servers.append(server); QJsonObject city; city.insert("code", "serverCityCode"); city.insert("name", "serverCityName"); city.insert("servers", servers); QJsonArray cities; cities.append(city); QJsonObject country; country.insert("name", "serverCountryName"); country.insert("code", "serverCountryCode"); country.insert("cities", cities); QJsonArray countries; countries.append(country); QJsonObject obj; obj.insert("countries", countries); QByteArray json = QJsonDocument(obj).toJson(); ServerCountryModel m; QCOMPARE(m.fromJson(json), true); { ServerData sd; QCOMPARE(m.pickIfExists("serverCountryCode", "serverCityCode", sd), true); QCOMPARE(sd.countryCode(), "serverCountryCode"); QCOMPARE(sd.country(), "serverCountryName"); QCOMPARE(sd.city(), "serverCityName"); QCOMPARE(m.exists(sd), true); QCOMPARE(m.pickIfExists("serverCountryCode2", "serverCityCode", sd), false); QCOMPARE(m.pickIfExists("serverCountryCode", "serverCityCode2", sd), false); } { ServerData sd; m.pickRandom(sd); QCOMPARE(sd.countryCode(), "serverCountryCode"); QCOMPARE(sd.country(), "serverCountryName"); QCOMPARE(sd.city(), "serverCityName"); QCOMPARE(m.exists(sd), true); } { ServerData sd; QCOMPARE(m.pickByIPv4Address("ipv4AddrIn", sd), true); QCOMPARE(sd.countryCode(), "serverCountryCode"); QCOMPARE(sd.country(), "serverCountryName"); QCOMPARE(sd.city(), "serverCityName"); QCOMPARE(m.exists(sd), true); QCOMPARE(m.pickByIPv4Address("ipv4AddrIn2", sd), false); } } // ServerData // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void TestModels::serverDataBasic() { ServerData sd; QSignalSpy spy(&sd, &ServerData::changed); QVERIFY(!sd.initialized()); QCOMPARE(sd.countryCode(), ""); QCOMPARE(sd.country(), ""); QCOMPARE(sd.city(), ""); { QJsonObject countryObj; countryObj.insert("name", "serverCountryName"); countryObj.insert("code", "serverCountryCode"); countryObj.insert("cities", QJsonArray()); ServerCountry country; QVERIFY(country.fromJson(countryObj)); QJsonObject cityObj; cityObj.insert("code", "serverCityCode"); cityObj.insert("name", "serverCityName"); cityObj.insert("servers", QJsonArray()); ServerCity city; QVERIFY(city.fromJson(cityObj)); sd.initialize(country, city); QCOMPARE(spy.count(), 1); QVERIFY(sd.initialized()); QCOMPARE(sd.countryCode(), "serverCountryCode"); QCOMPARE(sd.country(), "serverCountryName"); QCOMPARE(sd.city(), "serverCityName"); { SettingsHolder settingsHolder; sd.writeSettings(); ServerData sd2; QVERIFY(sd2.fromSettings()); QVERIFY(sd2.initialized()); QCOMPARE(sd2.countryCode(), "serverCountryCode"); QCOMPARE(sd2.country(), "serverCountryName"); QCOMPARE(sd2.city(), "serverCityName"); QCOMPARE(spy.count(), 1); } } sd.update("new Country Code", "new Country", "new City"); QCOMPARE(spy.count(), 2); QVERIFY(sd.initialized()); QCOMPARE(sd.countryCode(), "new Country Code"); QCOMPARE(sd.country(), "new Country"); QCOMPARE(sd.city(), "new City"); sd.forget(); QCOMPARE(spy.count(), 2); QVERIFY(!sd.initialized()); QCOMPARE(sd.countryCode(), "new Country Code"); QCOMPARE(sd.country(), "new Country"); QCOMPARE(sd.city(), "new City"); { SettingsHolder settingsHolder; QVERIFY(!sd.fromSettings()); QCOMPARE(spy.count(), 2); } } // User // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void TestModels::userBasic() { User user; QVERIFY(!user.initialized()); QCOMPARE(user.avatar(), ""); QCOMPARE(user.displayName(), ""); QCOMPARE(user.email(), ""); QCOMPARE(user.maxDevices(), 5); QVERIFY(!user.subscriptionNeeded()); } void TestModels::userFromJson_data() { QTest::addColumn("json"); QTest::addColumn("result"); QTest::addColumn("avatar"); QTest::addColumn("displayName"); QTest::addColumn("email"); QTest::addColumn("maxDevices"); QTest::addColumn("subscriptionNeeded"); QTest::newRow("null") << QByteArray("") << false; QTest::newRow("invalid") << QByteArray("wow") << false; QTest::newRow("array") << QByteArray("[]") << false; QJsonObject obj; QTest::newRow("empty object") << QJsonDocument(obj).toJson() << false; obj.insert("a", QJsonValue("b")); QTest::newRow("no avatar") << QJsonDocument(obj).toJson() << false; obj.insert("avatar", QJsonValue("avatar")); QTest::newRow("no displayName") << QJsonDocument(obj).toJson() << false; obj.insert("display_name", QJsonValue("displayName")); QTest::newRow("no email") << QJsonDocument(obj).toJson() << false; obj.insert("email", QJsonValue("email")); QTest::newRow("no maxDevices") << QJsonDocument(obj).toJson() << false; obj.insert("max_devices", QJsonValue(123)); QTest::newRow("no maxDevices") << QJsonDocument(obj).toJson() << false; obj.insert("subscriptions", QJsonValue("wow")); QTest::newRow("invalid subscription") << QJsonDocument(obj).toJson() << false; QJsonObject subscription; obj.insert("subscriptions", subscription); QTest::newRow("empty subscription") << QJsonDocument(obj).toJson() << true << "avatar" << "displayName" << "email" << 123 << true; subscription.insert("vpn", QJsonValue("WOW")); obj.insert("subscriptions", subscription); QTest::newRow("invalid vpn subscription") << QJsonDocument(obj).toJson() << false; QJsonObject subVpn; subscription.insert("vpn", subVpn); obj.insert("subscriptions", subscription); QTest::newRow("empty vpn subscription") << QJsonDocument(obj).toJson() << false; subVpn.insert("active", QJsonValue("sure!")); subscription.insert("vpn", subVpn); obj.insert("subscriptions", subscription); QTest::newRow("invalid active vpn subscription") << QJsonDocument(obj).toJson() << false; subVpn.insert("active", QJsonValue(true)); subscription.insert("vpn", subVpn); obj.insert("subscriptions", subscription); QTest::newRow("active vpn subscription") << QJsonDocument(obj).toJson() << true << "avatar" << "displayName" << "email" << 123 << false; subVpn.insert("active", QJsonValue(false)); subscription.insert("vpn", subVpn); obj.insert("subscriptions", subscription); QTest::newRow("inactive vpn subscription") << QJsonDocument(obj).toJson() << true << "avatar" << "displayName" << "email" << 123 << true; } void TestModels::userFromJson() { QFETCH(QByteArray, json); QFETCH(bool, result); User user; QSignalSpy spy(&user, &User::changed); QCOMPARE(user.fromJson(json), result); if (!result) { QVERIFY(!user.initialized()); QCOMPARE(spy.count(), 0); QCOMPARE(user.avatar(), ""); QCOMPARE(user.displayName(), ""); QCOMPARE(user.email(), ""); QCOMPARE(user.maxDevices(), 5); QVERIFY(!user.subscriptionNeeded()); return; } QVERIFY(user.initialized()); QCOMPARE(spy.count(), 1); QFETCH(QString, avatar); QCOMPARE(user.avatar(), avatar); QFETCH(QString, displayName); QCOMPARE(user.displayName(), displayName); QFETCH(QString, email); QCOMPARE(user.email(), email); QFETCH(int, maxDevices); QCOMPARE(user.maxDevices(), maxDevices); QFETCH(bool, subscriptionNeeded); QCOMPARE(user.subscriptionNeeded(), subscriptionNeeded); { SettingsHolder settingsHolder; user.writeSettings(); // FromSettings { User user; QSignalSpy spy(&user, &User::changed); QVERIFY(user.fromSettings()); QVERIFY(user.initialized()); QCOMPARE(spy.count(), 0); QFETCH(QString, avatar); QCOMPARE(user.avatar(), avatar); QFETCH(QString, displayName); QCOMPARE(user.displayName(), displayName); QFETCH(QString, email); QCOMPARE(user.email(), email); QFETCH(int, maxDevices); QCOMPARE(user.maxDevices(), maxDevices); QFETCH(bool, subscriptionNeeded); QCOMPARE(user.subscriptionNeeded(), subscriptionNeeded); } } } void TestModels::userFromSettings() { SettingsHolder settingsHolder; User user; QSignalSpy spy(&user, &User::changed); QVERIFY(!user.fromSettings()); QVERIFY(!user.initialized()); QCOMPARE(spy.count(), 0); SettingsHolder::instance()->setUserAvatar("avatar"); QVERIFY(!user.fromSettings()); QVERIFY(!user.initialized()); QCOMPARE(spy.count(), 0); SettingsHolder::instance()->setUserDisplayName("displayName"); QVERIFY(!user.fromSettings()); QVERIFY(!user.initialized()); QCOMPARE(spy.count(), 0); SettingsHolder::instance()->setUserEmail("email"); QVERIFY(!user.fromSettings()); QVERIFY(!user.initialized()); QCOMPARE(spy.count(), 0); SettingsHolder::instance()->setUserMaxDevices(123); QVERIFY(!user.fromSettings()); QVERIFY(!user.initialized()); QCOMPARE(spy.count(), 0); SettingsHolder::instance()->setUserSubscriptionNeeded(true); QVERIFY(user.fromSettings()); QVERIFY(user.initialized()); QCOMPARE(spy.count(), 0); QCOMPARE(user.avatar(), "avatar"); QCOMPARE(user.displayName(), "displayName"); QCOMPARE(user.email(), "email"); QCOMPARE(user.maxDevices(), 123); QCOMPARE(user.subscriptionNeeded(), true); } // IPAddressRange // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ void TestModels::ipAddressRangeBasic() { IPAddressRange a("ip", (uint32_t)123, IPAddressRange::IPv4); QCOMPARE(a.ipAddress(), "ip"); QCOMPARE(a.range(), (uint32_t)123); QCOMPARE(a.type(), IPAddressRange::IPv4); QCOMPARE(a.toString(), "ip/123"); IPAddressRange b(a); QCOMPARE(b.ipAddress(), a.ipAddress()); QCOMPARE(b.range(), a.range()); QCOMPARE(b.type(), a.type()); QCOMPARE(b.toString(), a.toString()); IPAddressRange c(a); c = a; QCOMPARE(c.ipAddress(), a.ipAddress()); QCOMPARE(c.range(), a.range()); QCOMPARE(c.type(), a.type()); QCOMPARE(c.toString(), a.toString()); a = a; } static TestModels s_testModels; mozilla-vpn-client-2.2.0/tests/unit/testmodels.h000066400000000000000000000021511404202232700217070ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "helper.h" class TestModels final : public TestHelper { Q_OBJECT private slots: void deviceBasic(); void deviceCurrentDeviceName(); void deviceFromJson_data(); void deviceFromJson(); void deviceModelBasic(); void deviceModelFromJson_data(); void deviceModelFromJson(); void keysBasic(); void serverBasic(); void serverFromJson_data(); void serverFromJson(); void serverWeightChooser(); void serverCityBasic(); void serverCityFromJson_data(); void serverCityFromJson(); void serverCountryBasic(); void serverCountryFromJson_data(); void serverCountryFromJson(); void serverCountryModelBasic(); void serverCountryModelFromJson_data(); void serverCountryModelFromJson(); void serverCountryModelPick(); void serverDataBasic(); void userBasic(); void userFromJson_data(); void userFromJson(); void userFromSettings(); void ipAddressRangeBasic(); }; mozilla-vpn-client-2.2.0/tests/unit/testnetworkmanager.cpp000066400000000000000000000011061404202232700240020ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "testnetworkmanager.h" #include "../../src/simplenetworkmanager.h" #include "helper.h" void TestNetworkManager::basic() { SimpleNetworkManager snm; QCOMPARE(&snm, NetworkManager::instance()); QVERIFY(snm.userAgent().contains("MozillaVPN")); QCOMPARE(snm.networkAccessManager(), snm.networkAccessManager()); } static TestNetworkManager s_testNetworkManager; mozilla-vpn-client-2.2.0/tests/unit/testnetworkmanager.h000066400000000000000000000005071404202232700234530ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "helper.h" class TestNetworkManager final : public TestHelper { Q_OBJECT private slots: void basic(); }; mozilla-vpn-client-2.2.0/tests/unit/testreleasemonitor.cpp000066400000000000000000000157651404202232700240260ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "testreleasemonitor.h" #include "../../src/releasemonitor.h" #include "../../src/update/versionapi.h" #include "helper.h" #include #include #include void TestReleaseMonitor::failure() { ReleaseMonitor rm; rm.runSoon(); TestHelper::networkConfig.append(TestHelper::NetworkConfig( TestHelper::NetworkConfig::Failure, QByteArray())); QEventLoop loop; connect(&rm, &ReleaseMonitor::releaseChecked, [&] { loop.exit(); }); loop.exec(); } void TestReleaseMonitor::success_data() { QTest::addColumn("json"); QTest::addColumn("result"); QTest::addRow("empty") << QByteArray("") << false; QJsonObject obj; QTest::addRow("empty object") << QJsonDocument(obj).toJson() << false; QJsonValue value(42); obj.insert("linux", value); obj.insert("ios", value); obj.insert("macos", value); obj.insert("dummy", value); QTest::addRow("invalid platform") << QJsonDocument(obj).toJson() << false; QJsonObject platform; obj.insert("linux", platform); obj.insert("ios", platform); obj.insert("macos", platform); obj.insert("dummy", platform); QTest::addRow("empty platform") << QJsonDocument(obj).toJson() << false; QJsonObject latest; platform.insert("latest", latest); obj.insert("linux", platform); obj.insert("ios", platform); obj.insert("macos", platform); obj.insert("dummy", platform); QTest::addRow("empty latest") << QJsonDocument(obj).toJson() << false; latest.insert("version", 42); platform.insert("latest", latest); obj.insert("linux", platform); obj.insert("ios", platform); obj.insert("macos", platform); obj.insert("dummy", platform); QTest::addRow("invalid latest version") << QJsonDocument(obj).toJson() << false; latest.insert("version", "42"); platform.insert("latest", latest); obj.insert("linux", platform); obj.insert("ios", platform); obj.insert("macos", platform); obj.insert("dummy", platform); QTest::addRow("missing minimum") << QJsonDocument(obj).toJson() << false; QJsonObject minimum; minimum.insert("version", 42); platform.insert("minimum", minimum); obj.insert("linux", platform); obj.insert("ios", platform); obj.insert("macos", platform); obj.insert("dummy", platform); QTest::addRow("invalid minimum version") << QJsonDocument(obj).toJson() << false; minimum.insert("version", "42"); platform.insert("minimum", minimum); obj.insert("linux", platform); obj.insert("ios", platform); obj.insert("macos", platform); obj.insert("dummy", platform); QTest::addRow("all good") << QJsonDocument(obj).toJson() << true; minimum.insert("version", "9999"); platform.insert("minimum", minimum); obj.insert("linux", platform); obj.insert("ios", platform); obj.insert("macos", platform); obj.insert("dummy", platform); QTest::addRow("completed!") << QJsonDocument(obj).toJson() << true; } void TestReleaseMonitor::success() { ReleaseMonitor rm; rm.runSoon(); QFETCH(QByteArray, json); TestHelper::networkConfig.append( TestHelper::NetworkConfig(TestHelper::NetworkConfig::Success, json)); QEventLoop loop; connect(&rm, &ReleaseMonitor::releaseChecked, [&] { loop.exit(); }); loop.exec(); } void TestReleaseMonitor::compareVersions_data() { QTest::addColumn("a"); QTest::addColumn("b"); QTest::addColumn("result"); QTest::addRow("empty a") << "" << "123" << 1; QTest::addRow("empty b") << "123" << "" << -1; QTest::addRow("empty all") << "" << "" << 0; QTest::addRow("equal 1") << "0.1" << "0.1" << 0; QTest::addRow("equal 2") << "0.1.2" << "0.1.2" << 0; QTest::addRow("equal 3") << "0.1.2.3" << "0.1.2.3" << 0; QTest::addRow("equal 4") << "0" << "0" << 0; QTest::addRow("equal 5") << "123" << "123" << 0; QTest::addRow("euqal 6") << "0.1.2.123" << "0.1.2.456" << 0; QTest::addRow("a wins 1") << "0" << "123" << -1; QTest::addRow("a wins 2") << "0.1" << "123" << -1; QTest::addRow("a wins 3") << "0.1.2" << "123" << -1; QTest::addRow("a wins 4") << "0.1.2.3" << "123" << -1; QTest::addRow("a wins 5") << "0.1.2.3.4" << "123" << -1; QTest::addRow("a wins 6") << "1.2.3.4" << "123" << -1; QTest::addRow("a wins 7") << "0" << "1" << -1; QTest::addRow("a wins 8") << "0" << "0.1" << -1; QTest::addRow("a wins 9") << "0" << "0.1.2" << -1; QTest::addRow("a wins A") << "0" << "0.1.2.3" << -1; QTest::addRow("a wins B") << "0.1" << "1" << -1; QTest::addRow("a wins C") << "0.1" << "0.2" << -1; QTest::addRow("a wins D") << "0.1" << "0.1.2" << -1; QTest::addRow("a wins E") << "0.1.2" << "1" << -1; QTest::addRow("a wins F") << "0.1.2" << "0.2" << -1; QTest::addRow("a wins 10") << "0.1.2" << "0.1.3" << -1; QTest::addRow("b wins 1") << "123" << "0" << 1; QTest::addRow("b wins 2") << "123" << "0.1" << 1; QTest::addRow("b wins 3") << "123" << "0.1.2" << 1; QTest::addRow("b wins 4") << "123" << "0.1.2.3" << 1; QTest::addRow("b wins 5") << "123" << "0.1.2.3.4" << 1; QTest::addRow("b wins 6") << "123" << "1.2.3.4" << 1; QTest::addRow("b wins 7") << "1" << "0" << 1; QTest::addRow("b wins 8") << "0.1" << "0" << 1; QTest::addRow("b wins 9") << "0.1.2" << "0" << 1; QTest::addRow("b wins A") << "0.1.2.3" << "0" << 1; QTest::addRow("b wins B") << "1" << "0.1" << 1; QTest::addRow("b wins C") << "0.2" << "0.1" << 1; QTest::addRow("b wins D") << "0.1.2" << "0.1" << 1; QTest::addRow("b wins E") << "1" << "0.1.2" << 1; QTest::addRow("b wins F") << "0.2" << "0.1.2" << 1; QTest::addRow("b wins 10") << "0.1.3" << "0.1.2" << 1; } void TestReleaseMonitor::compareVersions() { QFETCH(QString, a); QFETCH(QString, b); QFETCH(int, result); QCOMPARE(VersionApi::compareVersions(a, b), result); } static TestReleaseMonitor s_testReleaseMonitor; mozilla-vpn-client-2.2.0/tests/unit/testreleasemonitor.h000066400000000000000000000006551404202232700234630ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "helper.h" class TestReleaseMonitor final : public TestHelper { Q_OBJECT private slots: void failure(); void success_data(); void success(); void compareVersions_data(); void compareVersions(); }; mozilla-vpn-client-2.2.0/tests/unit/teststatusicon.cpp000066400000000000000000000034041404202232700231550ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "teststatusicon.h" #include "../../src/statusicon.h" #include void TestStatusIcon::basic() { StatusIcon si; QCOMPARE(si.iconUrl().toString(), "qrc:/ui/resources/logo-generic.svg"); QCOMPARE(si.iconString(), ":/ui/resources/logo-generic.svg"); si.stateChanged(); QCOMPARE(si.iconUrl().toString(), "qrc:/ui/resources/logo-generic.svg"); QCOMPARE(si.iconString(), ":/ui/resources/logo-generic.svg"); TestHelper::vpnState = MozillaVPN::StateMain; TestHelper::controllerState = Controller::StateOn; si.stateChanged(); QCOMPARE(si.iconUrl().toString(), "qrc:/ui/resources/logo-on.svg"); QCOMPARE(si.iconString(), ":/ui/resources/logo-on.svg"); TestHelper::controllerState = Controller::StateOff; si.stateChanged(); QCOMPARE(si.iconUrl().toString(), "qrc:/ui/resources/logo-generic.svg"); QCOMPARE(si.iconString(), ":/ui/resources/logo-generic.svg"); TestHelper::controllerState = Controller::StateSwitching; int i = 0; QEventLoop loop; connect(&si, &StatusIcon::iconChanged, [&]() { if (i > 10) { si.disconnect(); loop.exit(); return; } QCOMPARE(si.iconUrl().toString(), QString("qrc:/ui/resources/logo-animated%1.svg").arg((i % 4) + 1)); QCOMPARE(si.iconString(), QString(":/ui/resources/logo-animated%1.svg").arg((i % 4) + 1)); ++i; }); si.stateChanged(); QCOMPARE(si.iconUrl().toString(), "qrc:/ui/resources/logo-animated1.svg"); QCOMPARE(si.iconString(), ":/ui/resources/logo-animated1.svg"); loop.exec(); } static TestStatusIcon s_testStatusIcon; mozilla-vpn-client-2.2.0/tests/unit/teststatusicon.h000066400000000000000000000004751404202232700226270ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "helper.h" class TestStatusIcon : public TestHelper { Q_OBJECT private slots: void basic(); }; mozilla-vpn-client-2.2.0/tests/unit/testtasks.cpp000066400000000000000000000047521404202232700221150ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "testtasks.h" #include "../../src/mozillavpn.h" #include "../../src/tasks/accountandservers/taskaccountandservers.h" #include "../../src/tasks/adddevice/taskadddevice.h" #include "../../src/tasks/function/taskfunction.h" void TestTasks::accountAndServers() { // Failure { TestHelper::networkConfig.append(TestHelper::NetworkConfig( TestHelper::NetworkConfig::Failure, QByteArray())); TestHelper::networkConfig.append(TestHelper::NetworkConfig( TestHelper::NetworkConfig::Failure, QByteArray())); TaskAccountAndServers* task = new TaskAccountAndServers(); QEventLoop loop; connect(task, &Task::completed, [&]() { loop.exit(); }); MozillaVPN::instance()->scheduleTask(task); loop.exec(); } // Success { TestHelper::networkConfig.append(TestHelper::NetworkConfig( TestHelper::NetworkConfig::Success, QByteArray())); TestHelper::networkConfig.append(TestHelper::NetworkConfig( TestHelper::NetworkConfig::Success, QByteArray())); TaskAccountAndServers* task = new TaskAccountAndServers(); QEventLoop loop; connect(task, &Task::completed, [&]() { loop.exit(); }); MozillaVPN::instance()->scheduleTask(task); loop.exec(); } } void TestTasks::addDevice_success() { TestHelper::networkConfig.append(TestHelper::NetworkConfig( TestHelper::NetworkConfig::Success, QByteArray())); TaskAddDevice* task = new TaskAddDevice("foobar"); QEventLoop loop; connect(task, &Task::completed, [&]() { loop.exit(); }); MozillaVPN::instance()->scheduleTask(task); loop.exec(); } void TestTasks::addDevice_failure() { TestHelper::networkConfig.append(TestHelper::NetworkConfig( TestHelper::NetworkConfig::Failure, QByteArray())); TaskAddDevice* task = new TaskAddDevice("foobar"); QEventLoop loop; connect(task, &Task::completed, [&]() { loop.exit(); }); MozillaVPN::instance()->scheduleTask(task); loop.exec(); } void TestTasks::authenticate() { // TODO } void TestTasks::function() { bool completed = false; TaskFunction* task = new TaskFunction([&](MozillaVPN* vpn) { completed = true; QCOMPARE(vpn, MozillaVPN::instance()); }); MozillaVPN::instance()->scheduleTask(task); QVERIFY(completed); } void TestTasks::removeDevice() { // TODO } static TestTasks s_testTasks; mozilla-vpn-client-2.2.0/tests/unit/testtasks.h000066400000000000000000000007071404202232700215560ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "helper.h" class TestTasks final : public TestHelper { Q_OBJECT private slots: void accountAndServers(); void addDevice_success(); void addDevice_failure(); void authenticate(); void function(); void removeDevice(); }; mozilla-vpn-client-2.2.0/tests/unit/testtimersingleshot.cpp000066400000000000000000000023021404202232700241750ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "testtimersingleshot.h" #include "../../src/timersingleshot.h" #include void TestTimerSingleShot::basic() { { bool called = false; TimerSingleShot::create(nullptr, 0, [&] { called = true; }); QEventLoop loop; QTimer t; connect(&t, &QTimer::timeout, [&] { loop.exit(); }); t.start(0); loop.exec(); QVERIFY(called); } { QTimer* obj = new QTimer(); bool called = false; TimerSingleShot::create(obj, 0, [&] { called = true; }); QEventLoop loop; QTimer t; connect(&t, &QTimer::timeout, [&] { loop.exit(); }); t.start(0); loop.exec(); QVERIFY(called); delete obj; } { QTimer* obj = new QTimer(); bool called = false; TimerSingleShot::create(obj, 0, [&] { called = true; }); delete obj; QEventLoop loop; QTimer t; connect(&t, &QTimer::timeout, [&] { loop.exit(); }); t.start(0); loop.exec(); QVERIFY(!called); } } static TestTimerSingleShot s_testTimerSingleShot; mozilla-vpn-client-2.2.0/tests/unit/testtimersingleshot.h000066400000000000000000000005021404202232700236420ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "helper.h" class TestTimerSingleShot : public TestHelper { Q_OBJECT private slots: void basic(); }; mozilla-vpn-client-2.2.0/tests/unit/unit.pro000066400000000000000000000132141404202232700210560ustar00rootroot00000000000000# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. QT += testlib QT += charts QT += network QT += qml QT += xml DEFINES += APP_VERSION=\\\"1234\\\" DEFINES += BUILD_ID=\\\"1234\\\" DEFINES += QT_DEPRECATED_WARNINGS DEFINES += UNIT_TEST TEMPLATE = app TARGET = tests INCLUDEPATH += \ . \ ../../src \ ../../src/hacl-star \ ../../src/hacl-star/kremlin \ ../../src/hacl-star/kremlin/minimal HEADERS += \ ../../src/captiveportal/captiveportal.h \ ../../src/command.h \ ../../src/commandlineparser.h \ ../../src/connectioncheck.h \ ../../src/connectiondataholder.h \ ../../src/controller.h \ ../../src/curve25519.h \ ../../src/errorhandler.h \ ../../src/featurelist.h \ ../../src/ipaddress.h \ ../../src/ipaddressrange.h \ ../../src/leakdetector.h \ ../../src/localizer.h \ ../../src/logger.h \ ../../src/loghandler.h \ ../../src/models/device.h \ ../../src/models/devicemodel.h \ ../../src/models/keys.h \ ../../src/models/server.h \ ../../src/models/servercity.h \ ../../src/models/servercountry.h \ ../../src/models/servercountrymodel.h \ ../../src/models/serverdata.h \ ../../src/models/user.h \ ../../src/mozillavpn.h \ ../../src/networkmanager.h \ ../../src/networkrequest.h \ ../../src/networkwatcher.h \ ../../src/networkwatcherimpl.h \ ../../src/pinghelper.h \ ../../src/pingsender.h \ ../../src/pingsendworker.h \ ../../src/platforms/android/androiddatamigration.h \ ../../src/platforms/android/androidsharedprefs.h \ ../../src/platforms/dummy/dummynetworkwatcher.h \ ../../src/platforms/dummy/dummypingsendworker.h \ ../../src/qmlengineholder.h \ ../../src/releasemonitor.h \ ../../src/serveri18n.h \ ../../src/settingsholder.h \ ../../src/simplenetworkmanager.h \ ../../src/statusicon.h \ ../../src/task.h \ ../../src/tasks/accountandservers/taskaccountandservers.h \ ../../src/tasks/adddevice/taskadddevice.h \ ../../src/tasks/function/taskfunction.h \ ../../src/timersingleshot.h \ ../../src/update/updater.h \ ../../src/update/versionapi.h \ helper.h \ testandroidmigration.h \ testcommandlineparser.h \ testconnectiondataholder.h \ testlocalizer.h \ testlogger.h \ testipaddress.h \ testmodels.h \ testnetworkmanager.h \ testreleasemonitor.h \ teststatusicon.h \ testtasks.h \ testtimersingleshot.h SOURCES += \ ../../src/captiveportal/captiveportal.cpp \ ../../src/command.cpp \ ../../src/commandlineparser.cpp \ ../../src/connectioncheck.cpp \ ../../src/connectiondataholder.cpp \ ../../src/curve25519.cpp \ ../../src/errorhandler.cpp \ ../../src/featurelist.cpp \ ../../src/hacl-star/Hacl_Chacha20.c \ ../../src/hacl-star/Hacl_Chacha20Poly1305_32.c \ ../../src/hacl-star/Hacl_Curve25519_51.c \ ../../src/hacl-star/Hacl_Poly1305_32.c \ ../../src/ipaddress.cpp \ ../../src/ipaddressrange.cpp \ ../../src/leakdetector.cpp \ ../../src/localizer.cpp \ ../../src/logger.cpp \ ../../src/loghandler.cpp \ ../../src/models/device.cpp \ ../../src/models/devicemodel.cpp \ ../../src/models/keys.cpp \ ../../src/models/server.cpp \ ../../src/models/servercity.cpp \ ../../src/models/servercountry.cpp \ ../../src/models/servercountrymodel.cpp \ ../../src/models/serverdata.cpp \ ../../src/models/user.cpp \ ../../src/networkmanager.cpp \ ../../src/networkwatcher.cpp \ ../../src/pinghelper.cpp \ ../../src/pingsender.cpp \ ../../src/platforms/android/androiddatamigration.cpp \ ../../src/platforms/android/androidsharedprefs.cpp \ ../../src/platforms/dummy/dummynetworkwatcher.cpp \ ../../src/platforms/dummy/dummypingsendworker.cpp \ ../../src/qmlengineholder.cpp \ ../../src/releasemonitor.cpp \ ../../src/serveri18n.cpp \ ../../src/settingsholder.cpp \ ../../src/simplenetworkmanager.cpp \ ../../src/statusicon.cpp \ ../../src/tasks/accountandservers/taskaccountandservers.cpp \ ../../src/tasks/adddevice/taskadddevice.cpp \ ../../src/tasks/function/taskfunction.cpp \ ../../src/timersingleshot.cpp \ ../../src/update/updater.cpp \ ../../src/update/versionapi.cpp \ main.cpp \ moccontroller.cpp \ mocmozillavpn.cpp \ mocnetworkrequest.cpp \ testandroidmigration.cpp \ testcommandlineparser.cpp \ testconnectiondataholder.cpp \ testlocalizer.cpp \ testlogger.cpp \ testipaddress.cpp \ testmodels.cpp \ testnetworkmanager.cpp \ testreleasemonitor.cpp \ teststatusicon.cpp \ testtasks.cpp \ testtimersingleshot.cpp # Platform-specific: Linux linux { # QMAKE_CXXFLAGS *= -Werror } # Platform-specific: MacOS else:macos { # For the loginitem LIBS += -framework ServiceManagement LIBS += -framework Security # QMAKE_CXXFLAGS *= -Werror OBJECTIVE_SOURCES += \ ../../src/platforms/macos/macosutils.mm OBJECTIVE_HEADERS += \ ../../src/platforms/macos/macosutils.h } # Platform-specific: IOS else:ios { DEFINES += MVPN_IOS OBJECTIVE_SOURCES += \ ../../src/platforms/ios/iosutils.mm OBJECTIVE_HEADERS += \ ../../src/platforms/ios/iosutils.h } OBJECTS_DIR = .obj MOC_DIR = .moc RCC_DIR = .rcc UI_DIR = .ui equals(QMAKE_CXX, clang++) { QMAKE_CXXFLAGS += -fprofile-instr-generate -fcoverage-mapping QMAKE_LFLAGS += -fprofile-instr-generate -fcoverage-mapping } mozilla-vpn-client-2.2.0/translations/000077500000000000000000000000001404202232700177545ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/translations/.keepme000066400000000000000000000000001404202232700212110ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/translations/servers-template.json000066400000000000000000000004241404202232700241510ustar00rootroot00000000000000[ { "countryCode": "be", "cities": [ { "city": "Brussels", "languages": { "en": "Brussels", "en_CA": "Brussels", "en_GB": "Brussels", "pt_BR": "Bruxelas" } } ] }, { "countryCode": "us", "languages": { "it": "USA" } } ] mozilla-vpn-client-2.2.0/translations/servers.json000066400000000000000000001004471404202232700223460ustar00rootroot00000000000000[ { "countryCode": "au", "languages": { "cy": "Awstralia", "de": "Australien", "dsb": "Awstralska", "el": "Αυστραλία", "fr": "Australie", "hsb": "Awstralska", "ja": "オーストラリア", "nl": "Australië", "oc": "Austràlia", "pt_BR": "Austrália", "ru": "Австралия", "sk": "Austrália", "uk": "Австралія", "zh_TW": "澳洲" }, "cities": [ { "city": "Melbourne", "languages": { "el": "Μελβούρνη", "ja": "メルボルン", "ru": "Мельбурн", "sq": "Melburni", "uk": "Мельбурн", "zh_TW": "墨爾本" } }, { "city": "Sydney", "languages": { "el": "Σίδνεϊ", "ja": "シドニー", "ru": "Сидней", "uk": "Сідней", "zh_TW": "雪梨" } } ] }, { "countryCode": "at", "languages": { "cy": "Awstria", "de": "Österreich", "dsb": "Awstriska", "el": "Αυστρία", "fr": "Autriche", "hsb": "Awstriska", "ja": "オーストリア", "nl": "Oostenrijk", "oc": "Àustria", "pt_BR": "Áustria", "ru": "Австрия", "sk": "Rakúsko", "uk": "Австрія", "zh_TW": "奧地利" }, "cities": [ { "city": "Vienna", "languages": { "cy": "Fienna", "de": "Wien", "dsb": "Wien", "el": "Βιέννη", "fr": "Vienne", "hsb": "Wien", "ja": "ウィーン", "nl": "Wenen", "oc": "Viena", "pt_BR": "Viena", "ru": "Вена", "sk": "Viedeň", "sq": "Vjenë", "uk": "Відень", "zh_TW": "維也納" } } ] }, { "countryCode": "be", "languages": { "co": "Belgica", "cy": "Gwlad Belg", "de": "Belgien", "dsb": "Belgiska", "el": "Βέλγιο", "fr": "Belgique", "hsb": "Belgiska", "it": "Belgio", "ja": "ベルギー", "nl": "België", "oc": "Belgica", "pt_BR": "Bélgica", "ru": "Бельгия", "sk": "Belgicko", "sq": "Belgjika", "uk": "Бельгія", "zh_TW": "比利時" }, "cities": [ { "city": "Brussels", "languages": { "co": "Bruxelles", "cy": "Dinas Brwsel", "de": "Brüssel", "el": "Βρυξέλλες", "en": "Brussels", "en_CA": "Brussels", "en_GB": "Brussels", "fr": "Bruxelles", "hsb": "Brüssel", "it": "Bruxelles", "ja": "ブリュッセル=ヴィル", "nl": "Brussel", "oc": "Brussèlas", "pt_BR": "Bruxelas", "ru": "Брюссель", "sk": "Brusel", "sq": "Brukseli", "uk": "Брюссель", "zh_TW": "布魯塞爾市" } } ] }, { "countryCode": "br", "languages": { "co": "Brasile", "cy": "Brasil", "de": "Brasilien", "dsb": "Brazilska", "el": "Βραζιλία", "fr": "Brésil", "hsb": "Brazilska", "it": "Brasile", "ja": "ブラジル", "nl": "Brazilië", "oc": "Brasil", "pt_BR": "Brasil", "ru": "Бразилия", "sk": "Brazília", "sq": "Brazili", "uk": "Бразилія", "zh_TW": "巴西" }, "cities": [ { "city": "Sao Paulo", "languages": { "co": "São Paulo", "cy": "São Paulo", "de": "São Paulo", "dsb": "São Paulo", "el": "Σάο Πάολο", "en": "São Paulo", "en_CA": "São Paulo", "en_GB": "São Paulo", "fr": "São Paulo", "hsb": "São Paulo", "it": "San Paolo", "ja": "サンパウロ", "nl": "São Paulo", "oc": "São Paulo", "pt_BR": "São Paulo", "ru": "Сан-Паулу", "sk": "São Paulo", "sq": "São Paulo", "uk": "Сан-Паулу", "zh_TW": "聖保羅" } } ] }, { "countryCode": "bg", "languages": { "cy": "Bwlgaria", "de": "Bulgarien", "dsb": "Bulgaŕska", "el": "Βουλγαρία", "fr": "Bulgarie", "hsb": "Bołharska", "ja": "ブルガリア", "nl": "Bulgarije", "pt_BR": "Bulgária", "ru": "Болгария", "sk": "Bulharsko", "sq": "Bullgaria", "uk": "Болгарія", "zh_TW": "保加利亞" }, "cities": [ { "city": "Sofia", "languages": { "dsb": "Sofija", "el": "Σόφια", "hsb": "Sofija", "ja": "ソフィア", "oc": "Sòfia", "pt_BR": "Sófia", "ru": "София", "sq": "Sofja", "uk": "Софія", "zh_TW": "索菲亞" } } ] }, { "countryCode": "ca", "languages": { "co": "Canadà", "de": "Kanada", "dsb": "Kanada", "el": "Καναδάς", "hsb": "Kanada", "ja": "カナダ", "oc": "Canadà", "pt_BR": "Canadá", "ru": "Канада", "sk": "Kanada", "sq": "Kanadaja", "uk": "Канада", "zh_TW": "加拿大" }, "cities": [ { "city": "Montreal", "languages": { "co": "Montréal", "cy": "Montréal", "el": "Μόντρεαλ", "fr": "Montréal", "it": "Montréal", "ja": "モントリオール", "ru": "Монреаль", "sq": "Montreali", "uk": "Монреаль", "zh_TW": "蒙特婁" } }, { "city": "Toronto", "languages": { "el": "Τορόντο", "ja": "トロント", "ru": "Торонто", "uk": "Торонто", "zh_TW": "多倫多" } }, { "city": "Vancouver", "languages": { "el": "Βανκούβερ", "ja": "バンクーバー", "ru": "Ванкувер", "uk": "Ванкувер", "zh_TW": "溫哥華" } } ] }, { "countryCode": "cz", "languages": { "co": "Cechia", "cy": "Y Weriniaeth Tsiec", "de": "Tschechien", "dsb": "Česka", "el": "Τσεχία", "en_CA": "Czechia", "fr": "Tchéquie", "hsb": "Čěska", "it": "Repubblica Ceca", "ja": "チェコ", "nl": "Tsjechië", "oc": "Republica Chèca", "pt_BR": "República Tcheca", "ru": "Чехия", "sk": "Česko", "sq": "Republika Çeke", "uk": "Чехія", "zh_TW": "捷克共和國" }, "cities": [ { "city": "Prague", "languages": { "co": "Praga", "cy": "Prag", "de": "Prag", "dsb": "Praha", "el": "Πράγα", "hsb": "Praha", "it": "Praga", "ja": "プラハ", "nl": "Praag", "oc": "Praga", "pt_BR": "Praga", "ru": "Прага", "sk": "Praha", "sq": "Praga", "uk": "Прага", "zh_TW": "布拉格" } } ] }, { "countryCode": "dk", "languages": { "co": "Danimarca", "cy": "Denmarc", "de": "Dänemark", "dsb": "Dańska", "el": "Δανία", "fr": "Danemark", "hsb": "Danska", "it": "Danimarca", "ja": "デンマーク", "nl": "Denemarken", "oc": "Danemarc", "pt_BR": "Dinamarca", "ru": "Дания", "sk": "Dánsko", "sq": "Danimarka", "uk": "Данія", "zh_TW": "丹麥" }, "cities": [ { "city": "Copenhagen", "languages": { "co": "Copenaghen", "de": "Kopenhagen", "dsb": "Kopenhagen", "el": "Κοπεγχάγη", "fr": "Copenhague", "hsb": "Kopenhagen", "it": "Copenaghen", "ja": "コペンハーゲン", "nl": "Kopenhagen", "oc": "Copenaga", "pt_BR": "Copenhague", "ru": "Копенгаген", "sk": "Kodaň", "sq": "Kopenhagen", "uk": "Копенгаген", "zh_TW": "哥本哈根" } } ] }, { "countryCode": "fi", "languages": { "co": "Finlandia", "cy": "Y Ffindir", "de": "Finnland", "dsb": "Finska", "el": "Φινλανδία", "fr": "Finlande", "hsb": "Finska", "it": "Finlandia", "ja": "フィンランド", "oc": "Finlàndia", "pt_BR": "Finlândia", "ru": "Финляндия", "sk": "Fínsko", "sq": "Finlanda", "uk": "Фінляндія", "zh_TW": "芬蘭" }, "cities": [ { "city": "Helsinki", "languages": { "el": "Ελσίνκι", "ja": "ヘルシンキ", "pt_BR": "Helsinque", "ru": "Хельсинки", "uk": "Гельсінкі", "zh_TW": "赫爾辛基" } } ] }, { "countryCode": "fr", "languages": { "co": "Francia", "cy": "Ffrainc", "de": "Frankreich", "dsb": "Francojska", "el": "Γαλλία", "hsb": "Francoska", "it": "Francia", "ja": "フランス", "nl": "Frankrijk", "oc": "França", "pt_BR": "França", "ru": "Франция", "sk": "Francúzsko", "sq": "Francë", "uk": "Франція", "zh_TW": "法國" }, "cities": [ { "city": "Paris", "languages": { "co": "Parighji", "el": "Παρίσι", "it": "Parigi", "ja": "パリ", "nl": "Parijs", "oc": "París", "ru": "Париж", "sk": "Paríž", "uk": "Париж", "zh_TW": "巴黎" } } ] }, { "countryCode": "de", "languages": { "co": "Germania", "cy": "Yr Almaen", "de": "Deutschland", "dsb": "Nimska", "el": "Γερμανία", "fr": "Allemagne", "hsb": "Němska", "it": "Germania", "ja": "ドイツ", "nl": "Duitsland", "oc": "Alemanha", "pt_BR": "Alemanha", "ru": "Германия", "sk": "Nemecko", "sq": "Gjermania", "uk": "Німеччина", "zh_TW": "德國" }, "cities": [ { "city": "Dusseldorf", "languages": { "co": "Düsseldorf", "cy": "Düsseldorf", "de": "Düsseldorf", "dsb": "Düsseldorf", "el": "Ντύσσελντορφ", "en": "Düsseldorf", "en_CA": "Düsseldorf", "en_GB": "Düsseldorf", "fr": "Düsseldorf", "hsb": "Düsseldorf", "it": "Düsseldorf", "ja": "デュッセルドルフ", "nl": "Düsseldorf", "oc": "Düsseldorf", "pt_BR": "Düsseldorf", "ru": "Дюссельдорф", "sk": "Düsseldorf", "sq": "Dyzeldorfi", "uk": "Дюссельдорф", "zh_TW": "杜塞爾多夫" } }, { "city": "Frankfurt", "languages": { "co": "Francuforte nant'à u Menu", "cy": "Frankfurt am Main", "de": "Frankfurt am Main", "dsb": "Frankfurt nad Mainom", "el": "Φραγκφούρτη", "en": "Frankfurt am Main", "fr": "Francfort-sur-le-Main", "hsb": "Frankobrod nad Mohanom", "it": "Francoforte sul Meno", "ja": "フランクフルト・アム・マイン", "nl": "Frankfurt am Main", "oc": "Francfòrt de Men", "ru": "Франкфурт-на-Майне", "sk": "Frankfurt nad Mohanom", "sq": "Frankfurti mbi Main", "uk": "Франкфурт-на-Майні", "zh_TW": "美茵河畔法蘭克福" } } ] }, { "countryCode": "hk", "languages": { "cy": "Hong Cong", "de": "Hongkong", "el": "Χονγκ Κονγκ", "ja": "香港", "nl": "Hongkong", "ru": "Гонконг", "sk": "Hongkong", "uk": "Гонконг", "zh_TW": "香港" }, "cities": [] }, { "countryCode": "hu", "languages": { "co": "Ungheria", "cy": "Hwngari", "de": "Ungarn", "dsb": "Hungorska", "el": "Ουγγαρία", "fr": "Hongrie", "hsb": "Madźarska", "it": "Ungheria", "ja": "ハンガリー", "nl": "Hongarije", "oc": "Ongria", "pt_BR": "Hungria", "ru": "Венгрия", "sk": "Maďarsko", "sq": "Hungaria", "uk": "Угорщина", "zh_TW": "匈牙利" }, "cities": [ { "city": "Budapest", "languages": { "el": "Βουδαπέστη", "ja": "ブダペスト", "nl": "Boedapest", "oc": "Budapèst", "pt_BR": "Budapeste", "ru": "Будапешт", "sk": "Budapešť", "uk": "Будапешт", "zh_TW": "布達佩斯" } } ] }, { "countryCode": "ie", "languages": { "co": "Irlanda", "cy": "Iwerddon", "de": "Irland", "dsb": "Irska", "el": "Δημοκρατία της Ιρλανδίας", "en_CA": "Republic of Ireland", "fr": "Irlande", "hsb": "Irska", "it": "Irlanda", "ja": "アイルランド", "nl": "Ierland", "oc": "Republica d'Irlanda", "pt_BR": "República da Irlanda", "ru": "Ирландия", "sk": "Írsko", "sq": "Republika e Irlandës", "uk": "Ірландія", "zh_TW": "愛爾蘭" }, "cities": [ { "city": "Dublin", "languages": { "co": "Dublinu", "cy": "Dulyn", "el": "Δουβλίνο", "it": "Dublino", "ja": "ダブリン", "ru": "Дублин", "sq": "Dublini", "uk": "Дублін", "zh_TW": "都柏林" } } ] }, { "countryCode": "it", "languages": { "co": "Italia", "cy": "Yr Eidal", "de": "Italien", "dsb": "Italska", "el": "Ιταλία", "fr": "Italie", "hsb": "Italska", "it": "Italia", "ja": "イタリア", "nl": "Italië", "oc": "Itàlia", "pt_BR": "Itália", "ru": "Италия", "sk": "Taliansko", "sq": "Italia", "uk": "Італія", "zh_TW": "義大利" }, "cities": [ { "city": "Milan", "languages": { "co": "Milanu", "de": "Mailand", "el": "Μιλάνο", "hsb": "Mailand", "it": "Milano", "ja": "ミラノ", "nl": "Milaan", "pt_BR": "Milão", "ru": "Милан", "sk": "Miláno", "sq": "Milano", "uk": "Мілан", "zh_TW": "米蘭" } } ] }, { "countryCode": "jp", "languages": { "co": "Giapponu", "dsb": "Japańska", "el": "Ιαπωνία", "fr": "Japon", "hsb": "Japanska", "it": "Giappone", "ja": "日本", "oc": "Japon", "pt_BR": "Japão", "ru": "Япония", "sk": "Japonsko", "sq": "Japonia", "uk": "Японія", "zh_TW": "日本" }, "cities": [ { "city": "Tokyo", "languages": { "de": "Tokio", "el": "Τόκυο", "ja": "東京", "nl": "Tokio", "oc": "Tòquio", "ru": "Токио", "sk": "Tokio", "uk": "Токіо-сіті", "zh_TW": "東京" } } ] }, { "countryCode": "lu", "languages": { "co": "Lussemburgu", "cy": "Lwcsembwrg", "de": "Luxemburg", "dsb": "Luxemburgska", "el": "Λουξεμβούργο", "hsb": "Luxemburgska", "it": "Lussemburgo", "ja": "ルクセンブルク", "nl": "Luxemburg", "oc": "Luxemborg", "pt_BR": "Luxemburgo", "ru": "Люксембург", "sk": "Luxembursko", "sq": "Luksemburgu", "uk": "Люксембург", "zh_TW": "盧森堡" }, "cities": [] }, { "countryCode": "md", "languages": { "co": "Moldavia", "cy": "Moldofa", "de": "Republik Moldau", "dsb": "Moldawska", "el": "Μολδαβία", "fr": "Moldavie", "hsb": "Moldawska", "it": "Moldavia", "ja": "モルドバ", "nl": "Moldavië", "oc": "Moldàvia", "pt_BR": "Moldávia", "ru": "Молдавия", "sk": "Moldavsko", "sq": "Moldavia", "uk": "Молдова", "zh_TW": "摩爾多瓦" }, "cities": [ { "city": "Chisinau", "languages": { "co": "Chişinău", "cy": "Chişinău", "de": "Chișinău", "el": "Κισινάου", "en": "Chișinău", "en_CA": "Chișinău", "en_GB": "Chișinău", "fr": "Chișinău", "hsb": "Kišinjow", "it": "Chișinău", "ja": "キシナウ", "oc": "Chişinău", "pt_BR": "Chişinău", "ru": "Кишинёв", "sk": "Kišiňov", "sq": "Kishinau", "uk": "Кишинів", "zh_TW": "奇西瑙" } } ] }, { "countryCode": "nl", "languages": { "co": "Paesi Bassi", "cy": "Yr Iseldiroedd", "de": "Niederlande", "dsb": "Nižozemska", "el": "Ολλανδία", "fr": "Pays-Bas", "hsb": "Nižozemska", "it": "Paesi Bassi", "ja": "オランダ", "nl": "Nederland", "oc": "Païses Basses", "pt_BR": "Países Baixos", "ru": "Нидерланды", "sk": "Holandsko", "sq": "Holanda", "uk": "Нідерланди", "zh_TW": "荷蘭" }, "cities": [ { "city": "Amsterdam", "languages": { "el": "Άμστερνταμ", "ja": "アムステルダム", "pt_BR": "Amsterdã", "ru": "Амстердам", "sq": "Amsterdami", "uk": "Амстердам", "zh_TW": "阿姆斯特丹" } } ] }, { "countryCode": "nz", "languages": { "co": "Nova Zilanda", "cy": "Seland Newydd", "de": "Neuseeland", "dsb": "Nowoseelandska", "el": "Νέα Ζηλανδία", "fr": "Nouvelle-Zélande", "hsb": "Nowoseelandska", "it": "Nuova Zelanda", "ja": "ニュージーランド", "nl": "Nieuw-Zeeland", "oc": "Nòva Zelanda", "pt_BR": "Nova Zelândia", "ru": "Новая Зеландия", "sk": "Nový Zéland", "sq": "Zelanda e Re", "uk": "Нова Зеландія", "zh_TW": "紐西蘭" }, "cities": [ { "city": "Auckland", "languages": { "el": "Ώκλαντ", "ja": "オークランド", "ru": "Окленд", "sq": "Okland", "uk": "Окленд", "zh_TW": "奧克蘭" } } ] }, { "countryCode": "no", "languages": { "co": "Nurvegia", "cy": "Norwy", "de": "Norwegen", "dsb": "Norwegska", "el": "Νορβηγία", "fr": "Norvège", "hsb": "Norwegska", "it": "Norvegia", "ja": "ノルウェー", "nl": "Noorwegen", "oc": "Norvègia", "pt_BR": "Noruega", "ru": "Норвегия", "sk": "Nórsko", "sq": "Norvegjia", "uk": "Норвегія", "zh_TW": "挪威" }, "cities": [ { "city": "Oslo", "languages": { "co": "Oslu", "el": "Όσλο", "ja": "オスロ", "oc": "Òslo", "ru": "Осло", "uk": "Осло", "zh_TW": "奧斯陸" } } ] }, { "countryCode": "pl", "languages": { "co": "Polonia", "cy": "Gwlad Pwyl", "de": "Polen", "dsb": "Pólska", "el": "Πολωνία", "fr": "Pologne", "hsb": "Pólska", "it": "Polonia", "ja": "ポーランド", "nl": "Polen", "oc": "Polonha", "pt_BR": "Polônia", "ru": "Польша", "sk": "Poľsko", "sq": "Polonia", "uk": "Польща", "zh_TW": "波蘭" }, "cities": [ { "city": "Warsaw", "languages": { "co": "Varsavia", "de": "Warschau", "dsb": "Waršawa", "el": "Βαρσοβία", "fr": "Varsovie", "hsb": "Waršawa", "it": "Varsavia", "ja": "ワルシャワ", "nl": "Warschau", "oc": "Varsòvia", "pt_BR": "Varsóvia", "ru": "Варшава", "sk": "Varšava", "sq": "Varshava", "uk": "Варшава", "zh_TW": "華沙" } } ] }, { "countryCode": "pt", "languages": { "co": "Portugallu", "cy": "Portiwgal", "dsb": "Portugalska", "el": "Πορτογαλία", "hsb": "Portugalska", "it": "Portogallo", "ja": "ポルトガル", "ru": "Португалия", "sk": "Portugalsko", "sq": "Portugalia", "uk": "Португалія", "zh_TW": "葡萄牙" }, "cities": [ { "city": "Lisbon", "languages": { "co": "Lisbona", "de": "Lissabon", "dsb": "Lisabon", "el": "Λισαβόνα", "fr": "Lisbonne", "hsb": "Lisabon", "it": "Lisbona", "ja": "リスボン", "nl": "Lissabon", "oc": "Lisbona", "pt_BR": "Lisboa", "ru": "Лиссабон", "sk": "Lisabon", "sq": "Lisbona", "uk": "Лісабон", "zh_TW": "里斯本" } } ] }, { "countryCode": "ro", "languages": { "cy": "Rwmania", "de": "Rumänien", "dsb": "Rumuńska", "el": "Ρουμανία", "fr": "Roumanie", "hsb": "Rumunska", "ja": "ルーマニア", "nl": "Roemenië", "pt_BR": "Romênia", "ru": "Румыния", "sk": "Rumunsko", "sq": "Rumania", "uk": "Румунія", "zh_TW": "羅馬尼亞" }, "cities": [ { "city": "Bucharest", "languages": { "co": "Bucarest", "cy": "Bwcarést", "de": "Bukarest", "dsb": "Bukarest", "el": "Βουκουρέστι", "fr": "Bucarest", "hsb": "Bukarest", "it": "Bucarest", "ja": "ブカレスト", "nl": "Boekarest", "oc": "Bucarèst", "pt_BR": "Bucareste", "ru": "Бухарест", "sk": "Bukurešť", "sq": "Bukureshti", "uk": "Бухарест", "zh_TW": "布加勒斯特" } } ] }, { "countryCode": "rs", "languages": { "de": "Serbien", "dsb": "Serbiska", "el": "Σερβία", "fr": "Serbie", "hsb": "Serbiska", "ja": "セルビア", "nl": "Servië", "pt_BR": "Sérvia", "ru": "Сербия", "sk": "Srbsko", "uk": "Сербія", "zh_TW": "塞爾維亞" }, "cities": [ { "city": "Belgrade", "languages": { "co": "Belgradu", "cy": "Beograd", "de": "Belgrad", "dsb": "Běłogrod", "el": "Βελιγράδι", "hsb": "Běłohród", "it": "Belgrado", "ja": "ベオグラード", "nl": "Belgrado", "oc": "Belgrad", "pt_BR": "Belgrado", "ru": "Белград", "sk": "Belehrad", "sq": "Beogradi", "uk": "Белград", "zh_TW": "貝爾格勒" } } ] }, { "countryCode": "sg", "languages": { "co": "Singaporia", "cy": "Singapôr", "de": "Singapur", "dsb": "Singapur", "el": "Σιγκαπούρη", "fr": "Singapour", "hsb": "Singapur", "ja": "シンガポール", "oc": "Singapor", "pt_BR": "Singapura", "ru": "Сингапур", "sk": "Singapur", "sq": "Singapori", "uk": "Сінгапур", "zh_TW": "新加坡" }, "cities": [] }, { "countryCode": "es", "languages": { "co": "Spagna", "cy": "Sbaen", "de": "Spanien", "dsb": "Špańska", "el": "Ισπανία", "fr": "Espagne", "hsb": "Španiska", "it": "Spagna", "ja": "スペイン", "nl": "Spanje", "oc": "Espanha", "pt_BR": "Espanha", "ru": "Испания", "sk": "Španielsko", "sq": "Spanja", "uk": "Іспанія", "zh_TW": "西班牙" }, "cities": [ { "city": "Madrid", "languages": { "co": "Madridi", "el": "Μαδρίτη", "ja": "マドリード", "pt_BR": "Madri", "ru": "Мадрид", "sq": "Madridi", "uk": "Мадрид", "zh_TW": "馬德里" } } ] }, { "countryCode": "se", "languages": { "co": "Svezia", "de": "Schweden", "dsb": "Šwedska", "el": "Σουηδία", "fr": "Suède", "hsb": "Šwedska", "it": "Svezia", "ja": "スウェーデン", "nl": "Zweden", "oc": "Suècia", "pt_BR": "Suécia", "ru": "Швеция", "sk": "Švédsko", "sq": "Suedia", "uk": "Швеція", "zh_TW": "瑞典" }, "cities": [ { "city": "Gothenburg", "languages": { "co": "Göteborg", "cy": "Göteborg", "de": "Göteborg", "dsb": "Göteborg", "el": "Γκέτεμποργκ", "fr": "Göteborg", "hsb": "Göteborg", "it": "Göteborg", "ja": "ヨーテボリ", "nl": "Göteborg", "oc": "Göteborg", "ru": "Гётеборг", "sk": "Göteborg", "sq": "Göteborg", "uk": "Гетеборг", "zh_TW": "哥特堡" } }, { "city": "Malmö", "languages": { "el": "Μάλμε", "ja": "マルメ", "ru": "Мальмё", "uk": "Мальме", "zh_TW": "馬爾摩" } }, { "city": "Stockholm", "languages": { "co": "Stoccolma", "el": "Στοκχόλμη", "it": "Stoccolma", "ja": "ストックホルム", "oc": "Estocòlme", "pt_BR": "Estocolmo", "ru": "Стокгольм", "sk": "Štokholm", "sq": "Stokholmi", "uk": "Стокгольм", "zh_TW": "斯德哥爾摩" } } ] }, { "countryCode": "ch", "languages": { "co": "Svizzera", "cy": "Y Swistir", "de": "Schweiz", "dsb": "Šwicaŕska", "el": "Ελβετία", "fr": "Suisse", "hsb": "Šwicarska", "it": "Svizzera", "ja": "スイス", "nl": "Zwitserland", "oc": "Soïssa", "pt_BR": "Suíça", "ru": "Швейцария", "sk": "Švajčiarsko", "sq": "Zvicër", "uk": "Швейцарія", "zh_TW": "瑞士" }, "cities": [ { "city": "Zurich", "languages": { "cy": "Zürich", "de": "Zürich", "dsb": "Zürich", "el": "Ζυρίχη", "en": "Zürich", "hsb": "Zürich", "it": "Zurigo", "ja": "チューリッヒ", "nl": "Zürich", "oc": "Zuric", "pt_BR": "Zurique", "ru": "Цюрих", "sk": "Zürich", "sq": "Cyrih", "uk": "Цюрих", "zh_TW": "蘇黎世" } } ] }, { "countryCode": "gb", "languages": { "co": "Regnu Unitu", "cy": "y Deyrnas Unedig", "de": "Vereinigtes Königreich", "dsb": "Zjadnośone kralojstwo", "el": "Ηνωμένο Βασίλειο", "en": "United Kingdom", "en_CA": "United Kingdom", "en_GB": "United Kingdom", "fr": "Royaume-Uni", "hsb": "Zjednoćene kralestwo", "it": "Regno Unito", "ja": "イギリス", "nl": "Verenigd Koninkrijk", "oc": "Reialme Unit", "pt_BR": "Reino Unido", "ru": "Великобритания", "sk": "Spojené kráľovstvo", "sq": "Britania e Madhe", "uk": "Велика Британія", "zh_TW": "英國" }, "cities": [ { "city": "London", "languages": { "co": "Londra", "cy": "Llundain", "el": "Λονδίνο", "fr": "Londres", "it": "Londra", "ja": "ロンドン", "nl": "Londen", "oc": "Londres", "pt_BR": "Londres", "ru": "Лондон", "sk": "Londýn", "sq": "Londër", "uk": "Лондон", "zh_TW": "倫敦" } }, { "city": "Manchester", "languages": { "cy": "Manceinion", "el": "Μάντσεστερ", "ja": "マンチェスター", "ru": "Манчестер", "uk": "Манчестер", "zh_TW": "曼徹斯特" } } ] }, { "countryCode": "us", "languages": { "co": "Stati Uniti d'America", "cy": "Unol Daleithiau America", "de": "Vereinigte Staaten", "dsb": "Zjadnośone staty Ameriki", "el": "Ηνωμένες Πολιτείες Αμερικής", "en": "United States of America", "en_CA": "United States", "en_GB": "United States of America", "fr": "États-Unis", "hsb": "Zjednoćene staty Ameriki", "it": "USA", "ja": "アメリカ合衆国", "nl": "Verenigde Staten van Amerika", "oc": "Estats Units d'America", "pt_BR": "Estados Unidos", "ru": "США", "sk": "Spojené štáty americké", "sq": "Shtetet e Bashkuara të Amerikës", "uk": "Сполучені Штати Америки", "zh_TW": "美國" }, "cities": [ { "city": "Atlanta, GA", "languages": { "co": "Atlanta", "cy": "Atlanta", "de": "Atlanta", "el": "Ατλάντα", "en": "Atlanta", "fr": "Atlanta", "it": "Atlanta", "ja": "アトランタ", "nl": "Atlanta", "oc": "Atlanta", "pt_BR": "Atlanta", "ru": "Атланта", "sk": "Atlanta", "sq": "Atlanta", "uk": "Атланта", "zh_TW": "亞特蘭大" } }, { "city": "Chicago, IL", "languages": { "co": "Chicago", "cy": "Chicago", "de": "Chicago", "dsb": "Chicago", "el": "Σικάγο", "en": "Chicago", "en_CA": "Chicago", "en_GB": "Chicago", "fr": "Chicago", "hsb": "Chicago", "it": "Chicago", "ja": "シカゴ", "nl": "Chicago", "oc": "Chicago", "pt_BR": "Chicago", "ru": "Чикаго", "sk": "Chicago", "sq": "Chicago", "uk": "Чикаго", "zh_TW": "芝加哥" } }, { "city": "Dallas, TX", "languages": { "co": "Dallas", "cy": "Dallas, Texas", "de": "Dallas", "el": "Ντάλας", "en": "Dallas", "en_CA": "Dallas", "en_GB": "Dallas", "fr": "Dallas", "hsb": "Dallas", "it": "Dallas", "ja": "ダラス", "nl": "Dallas", "oc": "Dallas", "pt_BR": "Dallas", "ru": "Даллас", "sk": "Dallas", "sq": "Dallas", "uk": "Даллас", "zh_TW": "達拉斯" } }, { "city": "Denver, CO", "languages": { "co": "Denver", "cy": "Denver, Colorado", "de": "Denver", "el": "Ντένβερ", "en": "Denver", "en_CA": "Denver", "en_GB": "Denver", "fr": "Denver", "it": "Denver", "ja": "デンヴァー", "nl": "Denver", "oc": "Denver", "pt_BR": "Denver", "ru": "Денвер", "sk": "Denver", "sq": "Denver", "uk": "Денвер", "zh_TW": "丹佛" } }, { "city": "Los Angeles, CA", "languages": { "co": "Los Angeles", "cy": "Los Angeles", "de": "Los Angeles", "dsb": "Los Angeles", "el": "Λος Άντζελες", "en": "Los Angeles", "en_CA": "Los Angeles", "en_GB": "Los Angeles", "fr": "Los Angeles", "hsb": "Los Angeles", "it": "Los Angeles", "ja": "ロサンゼルス", "nl": "Los Angeles", "oc": "Los Angeles", "pt_BR": "Los Angeles", "ru": "Лос-Анджелес", "sk": "Los Angeles", "sq": "Los Axhelos", "uk": "Лос-Анджелес", "zh_TW": "洛杉磯" } }, { "city": "Miami, FL", "languages": { "co": "Miami", "cy": "Miami", "de": "Miami", "el": "Μαϊάμι", "en": "Miami", "en_CA": "Miami", "en_GB": "Miami", "fr": "Miami", "it": "Miami", "ja": "マイアミ", "nl": "Miami", "oc": "Miami", "pt_BR": "Miami", "ru": "Майами", "sk": "Miami", "sq": "Miami", "uk": "Маямі", "zh_TW": "邁阿密" } }, { "city": "New York, NY", "languages": { "co": "New York", "cy": "Dinas Efrog Newydd", "de": "New York City", "dsb": "New York City", "el": "Νέα Υόρκη", "en": "New York City", "en_CA": "New York City", "en_GB": "New York City", "fr": "New York", "hsb": "New York City", "it": "New York", "ja": "ニューヨーク", "nl": "New York", "oc": "Nòva York", "pt_BR": "Nova Iorque", "ru": "Нью-Йорк", "sk": "New York", "sq": "New York City", "uk": "Нью-Йорк", "zh_TW": "紐約" } }, { "city": "Phoenix, AZ", "languages": { "co": "Phoenix", "cy": "Phoenix", "de": "Phoenix", "el": "Φοίνιξ", "en": "Phoenix", "fr": "Phoenix", "it": "Phoenix", "ja": "フェニックス", "nl": "Phoenix", "oc": "Phoenix", "pt_BR": "Phoenix", "ru": "Финикс", "sk": "Phoenix", "sq": "Phoenix", "uk": "Фінікс", "zh_TW": "鳳凰城" } }, { "city": "Raleigh, NC", "languages": { "cy": "Raleigh, Gogledd Carolina", "de": "Raleigh", "el": "Ράλεϊ", "en": "Raleigh", "en_CA": "Raleigh, North Carolina", "en_GB": "Raleigh", "fr": "Raleigh", "it": "Raleigh", "ja": "ローリー", "nl": "Raleigh", "oc": "Raleigh", "pt_BR": "Raleigh", "ru": "Роли", "sk": "Raleigh", "sq": "Raleigh", "uk": "Ралі" } }, { "city": "Salt Lake City, UT", "languages": { "co": "Salt Lake City", "cy": "Salt Lake City", "de": "Salt Lake City", "el": "Σολτ Λέικ Σίτι", "en": "Salt Lake City", "en_CA": "Salt Lake City", "en_GB": "Salt Lake City", "fr": "Salt Lake City", "it": "Salt Lake City", "ja": "ソルトレイクシティ", "nl": "Salt Lake City", "oc": "Salt Lake City", "pt_BR": "Salt Lake City", "ru": "Солт-Лейк-Сити", "sk": "Salt Lake City", "sq": "Salt Lake City", "uk": "Солт-Лейк-Сіті", "zh_TW": "鹽湖城" } }, { "city": "San Jose, CA", "languages": { "cy": "San Jose, Califfornia", "de": "San José", "el": "Σαν Χοσέ", "en": "San Jose", "en_CA": "San Jose, California", "en_GB": "San Jose", "fr": "San José", "it": "San Jose", "ja": "サンノゼ", "nl": "San Jose", "oc": "San José", "pt_BR": "São José", "ru": "Сан-Хосе", "sk": "San José", "sq": "San Hose", "uk": "Сан-Хосе", "zh_TW": "聖荷西" } }, { "city": "Seattle, WA", "languages": { "co": "Seattle", "cy": "Seattle", "de": "Seattle", "el": "Σιάτλ", "en": "Seattle", "en_CA": "Seattle", "en_GB": "Seattle", "fr": "Seattle", "it": "Seattle", "ja": "シアトル", "nl": "Seattle", "oc": "Seattle", "pt_BR": "Seattle", "ru": "Сиэтл", "sk": "Seattle", "sq": "Seattle", "uk": "Сіетл", "zh_TW": "西雅圖" } } ] } ]mozilla-vpn-client-2.2.0/translations/servers.qrc000066400000000000000000000001371404202232700221550ustar00rootroot00000000000000 servers.json mozilla-vpn-client-2.2.0/version.pri000066400000000000000000000004121404202232700174310ustar00rootroot00000000000000# This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. !defined(VERSION, var):VERSION = 2.2.0 DBUS_PROTOCOL_VERSION = 1 mozilla-vpn-client-2.2.0/wasm/000077500000000000000000000000001404202232700162025ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/wasm/favicon.ico000066400000000000000000000701201404202232700203230ustar00rootroot00000000000000 h6   [F(    -+.+&$$cccaaa$##,++VUUfee'&&&$$! 533'&&'%%:99tss{zz$##lllfeefeeaaarqqTSSVUUccc,++onnedd;::&$$-,,A@@! ,++-,,TSTfee%$$,++rqqlll'%%3-3/ ( @   6,.I:=#OqyyyyyyyyyyyyyyyyyypM("#]IMB58$ d_/'( & !" 1(* )#$h_("#X0//~}}+**M433-,,py&$$srs y(''^]^! yWVVdcdyFEE977! y1///-.HGG-,,y4330//.,,,++YXX+**y655977866866877877~}}y$##yxxdccy&%%lkkzyyWVVyeddjiiHFGKJJKIJKIJKJJKJJ^]^srsyCBB877" IHH0//yFDD0..GFF433y,+++**XVW.--(''&$$yzyyy+**jiiy,++eddylkkyxxyFDD1//q]CBB433N#&%%$###sd% ! !jqsh" ##]Y5,.)#$PNG  IHDR\rfZIDATxw|չ>33UmKnclcL)!_jroB ˅ܐ) ɥ@LB)^pظH$KV]im1;3;+|cY=my{C00c\7a >ݷ5M0$ H) x^iy ,-,<Ѵ=ϖ\A2!XNPz`SP*J(%2Ά`@ eP T`R93Θc @Ⓦ X ,# KAA11,a9|ph8cr)缀sV9/@8s^~ !A$ >PNRB hkm )cO")|BE&B[ džD39g8#sB=dA'@:!')G*D]rS r8 @}d($ rF O#mD&PB@1. br|tp>D>?!X F@j DQb9?u9= y92UuH"*(2YvxFsrx l|4[GfCЇ !R[m8.@|1?P@D]"%(I`mZ(TfbT`.AzawvTd<G@5>%h:7 d-㜏y? F9!vux ~~['/ ,-3A5Wrl`1pBH(Ho8] 3#S/ Twz[zYX i!`ZIqp3Ƽz Z_Q B9gHƏRG2CT鰻.lrνPaLEf<5,7|Ƀy!+!0Sh{S:aҒ&?QJ\- XB>'OjmrdcYPsd9:[AeUhj]nH"-,ZN^ ✟~[RNZ(lL(}v$%T ZFn(/R$DQ>[pTj$ 101 AŲ E4hH/ "W_:tu'3_  `A~ Y)!YPHmEA !2"h:cۿϘu &?H Y@dL %vD u@dͯhQCH:ʘ<)Jb@Ǝ A ;TCѷepCАQA{@:H -HXSJ-j/bŸ\>@a9?TH !_ ri{]?c0dA`M ˡ:^gұR"$vViFVi11pj8u J)@~foim i?$V!8B6j, q/2߭F&|+t&yqX $>Q3qN \B@{\@v0$ӕd9|NWWHNB(_/$hAYAwH! PvRRZTD2& >,<Ӊ8=k!8BHZV@%vz$轝gMR8zoNrxFww p(r6T,*6e7`PXYCВ8M)_wO#pB HJڟ  ᜏu/G{#D&n_ AN=aP h dPINo\`P~B a 2 zfB Ћzi`' \9u O?AO'd݋9gV@RgQήieqȵO}[Ia %A8<M$xܰ@$3YvK#9M*>~e_5(B- $%"66Zfl9>g~zB >bu5h(<7 TidM9CtƢO d0XLqC :HJrx l >=g +YaIn@ 6clN;޷ pLeYF8F(B8F8{Cv JD/ӂA(B 1_' @ m_rU(,(@yy9$QDQQ!A9~w MMM2S.4%j̾~2pu7@=. w^BA#|eS1Ũg1c1nX >N Mg9pmmmhhhpQl߾ǏGCc#O6?m0O88-LzAgz BWW{|z;r+|8;ƍE,‚ 0k, JiJf|qI &L " |&aZZ 3vA,LJh眫~P,^  c\7>udC#cw9yYK1v9,i KJPRZfÞ{ꫯbqdW@]v"'s0PB\ $EJ"D^x?N1ӧ[EK`᠂~˅ '`0wpP___O =doPwӎ!6QC&o hXD^dC6G>88c(++rnfL!$7 ,tbE9s-_?ض};0TAnK[BXN Ad&z:oOuG?7꣘gy&>r(92'{s6L6 χ 董 X0|I;\N':tPo#"{\7.E:fVɅ?#WL~Iq٥+.(f]9GII /8p,8}Iۜ[*6C!EIaԂ0[~5]ﻜp~I&j 9$IYgI&b}h>u*2vZ@Bz~kJV'BId`@JK9~^].v-;PRRG?e˗C%1Dv }ڟ/w]-3 hv}9/8 ,?&ڐ1$_w}7-s /Z~ MHLm,AV8ιHpj L8g\3?7OvWFAaAs /`Q#!V uQH@`99/uk]7Fq1pwň~m?]VR" ~a\.q!|ԇ37v n?f_Vʕ+!B sؙm X"1 @BpeŸ8p:[0Ⴜ~/noaܹbnÂ}Գn8p[l֭ކnp80yd̟?g͚*PJ #"Λŋ/K;8g g @ߘM kZUƍ?ߐJ):><ߣT "A P0 $!=<3sb…pim wߠ~5h` *%`;g**+uG!x-!E/E݉z//pX 8@}NAm]Z,@F"w<2vc̙<'λqd%'' Aކ'~6Ac cƎg2KdMkw]F['߬ Ԅ?0PTXYgbZ+3 ReݟXA@]}=~8QWeYӳ|-rHf_QQr]ytzDl݊_~YRL: PՓZ?,01HfP~BM ǭx{z۷4Oh@5llmL0#$`@@.,ϭ'Choo7"jTD͉=΍LB$,DAaa1JI}٩<+³ y hokU+U\=`clƜHՓ#AYVPO>0oB-Y~m=_|t[E\| 9.E]42/D]] |Ȧ`2{'$K?{_9v]1̜5 +V dr Grh/)ĪkV [VƀO?EgggCAKr<FL_bvuyفt. 7r3Od$~We$^{ ̝+ Xo:,G7 @ oU_~O֎͛?ѝq1ӦދQp$~&C\q! ؽgw4)icǧ3h%/d0F?m~`hjj2$'ŗ\}{ `L:rjr +e\RJ}{(u{ˬ`j5T8صs'*++3$EEQ5]b<DZw>aPd<]%x$SQV]}5.TVUNNǏwy]ݾgYd'> ؅l$c&!B::;kŋuGH.^ &}o&+ӄA>ęӧƛnėt!wJpal޼@j9 }$epjt_Ï{._N'jUX:lܸMMMh@ Eaa! 1}h\xbTVUbHB@I~~:D R|̱$L k+;446⥗^Œ΂ۭ?E8m6=<=gP[WcG3r$YBEEƍ#vmDR;z DQJH=5x ˽`&,t3Pr~ M|uȌa]8؈&&|B `QV9sLhe^EsƏkBf`0Xی &. reQv3=߾3fiW%R{ſ@0QXAu`m@sx㜁1ѣFիVAԹn%Bݍgyh,Fib2p7L&bl]QUʕWRu m6^kxWKZ8 stZ<}l@k˩韸 (}Xr%0Rݻv'Dk{{䚲T&*|e8%3#}rU\m%PJQ[SG~{HyET2PȽoD^̨Qʫ\ryqP ,eyۯ '+Вۀ5N=>Ϙ"?rՕS[ZأWA(\Rw75O~K[:eŗglXwmh/sl_>^0cJ͟`8viy$U&yՉMRQɮ/{9ց 4W>7P9@ qǏQ=ӦMCEn+"DA@ @:/>}ڂ/ge`+.,g/Hg(1{.-_?k[kS`͆RL4 +.]&ڹ>#446BfJxo4DŽ5,a@Aa!Ai|xg'@)$K,5^Yg0ea|TTT.^ݻvᥗ^®ۿW9?'pgs(6 FhoǶm۰fλhik!4VD%mV"uvμyXyJ?>*++!b*H&bY3pW;xp8"Ps 2c<$/=R,oIFӻj ÎE ƛnEv9̂!^/N66b̘1%ux衇p1PJQ9r$-Z9sQ><}nI9G0Dزe ^kرs'N@'I~-OV&a9|_aÇiwaǎxŗnzD,n$rR|IzO%u :Ǎ[o+W]rW"ɪڵC?ñ1va)={/X 'bȑ(**7O`0SN}݋ǎ!/iyQ? ;f4n᫸PYUUJ]]]x? nE(4s3vwEOԿ?h @;y$Iwߍ Ds qTd]Ȳ Q`I(// QRZ*TVUN3zO_gg'N5B]]ԄCӃO,_Oy{pιBŜ?ǏzFs+Ɩz +Gr<6I+½߽ƏD ._Zg?ZPA uL1 ǎ׀3At8RMG 8=]5VᎻĨQrԶ;0m4<㨩牖@.՟]=˅}QV6hHkuZuR1~]Eз7T/a&n7PXXhWvЃ` )owZy |2r:pۭ஻FAa&^ĄP>?uiHɖ&q^ϒ\},ˍ7pXUt%n<'r.=Gt-qZ~((,4v@&R# f~ͪ lQTP禛t_9 pqĴSe۝;YD>t&ÞR/|^X~7 fM (5bZ`lCc v7pn^2N\{5T]mP\^r1W^'~#ǎERqY` tMY"?3/K.dUq?pzi%9сCCC#PJ1tFU #ˣ!Fsr=?ݛb|_ۖX$;G)%\gmDzQQ((.*ƨѣPQY Hd+**Wo*>457TG'u9\K /o[Agg'cV$ATWWsŗ\fru]yÏA'w`aTeV\ fuAihh;o m]9?*fR cXbΞ3ǰyylRŗ_Y@6/daرA)5ũ-^}e-طo?|==LoYFl޺vիb |͘:m(s_3ΜMlpkTRDӧ) #_dz<]w9 ͻcBcǮ]ص{7^ye-/[n3g4Ě+oCk[[SP^V(Jxpf r0㪫ĕW^$| J)90q1ȌC>ב#vuӽ{mV >'L vҊ7A D8 'c\;q麅R<ȯO]CxK.ib`d#mيbL;cnGn6l@ss3Ɍ<\U9,Øqt|_Uuux?ŋ/u;}ͧNap:1c!$vЈM6ENUE(3E9w9GȑkJ)O?}o.Wmצ&MUW_[6Cu"F2A PQQB҂_x5هcǕ~3.eFS-AQq1Z%`(1'" K;9(!8묳P:l!xOw\Ҫ -!o @D݉z<1aL6MD$ ߾vF~n-p/T^(jiO8{]saر8А@@tO3NǛ6o@4х*DH@w>;kt$ 3VBՅ^|qk 2|M޵KC0cߏ5נ&$a xOtqv;ƍQIk @yy9ƍ[8p6} =#SR X:\ݠ!555ذaC4]N!6]ĉ1|E $6|.`a֭h>u*{Sslܸ}!![6oƉ4ƞ`Ν?qB9Gee%<:hNNSWYP7o?zX>& ABؿo?}ݚlƕjkjQqQRZVLZ\²PךZ]wtvD7EZ{8vBؘ`3sآ(jz+#AWtvvE4yPV%C B~?#{CAtttV$6 َ5 Hz1 A6X{͗o#!L,#dݰ<C9\n7ēf&%6ncȆ)I$ȑ% z!sFN-UQQap(//`ʍJe-%S>p@KPȑ2ө-/E6jҒB7nh`8p{<;nlAĩc [ٸ [7M羺 `>NWizoQ&%g|!0̛;JEk]nq3vs_w7+جdaytY3gbr$0Y6E8p9hɒuoU00:eJa=  .Xd%3ͧ S&&J)p1]0PUU. vЋԄ19}:.p9V^eM< 8'Mu+Bͽnn6o2 ²G"3RYsDVzR{uQanFT=9 !Jz+JcL__5Ǡ^^Tq &NdǏGցeQ_1l߾^:3F;555;QA$` GɐD _e ^xN\nwEp#Fλđ#Gp@H0 ?ǥ+VU@)}rb}tnf^f ,KZ?~ǏנT%K⮻/0Z"aƩL$5K/e(஻ۓYrp{ lܸI h Ȳ _]icw9w> hIHET_u(.B|_475a}ŴKhhloY3u)n >zŞ{p%o|/^3Θ_~5:tH9ȓҔ+8v;FE]U׬œs7W׋7x@ NH⟰]f3@ ֺuXj יA_h&Okյ矣=ɸ*m0bx-ZUV΅4/Ʀ7S2v(2&Tw M2TK ^}3b1Qo+W۱mVԢݠE TUUz2,\IՓv!3b !ر};6oقoW ~Go/:^}e-_taRj#Gĥ_.Y^]!ʻ+(~T]R|q0֭Si}7V 9Ol_^ fΜe] (**Bqqqj=fxMS' 9 {[[ZQf#yXu^=hhh@kK+ L{w(,,Դw(͚5@8TddKenzۦ%Ǩȳr?9<xm;Ա:e2s=5[eʮҾ{fACfTZcǎv{6w|Gcz$л~Waяe˗`pȑHL]7uQm۷~|y=(غe+ߧ7tuH 5ᯞ4 ?tٲAwFsW^~oFԗO#P#kח^ZuA)EMM ~c8őE Ki~8(Rm['>n}*á2Df_1,Ek{~';ikmo~86n6վ${gUrInO;z="jN4'56jӟ￷!oH@Mu'5k .L} s Uқ<XtF{ i*u, )xA 'JiTSm:&qOwiXlipPJqQ@f,%I eU8}=%٩}]y)3zRҩStTVVb:/4SO]oޱU:D͇>1\fKskۧSoZg?_7YI7(NQ9mZt9 ̢ء1`gqɓ'j*=8)8|կXSl:#bϘYD[9$@$I,3Ȳ |>B(!qjBN}8?RBߏG~o²& VEy T RMm~p`۷qǙ(x xDZs.0Σ 1v %wr`(//GYY0l0B=hiiCpIq.Nբ~J) ^6~ؾs'cq'rט>CAƔx^<,ߏo/QU6f9x Tͯmsc[ ..10|L4 e())$ |a c N457#شq#v؉mhcMbQ/{976FZ!-Uk&Zaa'~z\wu;oQM9%QaQ1yd,[MMMصc'|Mlx}743)S&CA /`̘((,4UrGGYO? lۊ`0|_c5̘>PfjH÷Qu?eTNc2#xʕWbܹ(--[1 = 6Wb=''>%럪p+1㬳3j<ߏ;wᥗ^g=ދeP(yO;o.z̞sGy˚֖l޼_=TO79t}w9}nwVRTh :P' SSv%E8{Xp!/X1cǠ $'B`$r 8wy6}ǎDtڛDpX|qιree#^/ZN¨ѣ zUgã>'! "Fa򗿌sȑ#Kubd[uw~֯;v@[G;T6:S+Xɴ+oUf"bagq&WWciT]ÇvnR ݎP(P8 &AGG'N8Сؿ?jjkIE{m%$k}1i|-+1lذvh36]nT{ '"I[IԪ+xP=if͞?SNEa(((|YՅ岙۶aعc'Nԡ@4~+KDPn5+@[V}3e߻$I(1eJ" ==~~eNC!E)Dr H*Q0pwżsѝJ sGCcht,BPTX˅ &`())AIi JJJPokmEGg:::t  MMMvu)B#УG#rEy΂V9U}>tupG!S5\R$hj~˅+߹_7?ğ>* | 9 ^/Ѧ`CEQTBJ,DdS}ɶdS{ #n=XE^8^+T q?GH&NzLe=o91̈́@}?~Ĭy,!_΁P8`(AR^G?8X楸PxCsQm;S[oq{t+l N} MMD!Hd'q@T軾gk 0VʹCˤֶH@WuV~k7 Z IRk'T@rM/h\X4Hope)b{ܗ\|1} P~y<&3$yTqm01s>̸[GCɾ.w;oU0en_ 5Rc2Fw܎)S N8o7RBn}5| 0dd. kdA+ٯ-I[}`Q?ct|k7Do!2jr6s<̳xG~zaTyRۀFXIo"ysN|_Ä  !3cdG84< IKoeoRǔ8.+%5,,_<.^PJQSS-7chllD}}=r73cѶƌ-7q6VƷʽu}hRɬxp8_Ȋ îؾm֬^ 6D}=}ݑ36Ja)jUK_ˆ# i ahljoOi,^xLc\chC?!---x/gEMm  B^n 9҂7n];֛oλ4c 眍c+D)U9P?~ygק?!1fRuu?ģzG"^T{N5igDtX۸;k I_x/ODf.=fe7))15"@Qa.Zrl6[hz<E1.z5Q]D@CO|k_yŰ9gz$Hefm2|\H@50eT|>~'W!Gb bX>JH  AQЀ} oڤ6fFYg)S诙\f\j H HoъfcD7^W2#Ađ__h:yRR@$(*,LNltN P&8MP=N3cP{g֦KUhI`MXvncԩp\sjQL'o+fzOtbqt;o]wZWԤ{)P:l&L$I ώ_/QԌ]39$xd!ztwtPc8صsG?~|&Bu\YXY `ŖIoQa;aJ69.+=]fO謇N3fΚ;# C8,G8]MT p,]zoGl߶ ޮWtƔ>}דV!uݷuu' 9=|#, ˁ2R/,KxɓxŗХ"3;ʲl9BY|! 46&O$f gNjM9֭_w~(O?^s-jzat9Μ82BHg[j֠$֑>pZZZ? Ǐם~ xeVPJ,O%G\q /׍U$1)[?otwϽKBܳ! eo6ZȽ!4@Fbi]wH`%~(k sfR8p{ 76j+HLl~2 eZ=-n^W_:[Z?RZ<ؾsg[t] pJ $2BԎYٲԺ.\%J6(A)ũfGo1w]c &A/ B{F889P/,"{;5z-ÒRpA<[P/)}c52(R7w<-?.k(U?SJIyemVd.cͧ=5j4ƍgJ)=|_`0E J}V~Thw:DQ:" 1Yv'`%%h>u [6oSaL Ivnݲ?Oƛo!!BŸ6gKו nW '8 pEJu||7$X~e <&8 wtb۶mhj]xx.  JJ[k&clv?NPۢSO;oѣt:&{W?z u'ND}(Uۛݱʦg]dtzB!Nh@{` 1X8TC&KjW YyV:t׭Wo*Λc)Xe_>5k`58|c\8+OȢh"!$8O2BHHţ$D8r#L"Zv@Luck`ҥ+0kl 6 $rS d/>_{o[  Bd϶iy%+R#([ц1}Ry1f%A^ͺˣ JJJ03q`;n`ۣGYՅ&lݺ?ڈMo‰Bqqz{f>5 Z_@K~-nhD%pƘlNJ@'0{Iol hMphֶ6llܴ e#F`ر5{ƍ'%E4.%Fs)9;a}8 J\ ~|! b(Jmsu ܋Ȃ 􈢴EW^]-F@r!3 8ЀM|Q0|pxn"6f^ ܌n_7#ʔҤf"B6 .(K8t h9? u Hz V$kn `6]PFP gB~>Gd97>(L!pRJw}Օ"%GLCӸ34bKIAEQ<( Zg[i(J%9!HEAJ(ylZ_D2 R]w ڎX}BHfs%tB898sݙ̡L _4DV{2{Z8߅*8쮃 %7W@fL m@ٶ:&  }0?fw) eC4~!v׻\H?b$"H +!쯹}B 0>r|Y`Œ1UxPr)c{&`!]mX6ɾ&9sۡS_?xRnwNiunjC.I@?ɞF ͱ?(BH+?K 1z<] 9hF)=+UBLزcͭkߞAn9C6ěI;)$ G!"}P\纗CHnPuҤ| (,aH82!dU_l@NdMfhq}7F;}$l>I/"ߋ>HJ 2?!*zRag;k!YK.M W!ApOhCdH%q4!T;lNcv`*(.W:QjfT$sl@_Ę<d=CH@v8x=ߗ 0C)m-$'ַNo._ I}q$$BLv$P(8{p !84~Nd9bw!a\wUd)9nstA,pz0!"NWBh]Ys^zo)^ΓPPe,.t`KK/F=Pv  )@2+ʒQ ZCo"c1$*Hawn(,!߂ض_t`3U,I Ep(XȘ<)2'#1$Zt-1 P@HKX!Qd; @ CVAd a"fH]4]p&R$@r @@vҵb|IG  p9BGCᐃ16 cp !|O ~ C- D,ns~Cajv[7 e Ƀ/B;^S7_"26 S*vX9E!\_Pv)k^龭 ?s W-!pX UbП0K!w BȒdSPPY>K8p ʈl$>&2chٽ~!3O,2?0$z@ mw8l9G"n(8dݯ!IDAv8ܟYs6ܨ3͂!7$$<w{P~?~f a[Ca.:!I=1/r9! ` p8ob;@9ҫ]O1}|e;M)CŜfeC 8fsnq9=lH&5ObZP? 'A!s>5(+`2BMv:<[v!BHs$_L>>(ziuH F6(@!s6Ms,CC *l{Mr'FGLM1{-#P ~ߌP?',qFrp[69!BH$a͹awTCGƞNLb~\O3;o@Kv(>@ܜ`?*Oe9<16sVeThfs|f9Jҡ˽P sO|Ugқ)rxTIq pO( +`U8/3s6sV9ws2C0 LB}.Jh hEQmM(Bٷ"?zu:L] KK* @wqΝpXC2 9nd7㬀Ιqq.%G BwB}RB(H vBP=#p?aW^Ы>?9Pۤ%} !9pQ.ZC_B41- #_9G!(DL4֩s.*ھd>b熄FAئ>2b!?k?4?@߆0K7sqkh@nC0 mozilla-vpn-client-2.2.0/wasm/index.html000066400000000000000000000155061404202232700202060ustar00rootroot00000000000000 Mozilla VPN - WebAssembly Viewer

Mozilla VPN - WebAssembly Viewer

Trouble loading content

loading icon

Trouble loading content

loading icon
mozilla-vpn-client-2.2.0/wasm/main.css000066400000000000000000000162051404202232700176440ustar00rootroot00000000000000* { box-sizing: border-box; } :root { --bgColor: #F9F9FA; --boxShadow: 0 12px 18px 2px rgba(34,0,51,.04),0 6px 22px 4px rgba(7,48,114,.12),0 6px 10px -4px rgba(14,13,26,.12); --gradientBeforeBackground: #eee; --gutters: .5rem; --inputBackground: rgb(240, 240, 244); --inputBackgroundFocus: rgb(223, 223, 227); --inspectorInputHeight: 5rem; --loaderOpacity: 1; --Metropolis: "Metropolis", sans-serif; --MozGradient: linear-gradient(-90deg, #ff9100 0%, #f10366 50%, #6173ff 100%); --svgFill: "#ffffffc9"; --textColor: #20123a; --textColorLight: #686869; } html, body { padding: 0; margin : 0; overflow-x: hidden; overflow-y: scroll; height: 100vh; width: 100vw; font-size: 15px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; -webkit-overflow-scrolling: touch; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; scroll-behavior: smooth; overscroll-behavior: none; background-color: var(--bgColor); font-weight: 400; display: flex; flex-direction: column; color: var(--textColor) } .rel { position: relative; } h1 { font-weight: 500; margin: auto auto auto .5rem; font-size: 1.15rem; font-family: 'Metropolis', sans-serif; } p { line-height: 1.5; margin: .5rem; } header, footer { display: flex; flex-direction: row; width: 100%; padding: 1rem 1.5rem; height: 4rem; } header { align-items: flex-start; } footer { justify-content: space-between; padding-bottom: 1.5rem; align-items: center; } .web-viewer { font-weight: 400; } .icon-link { color: rgba(0,0,0,0); min-width: 2rem; font-size: 1px; opacity: 1; transition: opacity 0.3s ease; } .logo { min-height: 2rem; max-height: 2rem; max-width: 2rem; } .logo path, .fill-path { fill: var(--svgFill); } rect { fill: none; } .Mozilla-logo { min-width: 7rem; } .icon-link:hover { opacity: .7; transition: opacity 0.3s ease; } .col-sm { padding: 0 calc(var(--gutters) * 2); position: relative; } .controller-content { flex: 1 1 40% } .qml-content { flex: 1 1 60%; } .col-left { padding-top: 2rem; } .container { margin: auto; padding: 1rem 4rem; } .container, .row { width: 100%; max-width: 1000px; justify-content: space-between; } .subhead { max-width: 80%; font-size: 1.1rem; margin-top: 0; } .row { margin: 0 !important; display: flex; } canvas { cursor: default; z-index: 999; width: 100%; } .control-bg { background: rgba(14, 13, 26, 0.05); border-radius: 2rem; max-width: 500px; height: 300px; overflow: hidden; } #controlcanvas { height: 100%; } #qmlcanvas { height: 68vh; max-height: 700px; } .canvas-bg { box-shadow: var(--boxShadow); border-radius: 2rem; overflow: hidden; background-color: rgba(14, 13, 26, 0.05); min-width: 400px; height: 70vh; max-height: 760px; } .wasm-loaded .canvas-bg { background-color: #F9F9FA; transition: background-color 0.3s ease; } canvas { opacity: 0; background-color: #FFFFFF; cursor: default; z-index: 999; width: 100%; transition: opacity 0.3s ease; } .wasm-loaded canvas { opacity: 1; transition: opacity 0.3s ease; transition-delay: .5s; } #qtstatus { font-size: 14px; text-align: center; margin: 3rem auto 0 auto; height: 1rem; color: var(--textColorLight); } .loader, .loading-error-message { position: absolute; top: 0; bottom: 0; right: 0; left: 0; margin: auto; visibility: hidden; opacity: 0; } .loader { height: 3rem; } .loading-error-message { width: auto; display: flex; justify-content: center; align-items: center; } .wasm-loading .loader, .wasm-loading-error .loading-error-message { visibility: visible; opacity: var(--loaderOpacity); transition: opacity 0.3s ease; } /* websocket controller */ .websocket-controller { position: absolute; top: 3rem; bottom: 3rem; left: 0; right: 0; display: flex; justify-content: center; align-items: center; flex-direction: column; } .draggable { resize: both; position: absolute; } .draggable-header { position: absolute; cursor: grab; top: 0; left: 0; right: 0; height: 6rem; opacity: .5; z-index: 5; } .dragging { cursor: grabbing !important; } .draggable-icon { position: absolute; right: 1.25rem; top: 1.5rem; margin: auto; } .log-wrapper { border-radius: 1rem; background-color: var(--inputBackground); min-height: 20vh; min-width: 30vw; overflow: hidden; z-index: 0; left: 10vw; box-shadow: var(--boxShadow); } #log { overflow: scroll; position: absolute; left: 1rem; right: 0rem; top: 1.5rem; bottom: 1rem; font-size: 13px; } .websocket-footer { margin: auto 0 0 0; } .controller { background-color: var(--bgColor); border-radius: 1.5rem; box-shadow: var(--boxShadow); min-height: 300px; height: 60vh; width: 40vw; position: absolute; overflow: hidden; font-size: 13px; z-index: 1; } .controller, .websocket-controller { display: flex; justify-content: center; align-items: center; flex-direction: column; } #content { position: absolute; top: 1.5rem; left: 1.5rem; right: 1.5rem; bottom: calc(var(--inspectorInputHeight) - 1rem); overflow: scroll; } input { position: absolute; left: -5%; bottom: -3px; background-color: var(--inputBackground); width: 110%; border: none; height: var(--inspectorInputHeight); padding: 1rem 10%; font-size: 1rem; color: var(--textColor); caret-color: orange; transition: opacity 0.2s ease, background-color 0.2s ease; } input:focus { background: var(--inputBackgroundFocus); outline: none; } .gradient { height: 1px; background: var(--MozGradient); width: 100%; content: ""; display: block; position: absolute; bottom: calc(var(--inspectorInputHeight) - 4px); z-index: 2; opacity: .4; transition: opacity 0.2s ease, background-color 0.2s ease; } .gradient.visible { opacity: 1; } .gradient::before { height: 4px; width: 100%; background-color: var(--gradientBeforeBackground); content: ""; opacity: 0; display: block; position: absolute; top: -4px; transition: opacity 0.2s ease; } .gradient.visible::before { opacity: 1; } /* end websocket CSS */ @media screen and (max-width: 1000px) { .row { flex-direction: column-reverse; } .col-sm { padding: 1rem 0 } .Mozilla-VPN-logo { margin-left: auto; } h1, .subhead { text-align: center; } .control-bg { max-width: 100%; } #qmlcanvas, .canvas-bg { min-width: 100%; } .subhead { margin-left: auto; margin-right: auto; } } @media screen and (max-width: 700px) { .container { padding: 1rem; } .controller { min-width: 300px; } .subhead { max-width: 100%; } } @media (prefers-color-scheme: dark) { :root { --bgColor: #4a4a55; --boxShadow: 0 12px 18px 2px rgba(34,0,51,.04),0 6px 22px 4px rgba(0, 0, 0, 0.12),0 6px 10px -4px rgba(14,13,26,.12); --inputBackground: #474752; --inputBackgroundFocus: rgb(62, 62, 74); --gradientBeforeBackground: #312f3780; --loaderOpacity: .4; --textColorLight: rgba(255, 255, 255, .5); --textColor: white; --svgFill: #ffffffc9; } } .webSocketResponseType { padding-right: 1em; color: blue; } .webSocketResponseError { padding-left: 1em; padding-right: 1em; color: red; } .webSocketResponseValue { padding-left: 1em; padding-right: 1em; } mozilla-vpn-client-2.2.0/wasm/qtlogo.svg000066400000000000000000000025231404202232700202320ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/windows/000077500000000000000000000000001404202232700167255ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/windows/installer/000077500000000000000000000000001404202232700207225ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/windows/installer/MozillaVPN.wxs000066400000000000000000000122611404202232700234620ustar00rootroot00000000000000 (&MozillaVPNFeature = 3) OR (&MozillaVPNFeature = -1) mozilla-vpn-client-2.2.0/windows/installer/README.md000066400000000000000000000010701404202232700221770ustar00rootroot00000000000000# Installer A WiX script for packaging up all binaries and supporting files in a convenient MSI file, suitable for further distribution. ## Building A build script is included in this folder, which creates an installer for each platform and handles downloading all dependencies, such as [WiX](https://wixtoolset.org/) and [Wintun](https://www.wintun.net/). Don't attempt to run the scripts without building the [tunnel](../tunnel), followed by the [Mozilla VPN client](../ui) for x64 target platform first. If the build succeeds, `x64/MozillaVPN.msi` should appear. mozilla-vpn-client-2.2.0/windows/installer/build.cmd000066400000000000000000000034511404202232700225110ustar00rootroot00000000000000@echo off rem SPDX-License-Identifier: MIT rem Copyright (C) 2019 WireGuard LLC. All Rights Reserved. rem Copyright (C) 2019 Edge Security LLC. All Rights Reserved. setlocal set PATHEXT=.exe set BUILDDIR=%~dp0 cd /d %BUILDDIR% || exit /b 1 set WIX_CANDLE_FLAGS=-nologo -ext WiXUtilExtension set WIX_LIGHT_FLAGS=-nologo -spdb -ext WixUtilExtension if exist .deps\prepared goto :build :installdeps rmdir /s /q .deps 2> NUL mkdir .deps || goto :error cd .deps || goto :error call :download wintun-amd64-0.8.1.msm https://www.wintun.net/builds/wintun-amd64-0.8.1.msm af9644438a716f5a022052e3574ee0404c3e3309daff84889d656178fbc6b168 || goto :error call :download wix-binaries.zip https://github.com/wixtoolset/wix3/releases/download/wix3112rtm/wix311-binaries.zip 2c1888d5d1dba377fc7fa14444cf556963747ff9a0a289a3599cf09da03b9e2e || goto :error echo [+] Extracting wix-binaries.zip mkdir wix\bin || goto :error unzip wix-binaries.zip -d wix\bin || goto :error echo [+] Cleaning up wix-binaries.zip del wix-binaries.zip || goto :error copy /y NUL prepared > NUL || goto :error cd .. || goto :error :build set WIX=%BUILDDIR%.deps\wix\ call :msi x64 || goto :error :success echo [+] Success. exit /b 0 :download echo [+] Downloading %1 curl -#fLo %1 %2 || exit /b 1 echo [+] Verifying %1 for /f %%a in ('CertUtil -hashfile %1 SHA256 ^| findstr /r "^[0-9a-f]*$"') do if not "%%a"=="%~3" exit /b 1 goto :eof :msi if not exist "%~1" mkdir "%~1" echo [+] Compiling %1 "%WIX%bin\candle" %WIX_CANDLE_FLAGS% -dPlatform=%1 -out "%~1\MozillaVPN.wixobj" -arch %1 MozillaVPN.wxs || exit /b %errorlevel% echo [+] Linking %1 "%WIX%bin\light" %WIX_LIGHT_FLAGS% -out "%~1/MozillaVPN.msi" "%~1\MozillaVPN.wixobj" || exit /b %errorlevel% goto :eof :error echo [-] Failed with error #%errorlevel%. cmd /c exit %errorlevel% mozilla-vpn-client-2.2.0/windows/tunnel/000077500000000000000000000000001404202232700202325ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/windows/tunnel/README.md000066400000000000000000000006411404202232700215120ustar00rootroot00000000000000# Tunnel DLL A simple yet effective way of leveraging the existing WireGuard codebase. This is more or less the same thing as the [embeddable-dll-service](https://git.zx2c4.com/wireguard-windows/about/embeddable-dll-service/README.md) from upstream [wireguard-windows](https://git.zx2c4.com/wireguard-windows/about/). ## Building To build the embeddable dll service, run `.\build.cmd` to produce `x64\tunnel.dll`. mozilla-vpn-client-2.2.0/windows/tunnel/build.cmd000066400000000000000000000037271404202232700220270ustar00rootroot00000000000000@echo off rem SPDX-License-Identifier: MIT rem Copyright (C) 2019 WireGuard LLC. All Rights Reserved. rem Copyright (C) 2019 Edge Security LLC. All Rights Reserved. setlocal set BUILDDIR=%~dp0 set PATH=%BUILDDIR%.deps\go\bin;%BUILDDIR%.deps;%PATH% set PATHEXT=.exe cd /d %BUILDDIR% || exit /b 1 if exist .deps\prepared goto :build :installdeps rmdir /s /q .deps 2> NUL mkdir .deps || goto :error cd .deps || goto :error call :download go.zip https://dl.google.com/go/go1.13.3.windows-amd64.zip 9585efeab37783152c81c6ce373b22e68f45c6801dc2c208bfd1e47b646efbef || goto :error rem Mirror of https://musl.cc/x86_64-w64-mingw32-native.zip call :download mingw-amd64.zip https://download.wireguard.com/windows-toolchain/distfiles/x86_64-w64-mingw32-native-20200907.zip e34fbacbd25b007a8074fc96f7e08f886241e0473a055987ee57483c37567aa5 || goto :error copy /y NUL prepared > NUL || goto :error cd .. || goto :error :build set GOOS=windows set GOPATH=%BUILDDIR%.deps\gopath set GOROOT=%BUILDDIR%.deps\go set CGO_ENABLED=1 set CGO_CFLAGS=-O3 -Wall -Wno-unused-function -Wno-switch -std=gnu11 -DWINVER=0x0601 set CGO_LDFLAGS=-Wl,--dynamicbase -Wl,--nxcompat -Wl,--export-all-symbols set CGO_LDFLAGS=%CGO_LDFLAGS% -Wl,--high-entropy-va call :build_plat x64 x86_64 amd64 || goto :error :success echo [+] Success exit /b 0 :download echo [+] Downloading %1 curl -#fLo %1 %2 || exit /b 1 echo [+] Verifying %1 for /f %%a in ('CertUtil -hashfile %1 SHA256 ^| findstr /r "^[0-9a-f]*$"') do if not "%%a"=="%~3" exit /b 1 echo [+] Extracting %1 unzip %1 %~4 || exit /b 1 echo [+] Cleaning up %1 del %1 || exit /b 1 goto :eof :build_plat set PATH=%BUILDDIR%.deps\%~2-w64-mingw32-native\bin;%PATH% set CC=%~2-w64-mingw32-gcc set GOARCH=%~3 mkdir %1 >NUL 2>&1 echo [+] Building library %1 go build -buildmode c-shared -ldflags="-w -s" -trimpath -v -o "%~1\tunnel.dll" || exit /b 1 ::del "%~1\tunnel.h" goto :eof :error echo [-] Failed with error #%errorlevel%. cmd /c exit %errorlevel% mozilla-vpn-client-2.2.0/windows/tunnel/go.mod000066400000000000000000000003561404202232700213440ustar00rootroot00000000000000module github.com/mozilla-services/guardian-vpn-windows/tunnel require ( golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 golang.org/x/sys v0.0.0-20200107162124-548cf772de50 golang.zx2c4.com/wireguard/windows v0.0.38 ) go 1.13 mozilla-vpn-client-2.2.0/windows/tunnel/go.sum000066400000000000000000000064321404202232700213720ustar00rootroot00000000000000github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1 h1:/QwQcwWVOQXcoNuV9tHx30gQ3q7jCE/rKcGjwzsa5tg= github.com/lxn/walk v0.0.0-20191128110447-55ccb3a9f5c1/go.mod h1:E23UucZGqpuUANJooIbHWCufXvOcT6E7Stq81gU+CSQ= github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4 h1:5BmtGkQbch91lglMHQ9JIDGiYCL3kBRBA0ItZTvOcEI= github.com/lxn/win v0.0.0-20191128105842-2da648fda5b4/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413 h1:ULYEB3JvPRE/IfO+9uO7vKV/xzVTO7XPAwm8xbf4w2g= golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876 h1:sKJQZMuxjOAR/Uo2LBfU90onWEf1dF4C+0hPJCc9Mpc= golang.org/x/crypto v0.0.0-20191227163750-53104e6ec876/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191210023423-ac6580df4449 h1:gSbV7h1NRL2G1xTg/owz62CST1oJBmxy4QpMMregXVQ= golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200107162124-548cf772de50 h1:YvQ10rzcqWXLlJZ3XCUoO25savxmscf4+SC+ZqiCHhA= golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.zx2c4.com/wireguard v0.0.20191013-0.20191128101113-ddfad453cf22 h1:I+PVPt4NrWyrzoxxgZbXdMalibOrpXTNpqmLsirzSLk= golang.zx2c4.com/wireguard v0.0.20191013-0.20191128101113-ddfad453cf22/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4= golang.zx2c4.com/wireguard v0.0.20191013-0.20200107164045-4fa2ea6a2dab h1:HVdRO4CGjul3Pj1BD1K5uThcFtrwXEF+F5qI+//V0mw= golang.zx2c4.com/wireguard v0.0.20191013-0.20200107164045-4fa2ea6a2dab/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4= golang.zx2c4.com/wireguard/windows v0.0.38 h1:RIXfYUYDCBk5+tsxrnNBRXxIvgnXJc544MrDMFFXj4I= golang.zx2c4.com/wireguard/windows v0.0.38/go.mod h1:bVbqKzpu4jLrEA2nVNn/WdWyyXGZARZNqkoGHd61DuM= mozilla-vpn-client-2.2.0/windows/tunnel/main.go000066400000000000000000000101011404202232700214760ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT * * Copyright (C) 2019 Edge Security LLC. All Rights Reserved. */ package main // #include // #include // static void callLogger(void *func, int level, const char *msg) // { // ((void(*)(int, const char *))func)(level, msg); // } import "C" import ( "fmt" "io/ioutil" "log" "net" "net/http" "path/filepath" "strings" "C" "golang.org/x/sys/windows" "golang.zx2c4.com/wireguard/windows/conf" "golang.zx2c4.com/wireguard/windows/tunnel" "golang.zx2c4.com/wireguard/windows/tunnel/winipcfg" "golang.zx2c4.com/wireguard/windows/tunnel/firewall" "errors" "unsafe" ) var loggerFunc unsafe.Pointer type CLogger struct { level C.int } func (l *CLogger) Write(p []byte) (int, error) { if uintptr(loggerFunc) == 0 { return 0, errors.New("No logger initialized") } message := C.CString(string(p)) C.callLogger(loggerFunc, l.level, message) C.free(unsafe.Pointer(message)) return len(p), nil } //export WireGuardTunnelLogger func WireGuardTunnelLogger(loggerFn uintptr) { loggerFunc = unsafe.Pointer(loggerFn) } //export WireGuardTunnelService func WireGuardTunnelService(confFile string) bool { logger := log.New(&CLogger{level: 0}, "", 0); tunnel.UseFixedGUIDInsteadOfDeterministic = true firewall.ExemptBuiltinAdministrators = true conf.PresetRootDirectory(filepath.Dir(confFile)) err := tunnel.Run(confFile) if err != nil { logger.Println("Tunnel service error: %v", err) } return err == nil } func findPhysicalDefaultRoute() (winipcfg.LUID, net.IP, error) { r, err := winipcfg.GetIPForwardTable2(windows.AF_INET) if err != nil { return 0, nil, err } lowestMetric := ^uint32(0) var nextHop net.IP var luid winipcfg.LUID for i := range r { if r[i].DestinationPrefix.PrefixLength != 0 { continue } ifrow, err := r[i].InterfaceLUID.Interface() if err != nil || ifrow.OperStatus != winipcfg.IfOperStatusUp || ifrow.MediaType == winipcfg.NdisMediumIP { continue } if r[i].Metric < lowestMetric { lowestMetric = r[i].Metric nextHop = r[i].NextHop.IP() luid = r[i].InterfaceLUID } } if len(nextHop) == 0 { return 0, nil, errors.New("Unable to find default route") } return luid, nextHop, nil } //export WireguardTestOutsideConnectivity func WireguardTestOutsideConnectivity(ip string, host string, url string, expectedTestResult string) int32 { logger := log.New(&CLogger{level: 0}, "", 0); // Attempt to locate a default route luid, nextHop, err := findPhysicalDefaultRoute() if err != nil { logger.Println("Failed to find the physical default route: %v", err); return -1 } destination := net.IPNet{IP: net.ParseIP(ip), Mask: net.IPv4Mask(255, 255, 255, 255)} err = luid.AddRoute(destination, nextHop, 0) // Check for errors, and check if route already exists if err != nil && err != windows.ERROR_OBJECT_ALREADY_EXISTS { logger.Println("Failed to add a new route: %v", err); return -1 } defer luid.DeleteRoute(destination, nextHop) // Attempt to setup a new GET request req, err := http.NewRequest("GET", fmt.Sprintf(url, ip), nil) if err != nil { logger.Println("Failed to create a new HTTP request: %v", err); return -1 } // Redirects should be treated as an assumption that a captive portal is available redirected := false client := &http.Client{ CheckRedirect: func(r *http.Request, via []*http.Request) error { redirected = true return errors.New("Redirect detected.") }, } // Set the host header and try to retrieve the supplied URL req.Host = host resp, err := client.Do(req) if redirected { // We were redirected! return 0 } if err != nil { logger.Println("Failed to complete the HTTP request: %v", err); return -1 } // Read response from the body text, err := ioutil.ReadAll(resp.Body) if err != nil { logger.Println("Failed to read from th HTTP request: %v", err); return -1 } // Compare retrieved body contents to expected contents compareTestResult := strings.ReplaceAll(strings.ReplaceAll(string(text), "\n", ""), "\r", "") if compareTestResult == expectedTestResult { return 1 } else { return 0 } } func main() {} mozilla-vpn-client-2.2.0/xcode.xconfig.template000066400000000000000000000003061404202232700215250ustar00rootroot00000000000000DEVELOPMENT_TEAM = <> # MacOS configuration GROUP_ID_MACOS = <> APP_ID_MACOS = <> NETEXT_ID_MACOS = <> LOGIN_ID_MACOS = <> # IOS configuration GROUP_ID_IOS = <> APP_ID_IOS = <> NETEXT_ID_IOS = <>

碟LQ Iɜ,k9|}OGY(xb+T9:gYg&ml_b%P*s;IS(IY)H3WXaYփp$ "viߖt.LW]< 6Wi3Jgym=/,AXd[v1Oihv܆{{?<ᤀ)^b+'%tj9' \d̀ H{lrJV %|c+v!}C[_/ A? %+R|&X1*}WD]rá0 vyEq^~:o\f$6go-eMqbY{_IENDB`mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/icon-ios-29@1x.png000066400000000000000000000013161404202232700313040ustar00rootroot00000000000000PNG  IHDRVg pHYs  sRGBgAMA acIDATxU@GwMH$CHc'H< AA" MZI)"tG~LXsܝVޟvfY;D$a!#izGw(ҺݮXVX.]FZt8< !$^i uO! jq1ϙ붤BJ{SeuCOލF#-lv!Cx:ȯ:>1HhfWV U08$%(rwIہ1tL&#à`4^PSi |t,'\f&0OShQe^9#yXxO}zxD*t0 3\s v ӁMh>~~ PNtzqf8b{0CaLg񃉬p3>kPO, BkЮ 0[hc%Mݚ/J}Zk6f3ň64 bU=ȬpJ*1 cÆ!D/rEIENDB`mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/icon-ios-29@2x.png000066400000000000000000000023171404202232700313070ustar00rootroot00000000000000PNG  IHDR::J( pHYs  sRGBgAMA adIDATxZOSAbHZB &:vաuu/hhbhG6̈́vŐ A1&\y^wwNA#4"6D 7iss~VTTP(u9A^VwPH.GQ) ;Dnprr8R2;;;|̹T* Q6A.\.#'D#HYX,<҉FV3ԣZ@'m^NWii{{G.Hi^"d2ɋ+Hk?Z"+i! [ h$ix*W,O3re~͒>1tL&#à`4^PSi |t,'\f&0OShQe^9#yXxO}zxD*t0 3\s v ӁMh>~~ PNtzqf8b{0CaLg񃉬p3>kPO, BkЮ 0[hc%Mݚ/J}Zk6f3ň64 bU=ȬpJ*1 cÆ!D/rEIENDB`mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/icon-ios-29@3x.png000066400000000000000000000032611404202232700313070ustar00rootroot00000000000000PNG  IHDRWWql pHYs  sRGBgAMA aFIDATx\oE~Fq tD7i|.Rhb*𹅂sC룥wH4 (Rs4 ]aȅRe8_v~܌37߼}~z" 7(ȵ@Er-"k\ZD "ȵ 9* -..RTbH~zmt:;"FpppUU/?k~ T.ct\"8x мzLpy1'/ŦB"u\a+v?}\'V#"Wm|A\4! 8NY;)xK.n+Gi>sssx;"` 8'|`hh|gVNwTV4v{{ko9pDa/Oċ\8\4DT2̭-! BvֲE, xbkݒkXߪkhw=N kkk˷Y{%KvCSTcqlJaou3n Kb|CZ^^677c ~U%)~kr[4FYadmMbE*02eojҩy^LGTd\Ϭ֦ռI$ ?[| 4-W!S9Uk7繉n?S5A.B˦aggVVV o,4ըju&yr,EUZʢ(HzJ,Ѥ6j-U@U>^B@&gUWٺ|~''' mrE oc 2Eu,UffE\acx㘻H Y15/:ˊ Y>7Ϥ_Ec9JCBaXзf4ZP&Ґ)TvVLoR]Ue10|$/2ky47LԼ<#r_żjMr*ɟVW8Kk5ͭ"[ EcEɠ}g;wh|&څKn ^ ,s*atCVs=/_# ߙ,"k\ZD "$q',Otuc5z~~zj^#>Ʒ βbtB[wIv|\\ϕ烀[tm{訽O7qѰn?͇1GPaJ_WS8QCw&3rmލ'$hh:zYm=dfXA2642.//!#aH0]fv A&ӌi./xzns}A ˳w$q -K47f'{/0w%zA"3. cNˉolȅw@tR'`<` v: 7bM\I!-"Dhȵ@Er-"k\ZrIENDB`mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/icon-ios-40@1x.png000066400000000000000000000016071404202232700313000ustar00rootroot00000000000000PNG  IHDR((m pHYs  sRGBgAMA aIDATxX=A~1wwgШ׸)젰~T`c5L X5ZĻB}&7dXv$ٝyxDb nL&X,R:tJ>K 2:F~,afG]9gJ"nbXSݮEmY̛^o.!WQaMP%E&PǞQX_ucH ،5Aĕ , E!WkI+GHœX2Trp3j \ Gt<Q6 6V]juBNi>@(+Yo|X,{f9r3Y %bѯVJh2̬t\. LT\!IRqkēĥ-p[BGtu1W2á>pϞo8iZxk1@ "`uI@`V`whH8u-["7v2ca2dYfH)q:땶.++j yԙ&º zE0r͞2XP-9#Lt`bqۺ|H6&>%=y콦IG5ȉK6_gS~Юs~})s׳igm`e&8~{bWD7ѻ'.>ھ'2Q=[%qu[G?ocE5]F`;%|^[Y|v1, RsF\;7Xg~.ǣ<IENDB`mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/icon-ios-40@2x-1.png000066400000000000000000000031061404202232700314330ustar00rootroot00000000000000PNG  IHDRPP pHYs  sRGBgAMA aIDATx\oF~* Xj"mvݽ>]""2rP#P@E8T#P@E8T#P(gT*h4huuժl2`0[1n띞z"oggǫjU?mR8>>fȳO`&SQ \m!)X777u+%*B8&"*HYD(Fp(""'<%VU)7cAUT06+cYXXۋ?D_Z;QR5MQ6苂\΍牰w-( 舂p Θ,)N xE^g!i}}=y!vx; -y?/)ԢgI;S ̬iAőI --9]ʹT8즒!Zs`sq7͕!, -#Dl DA&j5 Ҙp]eV{$3 dR<4#.aOHg4@i%j((3Ru*B Ig-<ِ!(3LOXb*U^sjս!NG+ANRI? )=:%Ty`(p /La>!J 9'rQI*Aϳ<[ _>rc>͕h2:T* u6RlDRd>n{hyN+YɄP+Z"}p8J"iyylV=Wрqp3OJ;}FQ#pG"p*cdxD׎}ZZ2my˓6~x@|0nѿ7TzD+Iw4fީr't2$g|E.Gh|sRSoNa|mq*{?R嬏ؽAYI'O39W{w*~N2%b6^@p7Ш0/"K~v6gA@90}Oګ}2 X ԺX0E>u/<K`b@!x5ad ?[z σKק~T1ﰅk9Xw. *L!?)K[sƽGoW(G"p*ƅfIENDB`mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/icon-ios-40@2x.png000066400000000000000000000031061404202232700312750ustar00rootroot00000000000000PNG  IHDRPP pHYs  sRGBgAMA aIDATx\oF~* Xj"mvݽ>]""2rP#P@E8T#P@E8T#P(gT*h4huuժl2`0[1n띞z"oggǫjU?mR8>>fȳO`&SQ \m!)X777u+%*B8&"*HYD(Fp(""'<%VU)7cAUT06+cYXXۋ?D_Z;QR5MQ6苂\΍牰w-( 舂p Θ,)N xE^g!i}}=y!vx; -y?/)ԢgI;S ̬iAőI --9]ʹT8즒!Zs`sq7͕!, -#Dl DA&j5 Ҙp]eV{$3 dR<4#.aOHg4@i%j((3Ru*B Ig-<ِ!(3LOXb*U^sjս!NG+ANRI? )=:%Ty`(p /La>!J 9'rQI*Aϳ<[ _>rc>͕h2:T* u6RlDRd>n{hyN+YɄP+Z"}p8J"iyylV=Wрqp3OJ;}FQ#pG"p*cdxD׎}ZZ2my˓6~x@|0nѿ7TzD+Iw4fީr't2$g|E.Gh|sRSoNa|mq*{?R嬏ؽAYI'O39W{w*~N2%b6^@p7Ш0/"K~v6gA@90}Oګ}2 X ԺX0E>u/<K`b@!x5ad ?[z σKק~T1ﰅk9Xw. *L!?)K[sƽGoW(G"p*ƅfIENDB`mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/icon-ios-40@3x.png000066400000000000000000000040501404202232700312750ustar00rootroot00000000000000PNG  IHDRxx9d6 pHYs  sRGBgAMA aIDATx흿oE78DHqa74>KmQDJRvC m`X .RdS@cMDDa&Ųߑ[{47;3R*R[^QkD`#3Gf9"0sD`#3Gf9*LMMIUuH8??@ST*rtvvrrrk)0ٞavvJnBSF൵HRY n0r de+a{{;ҦTd7KKKʄZ- E"U6,.5c\dGl lz!* c9L&ʈF43a|8&(7fX'5Եƕ9/a0ysI7- \m8m _ysƖPޏ6c`sحysWجACݢ|\hq=}tLdT\rnI}vC19c9 0֒t] q_ l='yĶ=*2o8-4kr@ "^Vdžn N|l. 0cӢ&G>mg&KD9zN7B;;dncza um!kuKW!,.''`Re &9E<%0A2Z)+jRZ@fu0X'X`y](yN!չkuG?FJ` ,9"0sD`#3Gf@޺b7Ct{]jswr&L=R?O {ƫQoo~?ٽ<[~F7۾ntMgr+ ~횿i}n6Wo-t(@F`4ht?:gZo̾[{.N_Gj7;uw2+W3ͱ{dD O<]4Zxk>[1ȴVZ_ןBВq*m-5~k=G#+>Uحvh;xWd!}TɩGԸqwZ-Ԍdk0cՍdEH*^ qel]z1Ѫ}VЮΤb~0ŕ7u!#p>AA뷎׿-̭l~1w[2'ʋLBAFറ`y4x_Oz$Ƌ,}֑V *"Y`qcl60Gf9"0sD`#3GfοةuuIENDB`mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/icon-ios-60@2x.png000066400000000000000000000040501404202232700312760ustar00rootroot00000000000000PNG  IHDRxx9d6 pHYs  sRGBgAMA aIDATx흿oE78DHqa74>KmQDJRvC m`X .RdS@cMDDa&Ųߑ[{47;3R*R[^QkD`#3Gf9"0sD`#3Gf9*LMMIUuH8??@ST*rtvvrrrk)0ٞavvJnBSF൵HRY n0r de+a{{;ҦTd7KKKʄZ- E"U6,.5c\dGl lz!* c9L&ʈF43a|8&(7fX'5Եƕ9/a0ysI7- \m8m _ysƖPޏ6c`sحysWجACݢ|\hq=}tLdT\rnI}vC19c9 0֒t] q_ l='yĶ=*2o8-4kr@ "^Vdžn N|l. 0cӢ&G>mg&KD9zN7B;;dncza um!kuKW!,.''`Re &9E<%0A2Z)+jRZ@fu0X'X`y](yN!չkuG?FJ` ,9"0sD`#3Gf@޺b7Ct{]jswr&L=R?O {ƫQoo~?ٽ<[~F7۾ntMgr+ ~횿i}n6Wo-t(@F`4ht?:gZo̾[{.N_Gj7;uw2+W3ͱ{dD O<]4Zxk>[1ȴVZ_ןBВq*m-5~k=G#+>Uحvh;xWd!}TɩGԸqwZ-Ԍdk0cՍdEH*^ qel]z1Ѫ}VЮΤb~0ŕ7u!#p>AA뷎׿-̭l~1w[2'ʋLBAFറ`y4x_Oz$Ƌ,}֑V *"Y`qcl60Gf9"0sD`#3GfοةuuIENDB`mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/icon-ios-60@3x.png000066400000000000000000000067441404202232700313130ustar00rootroot00000000000000PNG  IHDR=2 pHYs  sRGBgAMA a yIDATx1lB v@Eܐ@  UT )(pM4"KLq`I I+бD"vs̛?xۙ7(L"$#(h4 dM&YAAIVP$+(h4 dM&YAAIVP$+(h4 dM&YAAIVH+Ϊߠ ~3ECA{]YYQjiii(* CO-vNfox}W!'j(O= r6D/]M1Qh ҕ>L`[5mqYv5ÎƇB$۰ۿlsΈ4fiB4e?s m*)La;VSymPL؈ W3cBAc㝁ͭ+c [Qq͕+WyM>6t~m<|zzs(h!6t KR/VWWUSPV6899QMQF&` qĬm9ۢh*Kc&jDiÇDJc&jK:Z1c&j@]Sl^clLn7ɎClu]NSkxO$ݺu0s<'\}ukTˍzLiYlp@;wFMs  !I5l+&E̘t^#z7)bRD ֫QY^0,P!(TƛhC&rae3X@:%QRlښS2}.N&ۭ>*&{*н{GZJJXwS9j.A_gP|^>K{%qa3|ȩ3: XbCQ5BTwZ̝7⹽MU9tU#tsljOĥml* > s&Uveom*ePsиHeRs 4wZNBgNe&Od٣?(3Y![ژ'ĜZf5"t' )9ǩ"FЈGcQf! ]rä /p3x_, vI\HT"XPhqGE^ڥcI߅^:́\`nwJrVjewBxˇvЉ]6cg˸ݦ\"oiMidolE'ʱwq'6>qeC^ad#*| >-Rd4pOb[W^ >eBA*'İtm\Ԝ/>hB'[ LoxE{öMma|wwren,H"S;sjaA|k׮)_'( YxO9$+hώCJ >/%Zo#tLжK(fr\)۽r]r;Xr&qk v۬m]&ElB}.l&ؙCq .vhEfsl@0r!p`rN[$G]$+5:?,'''l^uBLljiH}SaŤl0D 6QL`6PPL`3D3);B(&f.4>ڦɤ.%MbSB1C]baB/`CLwM.׽BL|\4dM`[7U\-D UsvkӡZ,jƇBJD:&߸V x 8@Զݻܙ f?,wes|+)Fm` {}a 3P6s>7$wW90}v Mb}EH&CIVP$+(h4 dM&YAAIVP$+(h4 dKciS_3 u[ٯ\G꣍?\?zV]j;塀.ͫŃߪC][?S_C1':&s+@Gҿ45u0/:~ w+=F贠_NN=z.Bཹ_O@Egb<E`Q~07b}).2%(Xa@J B:=zWuNeQOxyreX>gꃘ<<3<41Cҝ_Vqo&ƴS oYObP"d9ǐxO،Nՙ^:qj!@<ߗ ykǸ4Ga:$9-^[w&':-*1 !LAHeux](hƘF<_l3w:-I@Z+~V-?Œ?nj2Bc {2Ͳ'W]q8aXԙq0%{<;4iAF$ՙv.Wf$hH!%yT7:oL;Rly6 nyɫP6pc\ڙ/㪓 q~f1$+rIVP$+(h4 dM&YAAIVP$+(h4 dM CIENDB`mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/icon-ios-76@1x.png000066400000000000000000000030311404202232700313020ustar00rootroot00000000000000PNG  IHDRLLǗQ+ pHYs  sRGBgAMA aIDATx휱o[EFuRDGХHvX 1 mH UH H b/m vל:w{8N~z|y~ eJ<0Ky`,Yf)RhNT(ZRTh4NCV>͋4 @ ^7 luu5DU(\+Xw~€\޹VTB!{QV?YwamnnJ;0kTa!C>^^x`*Wi1ˣ4 fHK͚is vRX0֔B,ju3'Ka`5N@bX9}BacדgVX0 (ԏct ' n4mH` ^$ Z{Fe)e&T@EtVebfn/B`ɬB1q=1ypB YŠR8ӚWN"S$8in.=+rYBucI h#dn74%n3)$Tv5=,skiefU‚ȕe#1[)*+Tf9UT=!ۛ8th;2ztͤzXmDؚ`"Rs:UEL\ 0aSTƻwzlMx<˄L2װxxhcٔ^ʤjJF.VIODhyXA3ukۖT*Dz*d3CZCGm'M+MB3]E:%$)X3_|6~2MׄwR pO]r 0<1~Oˤ=xp1QrY tl2EY 3Ϊ*a 1[fJU=`aqJXRi`*% sEe:TUKK(r’2ݼ⠸(YOv7]Tx\R-zLO_9OvS7+R3Wy˧-Yf)RIg9*|FśS6ԽwZ)R},H}[RNH_(_zwk*' l2Vn7w7?ޣN'@S`ǃGNC:CZ nBǃw_|[,YfkLjI@IENDB`mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/icon-ios-76@2x.png000066400000000000000000000060141404202232700313070ustar00rootroot00000000000000PNG  IHDR ! pHYs  sRGBgAMA a IDATx흿oE'R$@Ž 4qZ(HH ' ;-M.؆" @)nő MHPdfn̼>ғnwn{ofߛyO)SJ VD`+"0L`E&"X VD`+"0L`E&"X VD`+O*a j|||'R;;;jccC U055_ 8=6;;ۻqFϔ!},ƲXrqBǂjSUsC& ,[ u9pƱXgr9$JXdA50,68wH}Ȃjqι,ZA*Daj7:t("O>m%.)"Dmw:[3k{no~~>Ėz=p܄&A5L^uC !bA5VVVHxȂjaH ^k{\Y|,ĺ.KKKd ރmS)̷0ס`PI!{0Jh!率rxP^`lv_+KlD/0J}:2q=dE)J^`I IM%-QCț2J$Tcիn0pnnrM>C FtzT ǧ $-u`ȫZ zGM*@YѺfff ," іSNT`kԜ$'ma Fէ4`lH}jlœ.sdA56Xq.djlhOHЂjlpB;:b7/+a='qDؔk8qℓ.ᩗϧk=>ҺEz8 /k|\ֹFY\|vgYNDVϟjs4*_ǰgw[V.)Ʀpqc2GŶy6*!fySaaaAe;zS^N!l<;rgɾgmc Zcjnle]|75wlg3'jl0|WPγҵ1m'I,[Yq,{Vj|Z`pAC2/Fhbuk۸lnbj]y/eJg1}dދkxt~P ^ч}fr`MaaBC ܙCdUݦ /H?vXH#+PlQmU"eʠ=J.׎kKR^? j_1Sk$ρ)`0=P 6Cu xuPjv9O)(W؆_<%,uflWb^vMņ溺Z"0R <8h>X uJ‡c,xlHRc?56T>bi5.o׺6iSg=SB [XnO)Vm1(4UN@+[Y~rlx@⪑&38S"u@ZψtQ]t_R($NY/nJ&cʐRU&|ia1\Ct:tXijkF ܋YEb1:p* ziXC#Vk0 |)bJ v!̗IO|PA<6\4TRSM&>;q5$&MwOe+}WI\і"k“s'o&j.z󈫒J:YUI a-Qo*~([^(AK|ܩ/ ںΪb[/\дsiAʢ 607hF-. ,APCf|ښN SC;$.8OvAXOσc~sϺ_\ 7m}zXCO N鲕\\ U$ Dĕ|M%kl->5/XC֘4oLOOU |L{ {_ XP52Rꔒ&<YP52jѤ>uծ-ƒ CԵn) o↺̙3gHFF0cGpгī\+`[aF?.)ߋ^`\,wv_|}C3OU^{kbDoG}>-ww "VD`+"0L`E&"X VD`+"0hv^6:_j|=tIZ7.W.R<ѧ[7VW߿owֻLLOe6; [~=}]NQ1##OA&"x*\ԝ? |Y>c;yoWx7*/Z`TƟW{={~wsႚ:b$J8cKT ?Ȫx5]}e@`s3{~[ṼKrO.\m+ڏ*F`9KWqaJ} <[+CaFmMM axUwuN|aŻ^̆n 7! /U4u.ϙi 7Zhu{(=X^<"?)zC4ąכ'S 9y_u;_ޣy> @q; a["Bxa! W '`+cLxtaᩪAwtãn-~AaPH.R3>NE+ {!;H8Uh"@o|nj5:4 X(egX VD`+"0L`E&"X VD`+"0F%2IENDB`mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/AppIcon.appiconset/icon-ios-83.5@2x.png000066400000000000000000000063561404202232700314610ustar00rootroot00000000000000PNG  IHDRtL pHYs  sRGBgAMA a IDATx=lG kJA 0 R4T)B ҤٺƭD)8:6 +YčlXP: 89/W5xys9T_"(BBqPD,' IBqPD,' IBqPD,' IBqPD,' IBqPD,' IBqWę^WZU ӐVZYYsu:nK3VgܻwwÖTaRÇ}7zhȒ*lT{n?$wI> Ъ=z#-7 :Ғ*lPkBXKIaR#-33r[P-%U v֭$677PvESKϏIzj?FQ(OƪHBͼo2wvvJ'}} >iR*WQN#k)c@K^ͦeCftM'[Rfv&ѷa~~^t= lĂ7kbjJJ֋S\(LY\\T8Mo [sLi?d+Nӛ>3{L׆5#⌌4Wdij3D,a@4C+` *WMGw\,+~72L2!S\"ld!z3&[Mj,[Rj i7wπ%UXfӵk"c(]r+W)=DEiƓ>Z "ߋrc<1<rBjng|a 0z{A ̐S6]LꚣTaJeUD$Lc7H\H!TuLSh0<+:'1W!}^3NiXϔ]8vwwQO/ueԗxI\'wg.c3iGL X绸c#n# {P\pB>4c6uFRn](Snܸ{c`-Wn&[SW)BPfUMҒDMq3%R60j} rjZKlFE:+LDڎsS:MZ;As,`rkSb:*$AisXt#279 q9ʹ XQFUT#dl`SӘgN/_(ĵXvI~"BB6GVcYR4: VCUCdn6A1a ](i*Uw #k&umR׉(8kOROHx"m²B4%nΨ}#qKT= I~7@bvR͋AaU&oiF,2c^mX3nFĠVZq=D*F^gJ :ql7O|=5[L%r‹YNROG&>˗!iRD61cCJG%nثghP>Oi9'OSܪ0Cxݸ)Ob<,q<=D:k n;ԙ]U=1McIL.=XR0 +NALbOAeIH2f?{qJ8GWSL+ڇ#H.K&̓Ycqrt%lI֫n5rGd-z5[am>:RE%UXofӥf>ɹk36 ϦH 2I=MtmCֳnmi +i!OL4ͯtӤ>J4%tr~:mF'gUmڂ{]Z|[EMVOz!aNBqPD,' IBqPD,' IBqPD,'L[9T>o~..\>Q?TӂϸNy=\wZ.~n}wեE3-ΗGOTw8eXڵ;8yͅ_'RNZnN~Fת3roMwS뙍8mkxa7SpVkqU\C),ˣ?OE+9l8޿gU_]k t- )c n&55ƣ(0ʅl",Yxכ8ߗqt CL1F W5|ߖ-dh\o%fHlZN̠|\.CX6+G+{oܕl4x]fX¸I[}f6S/OǞXQQ둾ue"DDz'uLf}왽8Ӆx;r8C[C$n7ӭgDKY]N8Y5AKsL+ ë_}:t2+d#/ɐdIY0V- #a e P3Zq@\U0\<7U?q$tzb4J3-NDgnU iޫi-A'Up(G@kfٝD^D,'b8X(N"$b8X(N"$b8X(N"$b8X}(/몽IENDB`mozilla-vpn-client-2.2.0/ios/app/Images-beta.xcassets/Contents.json000066400000000000000000000000771404202232700252530ustar00rootroot00000000000000{ "info" : { "author" : "xcode", "version" : 1 } } mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/000077500000000000000000000000001404202232700216465ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/AppIcon.appiconset/000077500000000000000000000000001404202232700253435ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/AppIcon.appiconset/Contents.json000066400000000000000000000044431404202232700300400ustar00rootroot00000000000000{ "images" : [ { "filename" : "icon-ios-20@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "20x20" }, { "filename" : "icon-ios-20@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "20x20" }, { "filename" : "icon-ios-29@2x-1.png", "idiom" : "iphone", "scale" : "2x", "size" : "29x29" }, { "filename" : "icon-ios-29@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "29x29" }, { "filename" : "icon-ios-40@2x-1.png", "idiom" : "iphone", "scale" : "2x", "size" : "40x40" }, { "filename" : "icon-ios-40@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "40x40" }, { "filename" : "icon-ios-60@2x.png", "idiom" : "iphone", "scale" : "2x", "size" : "60x60" }, { "filename" : "icon-ios-60@3x.png", "idiom" : "iphone", "scale" : "3x", "size" : "60x60" }, { "filename" : "icon-ios-20@1x.png", "idiom" : "ipad", "scale" : "1x", "size" : "20x20" }, { "filename" : "icon-ios-20@2x-1.png", "idiom" : "ipad", "scale" : "2x", "size" : "20x20" }, { "filename" : "icon-ios-29@1x.png", "idiom" : "ipad", "scale" : "1x", "size" : "29x29" }, { "filename" : "icon-ios-29@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "29x29" }, { "filename" : "icon-ios-40@1x.png", "idiom" : "ipad", "scale" : "1x", "size" : "40x40" }, { "filename" : "icon-ios-40@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "40x40" }, { "filename" : "icon-ios-76@1x.png", "idiom" : "ipad", "scale" : "1x", "size" : "76x76" }, { "filename" : "icon-ios-76@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "76x76" }, { "filename" : "icon-ios-83.5@2x.png", "idiom" : "ipad", "scale" : "2x", "size" : "83.5x83.5" }, { "filename" : "icon-ios-1024@1x.png", "idiom" : "ios-marketing", "scale" : "1x", "size" : "1024x1024" } ], "info" : { "author" : "xcode", "version" : 1 } } mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-1024@1x.png000066400000000000000000000547001404202232700305340ustar00rootroot00000000000000PNG  IHDR+ pHYs  sRGBgAMA aYUIDATxݿ[\ٕ/-2g@#A 2;dx"'&2OƎh:2gl2PLWzQS{|}>^߽RJ0~' * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * oP~o޼I@Pkeee3Ys{M pww۷oG?o(ǃw Nkkk (?\^^~^]]9 G>GeWOlD0,'O~6 ^~s?磟%Ďƨ菟5O#NNNF@@GNt}%LOShv>}I&`r@;h:v0(={6N x#Ђnh@:|Aϟ?w t&OD+04>&-D1S7AB@ ¿n1$p{{ۭT7 *WSo}}=z/8@M{K0bIэ5,{2fQ4 gf~yr?6 V _l !2Ĥ(]FNNNFD@V1Juyy677  + c^!ܸ,(7F)WDL)wB(:90 "j= BZy0K@T+ IYYYIP g$`0/_Lkkk HA|gϟ?O_/M7y ݳgO?_Noߦ9@KГἸH׭}14΁os;}@o4Bt]@/bډ#310` {{{ h4^8@b?Z8 `( ]r,,,L'>K10~@Wt&.;݈ϒyt:Эtrr`Z;oVWWӛ7oL?O]@L%H@Ni8b¤rLK LKDbQGn#f?NX^^6tZ SlQ8~m(0[,tqfOm [ [YYI@[0hw͛j cXb_yNq\r- 7=}Tku0|ȟ.ơ7]C_L o |剝߸2܌g/{ѣG+++rk~3"$]]]Q??U,F'O~z_K)|VW' Q?I߅" @ ~t|JlmmD(g!Fz(#0}~ n/F?K9{Y__OpDع?vKq]0[0 EDܣ}?ۣ [ tA?UZ[񾊙˕|J(ІܷjGG@7P0:1^GE{ըU~_]Qg0{0F +vc'$,v?@Fw;૫!FaH@666#+ZWK.bH]nhZwvvя! Cؓ^w+@Egk߅Fyzz61n_n:*{,~w;;-p G*[V:88Ht1tT,[+wJ7@|bw'Ot+~^JtgnT,+wvv6:NW P7@617oތ IzqqʔbrRwc{{[?(XwtR^jkP/@=z\%sz4Bt6b` drTH@LSNOGg@\qيv';N =~81#_v!re:LKhϋ.w>8;1e, .2G|GP=#E{Q899I>X?>HLMui,..PY :5P@E쎶,9 "~;ގP.Xsuu(ׯs+@]oGKyy.//!C_,Ц@=1{|q\P` ")ep|'EP ;}X|7CP v"KF;?CJq\vP@%("%iw@=rM *ao| MTB0>ctp #_@o&vss  `---%c?* O<|0Q|LP S!|7CP )˧`|!0E\+++ 5@%޼yjLvP|cmJhGP.vnnnuTD?~(ӣGP@E,Βimm-1 uT1E,yyN"#(Y;o߾MCP_+++\]]%"aΗduTF08cD^obFd2{y"/&5pfzBq%%}}h/@]tTɽ|2P'@ \p#9?@}L읞&&N d`gg'1q&}@0P/~{{{iR,,,İw{&'R9 00qz>z.P[X__WTlee%]\\$?uPׯ_'m'~lj]]]%%]n4n4t$P/@݉/_&Sw~uT,Wtgkkk4n2~t#>1z *:nB0~tPhNt+/^$S#EA(N(s@݋B`u~h 8c=Lv` k@0p@@w^n...=J@0E_QGųg#xU#ц}{{膀Zwe766@C#www dkkk?k Ft(hÉW -aE 4#q ZV\gc{)a@C10l6"y9GKxGGG G ع1va+dže"pGWF垳ɓQ}>\ k#DSW@xC#'"P) -€nǏzO ,]e["8??v~㺾Q?㥽??>G(WD pss3߾tt x---~6b<k~ 1@>q{|*qVp ov$ ;PUEߤ fqvww/Cc0X2dq>}Lmoo'0Yq8)!oG`:,?իWZ(!oQ hEQC??m?2&hK`r?B?P9?! IS`J)Z#RC9QC9@EPtJ(,yN@P_]]80Pܿ ?})?'}0`)GGG>#P@߭XD;{C(]L8"  SaDc,>&_i{,xfyꮁ n3|<ŢeVF?Kgi؝g#̎fgL̀q-SG[r3 D?E׊{i9|]˫bw/>~}Gw~}޽wq) 7>̗y;egw ?q˗>s,>@uEiV˦ߵ, }9nLF;TQ|\-7:!>s { ۤ}SLo3{.I!ME 䯋>fO-p8Bg>>&њhTt4aB?vcohg\tВE #t'ysF|'D@ Z C0B'O0(㼯 h0?c",?~lf">&E]ԡ ݱ opʑS9@v(,5,"0y=zh&c'}4ZZZ[G[WS8Z(R#~!d(H:P=<sgksMqee%PC={bm``^|pK?9PC666a~!@tOP:ӢQCG?׻]8Cy2H-n8::JP*ŊŅ,P?+fݍc Jd E2ʣ|Ő3?|[("_P?+f$`<(@ 8A] h'B7P"3(JW(eLoss3$(bX@Yx%`r1puuճb@1P?-)azP@(!* ȟae~;(#d/v(bȟEŅ:A<(uGY#d Ț AtvvvЏXrrAs Qr& K*P?!ց31+!7gϞ)!s(C Up g:N|i^__' _(GpwwwW~gGvk &χl ua6 H>ft\^"&~ѣG8K@^L/Gz .E^^b1rtt4*g {>.nO@O׺9E_hjv(~",`a09߯&LmK϶ 3 wv*-`v}.//ZL!S yv(v?n A@⌢sDؿ,, `qL.a) E]=: W~nLמm]jXψxVX{U~WYGNE;Ů+0 ?wrraOt7qMX?(Ҟ+0 )'KtKg}{++++3»> zDw̿t# /_cii)ѝAGߍX`D7b '}y.Anoo v.E?NOOSLqC ݊uӋ}p^8)Ʈ{G|w?݋JY9ZI@/|iu#*;;;~D*#:4t5ݰ/zb  C_|+'X4}1ERg:1 0yUx2 Lk:\\ tbV, ̟n5:`MMt.t,Tf#vCGlP@ӱ:ѣ,Tfl`>訛t;oIEil=|0]\\`,// gD EsK:蔳Jӑ^,[(<88،k:%\(Za߹E4˙n<::JLښ 蔳J#]g 71X[5Ӵg??S]q 6Ϗ.2yIdl5PNtuyM{1pii)AWtFB9-qBΘ+hO@[y4dK:cJi{*Ŋ yYמ#tI@g].y oB2|eKKЁ[!@ LW}o򵼼nj!f-..&:⿝ׯ_'^rk:. $)(b/zYl~HJE`/e5Pt1] \]]%K8U&]l+:!hG[tmwsڱ֦+:!lCL:7cFKtm :K@,VZ&s`6tB,Ton|&QXحȋp\֍Z/YMԲ6]=z(5 O@'㱃\@eZ/) oz8֦+:^yؕ ?2 ] ^㻹LnHWt—R;kkk<~8$h"v,VdezIb|tE@'lll$]O|7+]Љ(%@|G%@c/tE"@h)=tE@'"cRإб/Ǵ"To:. drةN"{-]ЙD;+exY o:d{o߾MӧOy]e;. =mpP(ŚtI@g=uBXS;99ILٰPOوgy:ӱ:bpòPO(tuÚ>\ qfizXQ#[̯ 0 u݈tkzq~~N+' |tee%ѯoczE@/tt#*/_L#(Pq6`5ĚE kiKKЃ[َ/ݺP(݊ u݈}m~:=}41xy&%ciiɬĵ1gg{{;эX)c?}@obbnw"  ag{SaX':ꮯuuHw]7"L:T3[v^t(_w+O'$6[I@o\=!tPaDFk])r}+5!@i0C^Wü'&W+h2I|]bQǀJ:ψxV(hٳgU?|^Y+&۵8v(L κij"ٹbݱP!_kE'? +v^O{v|9~_ A@@XWSO~iOO!Y׿xvD'_"~϶ A@ MٿfעK;О8 kW^%W{]G˿@}:ʃw zP38?^9ŚDÈrf@b#ŋU˱n ĺMwC 21x`___Wn¥XY ?̖.4;\g]وP]P Dy= pNfQsϠE xإHSzXov"Hl @g+\-]]gG\Pt0g+ ص& v/b;~ly<5x5]xي Iϐt0(er=w X`=y.W& lC: 9_ŕ M,>CE,L>}:Zڕhݎm%B8.Ck#PLKb? It-€ňKXD/;y1_'Q5!B8AGF?7?fAL(G\4AdG#%Qϥ{ q8Ίy<2onn>G@^P(s'?q:J%HKΫWNyq (V&C.//GWyQCyFT?2bEQ/f2 ]?%3E3b dFeY:dCX ?) ȇd. azix /(ɉt s#*BR@~P8nXs%B ,(Lc/jj+?dFeR3t0,QJ@>xL8_&/x9@P- (K-u?%-P-us?:EPPhGS?~xs\<?#WILc_HЅ [ɓQXk-EvPވ_&vtX3Es+؏G;ׄX+ A{h)sEJs.s~!@ ɌP`??LF2qY̯(cPhtxx8 3?Y8>Gk|B?>WFiMU@bx||<: "e.thKm?->5@ @|i_3Bt|zwwW?tH=|_6刿S=qN'tt*x+޾};Z( W,Jcρۿ[_?>Q1Qg 9;6^777֜sY?)oK?нw^^^/.߽ߍz|~U{˯Żϟ}988}^0lo~%{F%<3J~9],r5w=gLף9p~~ _uܮ|ZaxX,Ti-pD75(!?*ku7tC#EK!立+K v!_mY<']t0{@$ggb>E]Bפ}%G/L@F-m9(C6'Êgi܈Sa:CjZ?~, |e WOUIi0Y伫X|XfaBoc(G4&hgg\UC s b$^o߾͂Ģ13og_{7?o ρ ,X/\]/V}-LJ?\m<_( M)4t jȋEzz?F$T PCay;9_S  _L(& +Q%,@6^|?( pppP?@9`P&?@Y̔ʤ(QCe01_QK\eRMP&?@K0gϞE0 bee%]\\$,{a:>>N@YE@?==ʡ?zupp(C0 &CyM@?&/,,$ ?):˗/ (!3qkk+eP :?EPPs?BP/L% WS `bZ #LD?A@C9>>N@'|)#1ȗ@+Ϟ=SC|fM`lq'?ߢQ0cg!?iuu587ŵS\ɹ[tMv!oo޼I F_eCt-:"P]|b78ݥx}N7^ZZh9;;K > >Bj3 (!@ǏG?# _kkkWS:Q!A~;??k 'OȔ.DDawqqGShagτ@+e trr}1Nzib=>]Dիvt< [\\,@\GNk/^德WdG ۝ $tW o[>P,-m N/n>6hIM>xcy}0bpbB{µhZta h_aj.Țn](Lg' \\\rQFyy8==5{G1g"=]G;Iޣ|/ !B1s'O$ [ *~gtEruI3!@3w_{ jDwӉ  T"|Gj qP0wtq |L̝~!'>|(k>aG|qύ Td)o8>@=KKK2T._;2$JX䷣,vnoouTBЎ\kkk}6P@%"WG% `|?NI|'EP;}sn(OfOP@E,L?|"},yyʸ gϞ%=G#kڋ9 @bkk+ю*co2;;;rRkkkvR;(pg= P/@}=QkcNܧ::VAb?[\\~#$EAz||W\PO/vT׉Ek˗/s;fz>uT9^tٳgioo/ yP[s:pC1byy9P/Svk?$8Ow I;M]utK#8Ѓi?t`gQ^\\O&+?8У5v|]w#+&~˗P:Cد(pÇEPK? #/ 8㾵j1*ݖпW^%FbNa"Uc7@3cGNn!ׯ_']uĮ?/^$h#aEhvO}@{:N Da˼ h;Dxq|$f#CbO9رC1Qĕ2Q>bjt+v l"7 ?>Gu{{k` 4Y]ﶲ%F;䫹=W =]'[(a#\ X"Wpu_}W1h~.--  ^__7E,/^}ns|.(K/vqġ q_t@`:*]#?$[tM _vI+&+{#f &O^J''' |A 2_'oND^h__'1Et80[i& D Vimb{2I0?UtHwJ'Z/^46j(ɕR94jiS0w`",mJX$_tiG]^&l yGAeM!Q AnضmȇwE+fr_ 5{qQ,ulX W DŽ?Kh4y,jJ g}O}(6T*EPOf(CHQkЏxIENDB`mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-20@2x.png000066400000000000000000000013211404202232700303570ustar00rootroot00000000000000PNG  IHDR((m pHYs  sRGBgAMA afIDATxX0\N*(*D@PPt@@9O$[;\`$K&qxwAD?Ts| $Z-zq~ǭ,D!M6M$kn7 BnØJNSEu!2~ocL%_%uo}&m3}dxi8!VU"QBDP& "eؼO–Mtd:2b0 }d2 J8 Vimb{2I0?UtHwJ'Z/^46j(ɕR94jiS0w`",mJX$_tiG]^&l yGAeM!Q AnضmȇwE+fr_ 5{qQ,ulX W DŽ?Kh4y,jJ g}O}(6T*EPOf(CHQkЏxIENDB`mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-20@3x.png000066400000000000000000000020741404202232700303660ustar00rootroot00000000000000PNG  IHDR<<:r pHYs  sRGBgAMA aIDATx횏Q217_ZZZZvVVVVVVTTTTpwc\Idoso&3]K6o;JLm-CCxt47UUggg ndD&4-Ǚ <ӓ >&k8 "ЇkMU !k԰r/bj֓0r[f}P6+D\ 9|Wj\bytM! ~+WRX aa% Fx:΅ 6~ FW-~ܳ .Lȵl@PGʪ?>>ϧԴMhxuuN,B+JElT-ᆸ#{ƞ#|}} bq4}enlc+uO7w\y|F3[o ;#gHQF*/CPelLW:UŘa'}!&#er]R1 0B+#WܔąދaAV16RE7@ +IY;/qrUYu%}ieè2t!EGs.mU@+,l 8\۪r5VAA.x̀.}5DUiv̈́\qώ-޳΀b҄O@LЅдD+4QYk4fqY3},E2s>`ynΧ.d@A&HAU-jwb #PF<rq2 RϮ4YJ EcT$dÕ8sD|U.""ےOaQ bIE~fb%,}_ W\'b@KI CIG>HW1FƜeGV=GU[* ا41i*V«vt47[Gmo(hIENDB`mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-29@1x.png000066400000000000000000000011241404202232700303700ustar00rootroot00000000000000PNG  IHDRVg pHYs  sRGBgAMA aIDATxV0 uO70l I`V`9?\m%IU&΋g 9z/}3p1rtU#HHۭaټÙV+yiZVHZ}$;BP`!w٣LD *vLPө!Å_bcVa|."¨zUIQw6UbQAYIߛ l bY"t\Jt:%&d"k`~N.w9Om-ڜ@4!:AS!=N9_-<<L\̵s,qc__N*ԋeˢnb!7n0g}(NADlVRmSls]JOD/oVV6"F9@FU; 脶zs9*m~B:adJmGïiB5VGIENDB`mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-29@2x-1.png000066400000000000000000000017171404202232700305370ustar00rootroot00000000000000PNG  IHDR::J( pHYs  sRGBgAMA adIDATxZq0Un(:*9gzeKoFX,J@7AEl ѲzZt:^ZN:j٨W!l`p\Y&^$U4p~S8%3Ʃn+Qn7A\;'r2hM(Q8z H#H@(đ6yn^!h4J QnMR09 xbIˆLrD"DAx<@$x%+B¿J9k8(|zU`[^8@UB%Џ>$XQv Ht62&X,Xd aHڴt4I#<(MA1iӢ!\ O#I"5mq"]M@Ie$x' Ls⑙6v]Vy;OB8[VO}I6ɖ:Y}~~fdnOڥp—7O:Ξlw gb,fe!^WCHb4xAڐ\f^CRզȪBЇdC;,v4pܞF(aI&ue[ nxAbv;|wL3bR lBg2$mm:j8B ۂrcت|DrsT\(U۴PW..K$@~F wKPCeC2Xl%O/4.qθ毅GsI]d!9`j^IpWm8An*ˈ4= Ѳ"Z6 f)fIENDB`mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-29@2x.png000066400000000000000000000017171404202232700304010ustar00rootroot00000000000000PNG  IHDR::J( pHYs  sRGBgAMA adIDATxZq0Un(:*9gzeKoFX,J@7AEl ѲzZt:^ZN:j٨W!l`p\Y&^$U4p~S8%3Ʃn+Qn7A\;'r2hM(Q8z H#H@(đ6yn^!h4J QnMR09 xbIˆLrD"DAx<@$x%+B¿J9k8(|zU`[^8@UB%Џ>$XQv Ht62&X,Xd aHڴt4I#<(MA1iӢ!\ O#I"5mq"]M@Ie$x' Ls⑙6v]Vy;OB8[VO}I6ɖ:Y}~~fdnOڥp—7O:Ξlw gb,fe!^WCHb4xAڐ\f^CRզȪBЇdC;,v4pܞF(aI&ue[ nxAbv;|wL3bR lBg2$mm:j8B ۂrcت|DrsT\(U۴PW..K$@~F wKPCeC2Xl%O/4.qθ毅GsI]d!9`j^IpWm8An*ˈ4= Ѳ"Z6 f)fIENDB`mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-29@3x.png000066400000000000000000000024771404202232700304060ustar00rootroot00000000000000PNG  IHDRWWql pHYs  sRGBgAMA aIDATx\Q0 ?t@PT@@tTTTːn/eGZvf<̲vV~qlIΏR&fr "5Dk A!\Cr Wr666zHooo!===%<2hsss v~ ߻{r1juLCJ N r(IH݄s # CU>Ӄ~5'qzU[!-J A!hzzj,%+tҨ:[[[5 h "YK%_0]tV+*-޺ {AbRA<µ rQQ2XRGDUjRWX\ۖ\ b.G#QBBsgՒSO {~~\\̾CXyﯯ{XrG X\%UG].& kE*9S25M^BO7h^%"*-rK퓄3ҐuЌݽFfS Ml|f߂f9B}Ɉz+ ŠX\Kl(lya1hr쓖XS郞J|Y? ÷\]b.NAvs͍pLMnFik7ׂ@- sh)8w ; q-љ94Nsh sܒ+걖jr܊;ιpqpٯzJ478:xވ#^G(G4$w.^1A7-H49x QS54mo!Emm8Rψ"5Dk A!\Cr "5Dk̺kIENDB`mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-40@1x.png000066400000000000000000000013501404202232700303620ustar00rootroot00000000000000PNG  IHDR((m pHYs  sRGBgAMA a}IDATxX0{zPt@@Pt%PtT@@9Hvؓ7|QB#hO jxAA_WZ+ Z*u!7vaG]*# JB[peh5VE3RL~4ZIm1(NȎe*z6hPϕu/fi5$~Hvk!ӊkoX=xIENDB`mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-40@2x-1.png000066400000000000000000000023641404202232700305270ustar00rootroot00000000000000PNG  IHDRPP pHYs  sRGBgAMA aIDATx\M0v``6 `6&Nnvv _J;۹klĉ|a`LAk1"( Q(D!PB@! Bk2驹2777|z6trDCU///f(guqqŘrr /=U[o T[15zI#I : bƵzGd2!-eE"+PBX#\7RDXe6NB`Vj򞃬d0)gggsNNNLnޗ[ЏXzQc.$qh)P;A3ߔ cKNH^jJUF,f nq̮eR|||gycҀ- r[,p> 1VYQh9PrVߘmJ@L'm v|'XInQAv(v\M/E'Ά—F&Xq yh }0f1e\/I9~)\MPPqaH 76|pcB̉cm*ITI>DJհsKG WiTŕh*N}C +I! ]yUd)ͭe\i$&”>8ذ%5]^^ȪFZngrB@6j+vϢ d\n}ػ"򩗰qra9Dߗ|*l 7%y*zٷEnwTi-wYPw@! B( Q(D!PB@!6U [(IENDB`mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-40@2x.png000066400000000000000000000023641404202232700303710ustar00rootroot00000000000000PNG  IHDRPP pHYs  sRGBgAMA aIDATx\M0v``6 `6&Nnvv _J;۹klĉ|a`LAk1"( Q(D!PB@! Bk2驹2777|z6trDCU///f(guqqŘrr /=U[o T[15zI#I : bƵzGd2!-eE"+PBX#\7RDXe6NB`Vj򞃬d0)gggsNNNLnޗ[ЏXzQc.$qh)P;A3ߔ cKNH^jJUF,f nq̮eR|||gycҀ- r[,p> 1VYQh9PrVߘmJ@L'm v|'XInQAv(v\M/E'Ά—F&Xq yh }0f1e\/I9~)\MPPqaH 76|pcB̉cm*ITI>DJհsKG WiTŕh*N}C +I! ]yUd)ͭe\i$&”>8ذ%5]^^ȪFZngrB@6j+vϢ d\n}ػ"򩗰qra9Dߗ|*l 7%y*zٷEnwTi-wYPw@! B( Q(D!PB@!6U [(IENDB`mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-40@3x.png000066400000000000000000000031551404202232700303710ustar00rootroot00000000000000PNG  IHDRxx9d6 pHYs  sRGBgAMA aIDATx흁M0 `00lL100lWTzKUB[⢒kѬ>==UNlSV-Յ;љ*ɉ:q#FE=2U7.iKeߛs^fa7*uT$r'P奉q!04(;ŅϹX,f_?}37T1Xooo R&5\ Y".wwwewv`ۂC8I 1'0IK\0DN0Ľh9gd.;Q% x EQ1P7.F\Jm^!,ӜX٦0J3IQ[O0?;&מ➝k9YU9RD>([wԂWWWeir'ah^,~ǘh>;X囒(gH䈋lnCg$MIr'?>>HV ީ5T0~M ƓPrA-e%| vOZb*4-O(omqKVhUйB[\-rz*Esoq%bc5!MHqH75ַ54}CASw8,wԵ{m݉P777~ [.`yb =MҾP S@}hR7iJYJ)6jr9!ou5w eύNB ߤ8Ei<ܵwe1S0wTV6k סcoRD~~~,CBt=oGc4aHz2hْttt@9JhP#Ё70ȝBM i.3̉(t2p Nۤ_ɜG;O8f\E`ZͧW;ᾀoB]-|LL\\3Iw0k.m5nsFn;WEOތrf͜.mwƧ{) ɚnڮo&Z\ǤEn!%\pșnoc,[muVp/`f.[ML]_qq aq}*.dJDZ+Ȕb[b[ ,IάmuxĶ:Ķ:mumu>muhY 88q.0,q.0gmu&24mu&9Y TVmu ܚFÓ~qpn3s3ufUk[ 6-=*&0ԝeBK3ѯa)p-pt>8"vN9!sB` ;'vN9!sB` RIENDB`mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-60@2x.png000066400000000000000000000031551404202232700303720ustar00rootroot00000000000000PNG  IHDRxx9d6 pHYs  sRGBgAMA aIDATx흁M0 `00lL100lWTzKUB[⢒kѬ>==UNlSV-Յ;љ*ɉ:q#FE=2U7.iKeߛs^fa7*uT$r'P奉q!04(;ŅϹX,f_?}37T1Xooo R&5\ Y".wwwewv`ۂC8I 1'0IK\0DN0Ľh9gd.;Q% x EQ1P7.F\Jm^!,ӜX٦0J3IQ[O0?;&מ➝k9YU9RD>([wԂWWWeir'ah^,~ǘh>;X囒(gH䈋lnCg$MIr'?>>HV ީ5T0~M ƓPrA-e%| vOZb*4-O(omqKVhUйB[\-rz*Esoq%bc5!MHqH75ַ54}CASw8,wԵ{m݉P777~ [.`yb =MҾP S@}hR7iJYJ)6jr9!ou5w eύNB ߤ8Ei<ܵwe1S0wTV6k סcoRD~~~,CBt=oGc4aHz2hْttt@9JhP#Ё70ȝBM i.3̉(t2p Nۤ_ɜG;O8f\E`ZͧW;ᾀoB]-|LL\\3Iw0k.m5nsFn;WEOތrf͜.mwƧ{) ɚnڮo&Z\ǤEn!%\pșnoc,[muVp/`f.[ML]_qq aq}*.dJDZ+Ȕb[b[ ,IάmuxĶ:Ķ:mumu>muhY 88q.0,q.0gmu&24mu&9Y TVmu ܚFÓ~qpn3s3ufUk[ 6-=*&0ԝeBK3ѯa)p-pt>8"vN9!sB` ;'vN9!sB` RIENDB`mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-60@3x.png000066400000000000000000000060351404202232700303730ustar00rootroot00000000000000PNG  IHDR=2 pHYs  sRGBgAMA a IDATxq9uW?dD,DDͳ-3^3V[~UVFZ6N744 t M&]ACIWФ+hh44 t M&]ACIWФ+hh44 t M&]ACIWФ+K 7oLo>7ɿ߾}KNDZ ٳg& ǓI9JO>mJg 7DTFZF4vB5f^oj07NMQ#ֽ{Bjl3VM+^x+TcիW֠ B5\-Gm>})TcM2Osfj \&-R_Z;'@62?dL33><2'= i@IwzNd-A~zr/uXW֘c'J*Tc [%{nKL_z-@[]%#$#O}H[UxK)ٞ.15ŗ̗pxx?88HmPw`MI5<1b)GGG.8B]5OaIDZrDBCR$&܅dnfKh3$3<*Gh3潩57nHt }tpΝt>DNGH4ׁ%/tzdF$-VUZ Ҋ#R0BkH#&r0eՖ4XvAQ̲tI[U0_LJavK *Wc.13B5Zœd8F4Ӎ!-c9/_Η&zqePs '}V*=eGiqKguGpzD3ϟ'0*Y ӡH@[EQ̌%HH(fF2K*Kja?ǎbfZ=Uo›hC&bl` ZU2j˗/}L lW~݆@o߾-".hadF53cŖ~I ,H9Q?VO3(7fj/mOc`DZ0Lxxx(27̆,9,j*6vzv?}M |#g5)'?ǡVyi#T4J{3*+~,.F8hx(j9f8h{*jyFf/+7F6ƲT" O4NnQF㔭qqP nF~`I=+{5zG&B!C4 +Hw5L7R ؜8:::`W9fgp2]{nq!P a ҢS?FQ@~cKͅJ_`/JJkK$eYix 8jSK1{]ki_"n}44b7"qWarHKW0RG1.<$RT4!Rm42-I$iZ VhI9,ZVi,i]&c6X>>"\˶PQW|N\-C_ZUeF&pGs=l}iuВ/NuVB`4a] yppkhYk44J`dJv)0Y.k_FV4 -F2F9Ut[zr,8 x- SCZ 59G$S$`\-1sJZi7K% KBHTq< M׭9JM` ɥ mXL+4khD(~939$;U%D(jY:J[D[T#E~k[1DHp a/Wjx6L]Օf}0Jk8`WftYF(jɤ ZnfĉxYtS4Ⱥ&6jEˍ2.l7/A ,ɓ'l %q smJ 悖m025h,W9Fm`649BF3BQ]jR޷_vqVPmC_G:–/SkraK8\#?mxPU01}y3YJu1 Y,,j2dG>3MW5ujhp Xs㲂fP5#B*TcD|Qa(VfEn< O |hι@(6&60LL׻3?Pi1#A{TVWnҬDA[Pj PiʲUŗLD9]2ߔVH` =B UJV`jir}L IϚ$tSG8@rkhaá }5 }t&yfФ+h3+B4phh N"]ACIWФ+hh44 t M&]ACIWФ+hh44 t M&]ACIWФ+hh44 t Myq_nIENDB`mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-76@1x.png000066400000000000000000000023261404202232700303770ustar00rootroot00000000000000PNG  IHDRLLǗQ+ pHYs  sRGBgAMA akIDATx휁M0`&`6 ```v|ճT>v,$;ϥ{Ƙ4DiPD#LF0%aJ4”h)S⯙ٙ9>>^|̼mf^__}k*aonv}}ݥ2i/kEa___^2pӐa'''^===n-aΛ'yay IB4\oLjD?Doyΐk׮Y%[wXJӚjEg(]+???ޕAGGG~U ,F r\dn,as(lv37ID9777ޱ@$"@f=ӉQJC%B+x YUF*\@J!Z'f@,B:Ji)dY$s(9#s\8=;KZk3ZKrrc %d + I@.IHc)J+( "/..A[i%ڔkY\GiBOo,Zy1Z,+YFs54`v H[VD4a???+aubAI) @SEt+]@)u;nr \e4~ Q](n; DB ]xXa"v<(eKҳJ JA-4;0nT[4DƍZS#/!WI.֞|[?|d5>y2YiY  bP\c bz!bč-3c^زDͮN*.VH/X87!D=*Y^0_\4aK@VRRE4*R?R)V ΐOTtYx@+{S"y kuJ֤j+(P5|P%/#8/na҃>Ou~@:C*vZ(y9[ uYY,*[墣\2FBZՄpP`ݛK gxkY^Z t0LMA+FQD#LF0%aJ4”h)1\ `2?C8T|eIENDB`mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/AppIcon.appiconset/icon-ios-76@2x.png000066400000000000000000000051141404202232700303760ustar00rootroot00000000000000PNG  IHDR ! pHYs  sRGBgAMA a IDATxq; ͛::  @**H:}cfY.wdZM>ndVJi@R( T UB`*!@XJ,P% T UB`*!@XJ,P% T UMAܹݻ/gN߿OaB`{xq:99>xk!/_O>مo/^}mǏ5<=ssXFYa[զuݻw;-pmOd[ͦh:,~H!q}\\\J檱,ÔT ^q=XZE>m.l^S{RЦwpm:$!LFȸFqM2,W !+I,~ml0w yQKe^JlA=$"v%m^`Ed~:JO^Q]sE  tX}_~M&}V\5e;*LmksXQ }:?e0( pE7myeQc UcYSJ*&ȐwUc]KqlV\D ƺ@{<_EsXwB;:n&+?-c{yO⡽%N)Vpo<{JP;)~ͼg"UG(S: or]ciz8ֈ# QųE 0T>|*/5S"4x& Ŋ5Psc jMŎmJf KbrCEX;L,3޾}&;yݳTE($E2֎ZY/=rКURx[;lse+aI\-Pγ͍bm\S&=V".|=G|\Y*ʥοjpT4sa 6L^L]%Ei:T-.O+[Œ4ׯikHDx?%sB+XE`fTװFrht6B^X?"0ʈRC`[,[YZ$PuHo3QjZ] H:SR2y_HAs˛SU~. "5ީhU uPP sPj ͐H ˔N]GE;%_%+2nQ2LbD n~6֡ I"&IˇSa{n Lְ0>G>)$<&¬,-沲 QA<Xv)yEYւz(jݫΡ5TҒG+e+-kZdB\ӣIM@7*c7ewMqƳ9A*V?W,J8 #0 (k^;K~@fLB0I3} &X5en6!-V^yQ Mg!QA`ӎkCnj"#^kj|()A(Se.[IyO' W!Ji n͍F,Ʋ 1?n>6>EղUcIY!x!4j&7j$|*>00'(5Adk"ᶆ,iP檱bU(\ZPҒjlUz4f[4d5*.n\5VjĵdjRa:(Tj0`z+Y5xmXmOUs ZfPbX>oߴPmn  tlSCjlNaJ;-`#Ҽ5Pmb'''k&?jZ?eXᙞ7Is?F*ag:cM"2mxׯ_o\Ao߾zkO" 5 ,`]իWk+!⩹~7<6Pu5ʆ:yK>-` OO>][8::jnf Bj}h)#+ >T$TݻwӨ +ӧOթ8pUugXq~HÇE?/qx%)p$NA6W)3ÊjyÊtɓrKGaYv<ܧO(02a<ȡRF Y@/jnY12\*P5՘}B;4&֗ ,`]rk`EŎ]"j4z]sճ zBֱ'վ`oÝyy{{0'=S-?m "a&qoC ]f&0I2}/(a&Yfgx.z*L b_;Af197(q\ 6%G2lթn( 9b*y&8)yzs\߀~wg5{3*5&<<5؎i$m/$oO5?-d C p۸Wuܚp*i\n>f7# Zj*v[*EHRڜVԚkV2A$-MC^)@֣1TM״nK,u:5j{R<}nT,jnġp-hO|"K[+Jd{e6 NCUY jZ!*7Kx|Ia}֖*5TXd_K|.)̰6V)RɖQ~< 5̥jA*~Zs6<@ք[]>V@H-c!bMncI ަgQ7q"k i̅':M%,U/gto}1%T s2X]50ܜY!*ŋ6f7#bP/z*v8d E&弾G-3L5)ę?,kEnE!L!CJt6{Cˤc=EsM+̳gOuϹܭ]$wpuWJ~aΎt7,er;cE1sdXJB VB !@[^lcN{64 .dnR-هpQqd%M6xV`zg%{j^jfM-9B լ,C[ >jBlf[zM-Df fNWg\X%/ţ$ddB}l+i\R[Ov, *R-u fgi}%EW=GeXqҺ8S"qu. +#!Ե`X<{OKgi֮X5KWGixÊҪ,`.c[ܐPrejf-PZ?o &&Y[dj{ڊ!Jӈ5XWH`e˵rĿ!ڤ9f -`ͳx'zuxhZ|p޿3N0gْFe4Rj͌0ko-`ڜ0/PmnsTiz G`j]ƃti>fPnXռztj;tpe"Y=DBcUpj\$2$NrKXS)hQEHS"q Z$NA)h8-EHS"q Z$NA)h8-EHS"q Z$NAԛ%KIENDB`mozilla-vpn-client-2.2.0/ios/app/Images.xcassets/Contents.json000066400000000000000000000000771404202232700243420ustar00rootroot00000000000000{ "info" : { "author" : "xcode", "version" : 1 } } mozilla-vpn-client-2.2.0/ios/app/Info.plist000066400000000000000000000033071404202232700205600ustar00rootroot00000000000000 CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleAllowMixedLocalizations CFBundleDisplayName Mozilla VPN CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIcons CFBundleIcons~ipad CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName ${APP_DISPLAY_NAME} CFBundlePackageType APPL CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) ITSAppUsesNonExemptEncryption LSRequiresIPhoneOS LSSupportsOpeningDocumentsInPlace NSAppTransportSecurity NSAllowsArbitraryLoads UILaunchStoryboardName MozillaVPNLaunchScreen UIRequiredDeviceCapabilities UIRequiresFullScreen UISupportedInterfaceOrientations UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationPortrait UISupportedInterfaceOrientations~ipad UIUserInterfaceStyle Light com.wireguard.ios.app_group_id group.org.mozilla.ios.Guardian mozilla-vpn-client-2.2.0/ios/app/MozillaVPNLaunchScreen.storyboard000066400000000000000000000057261404202232700252170ustar00rootroot00000000000000 mozilla-vpn-client-2.2.0/ios/app/launch.png000066400000000000000000000310311404202232700205630ustar00rootroot00000000000000PNG  IHDRH۱3PLTEj tRNS0@ϟ` Ppifx1IDATx r0PRd(mD޻îWP붔B!58b4vmܥ!仅!`S 1?@ ~t:凚֭ku)' IrSusu˔_bZ P EJpD_~=e^E&?\~3 61"x1䢄^ݦ\is盗bfKQ O6`88i*.-M&RdY#tE߮n|׼ZE-{;_9.pӮ0U_!! ]R>$;y-BU,nqݟ=wWxcBz;opzATOr)w_wm8_@uP:R:R:Rg}xN Sػa "$TIJEB /߿'eeKV}5y;4h"D?k(pq Jqo.uIJ\4J>d$+nb "1#KP-k &1rp /3d*i"13f@`dILPS@IHL  8E-Пq@wB3d&H,r `y>_y^] t&h0 q+p;!-/uCu[&KhzP ] m'['~A/s0j :gక'lq2G.VW tbl!9TEPIjJ <{[ʉ%@:Wd׷.zKXN7/$toqL ޽`; A֨yv0h97B 9]`2"4 FpSatt]͸ :|~eq/h:] Mm+=ԌA⥓,ǿ ݮs,5ۢǦ  {7M,x*h8@H⡨i*V '_-M&puUK](B/y|7(0 TF*ftw:й'.C}2oַЧl9=\@2|5 @\Ten( C1^"(ު:@ ;Y _vm|, 0<W-СMe/YC~"TU5K!d-ܬ2@u < UF1Nr @`O*@)3Ada 1Ս *EpWq47W9U8Ce@>cWUG^*r_!Cea6U JP{>*ɰ>TFfUY &'yY%L *`>b*4Up *JPLH"@9 o}{[E\ [ rת"P oO !LVIJBҬ$,*HDR/ףϿxج G f}{u0k;3_m>( Cm-!EJ xTBK\J?"!.K%(STBTd~U k("5:Dbzd cWo + ^5Y`֫,O[EUe#wLI ̵jR C/[%M d^R>R& rBRUFڠ UUuI!2UDܨlEm* ^ytP IuwwIoD#I%YB[!yAU@H즈Am06+ y~0(@m xVh^Us78U!y"; wS 8() ) 6h`v/&ޤ*%@0> L QAuKaT BS!jwW f0nkՂ@nV Um| ꀭ O!^mZbv7>?&y%6_n:NՆ6 $0*f ^Ԁʣ&fe"ߗ~x.\C$Vv~ۆæV  Nâv%/c~ s_r0EQ"cOjJ9s߳֋-1Os(f;F ДWx v *g('@KY βW D٬ZWpֆB@y+ !}BoEhpHAqׁ,j'[W@@@ 0f N:P&rO@z,L(/2?i&B%H2ӒNK0 +A OL@R KIbge(D…J{/ RwgY@KPWCԄ[A8jskqbEꂍ]+RWV\p/yՕx&>g.-#rF =B|6>sk?IlICdU#u"28{dGbpC@ am"# 0OaBCcƧ:>a>C6A^qa_P= |qCU޺Ɂ)EABn1Mr#b8Im , )y݅yK`k] -9i3-Xk )<4NNp|2`4:rQI7/5+RPr<YxH! ?:.`K?%@={s8S > r5:B'ߐIn c Cۥ"}[5򻼃),b9yݗ25!˛'GevvJ~ COnJnT4F~Z:A^0i)w p t\N^0LBͦFGFKDvbR'C,_TewţWW)gX0 Tt%"P'9 L&΂{4mdQddXQ0Pr A2oc`#r 1>P7-TdQjXuĂ/M ԤI%aMyV7駱4w?4H^R;/t$ݸv h3X(AYV?uCT_ '(Qpa apr O[uHuX|O|Xh M%L)kf'0:v"P`4^:޲"T)7-U\1=E*]:Sk1 y:G;Xo^ ޹@!Q >ĤGpoOLuZv"%H$P xJփT=\P~?}lX ؐۦkWࠆڃT< ]K0F`,Y-hރTǓS~uE%\Ӭl H4( V* '] S(>Hu(u]DSRo33k.(fe n%@qH̔Z#X(6.tNnz]`Ir2E@lk P[ZQ^h9 &%@1-G۾RH sm,'4F7"-( Y2']"Vv/p2hc3șPSdNT~p_)u9#00"3U@f /r )iKP\?oA^ X| 8^"H{R@@@2 PD@#[s.i8 $- v0 Es*X J0SX de)(KAMQv/cnVX nk,0)yp*)¦Ti0r>8ypW8}L`nNfACq-uY^)?! %.OOMfKBsq{LQ5.Yp00L5Y:7o$X">%b uaYƽx!Eq(`;&ݪU@$`#d7N2$ hg(9(o^`N ?;À[pb9Coz'FXP(?F<1>U?v h<[{FיP~shp{ تR OꭻfS#R*UdzG \R,M;ԆzΧg;JCiASCy+\g#*jGhj>ʶZd5T;M͕;kv0H;*«=bv^ ʺk"CpCc}@)EaLhz2J,0LI@hW?B) -0U`o)Zr`:;KRr/.KA#U슑ar݄m"W+2G{{k1U]p5Ե_J]>9C OHgInySd7'p7-"QHzV:񁜂‹W&xlQL Ы! ":eBr+#Jebr'+&G^@Ȩ`]pG:~? +ه{qFyt?Qj,8D|.%(ɒ7JRDG+cp\PAOSl@O}Pf]_F\MO)Q,]]9qw6#<ڂ{=wSa98;U?pp`ÍL?~t/t7Q?3Ouv@b5יftç /JSp&8o%@e%@d[ PveKJ3D 806R2v Pi/ ; 8;"Lۙv PǑ.SpG$y(>0_ٿJ @e^+Ӟ >15BN6N۾Ba\|'NMiOU۰&HHճ.S_#i6ns}v5}46c Pr%nAj9%@?.&܂ :F0xu"O\z8G '97 z7@#`(x`I?}鷽0d۸/po dRxx5ŏ߀-_[|r&a/\{~E֫/?`CU0=?`xS//?)yxӘ>Ձ4X32J`!3N&i?TJ;n߁;3,Nw̿f2^ߛP;| Dr7'){)&fn ^Be'ib&|C`ב2Ϳ'i⺄Tm<>?w4mQi_ORwr'ycGKx[UGwn;0%i.M_{T@qF̅N 2lu'}- ii3:Ɋۨ2`=nn!k✿8W$j^rߐeChy'Q?&6<?ORӟS_),0Fi0C}0m_v1Syl2 zZ`AMW)I?JOhhm)u<#U;ӿP |*A2Pjf=-{ ۿ+l;˩6I!6NpG0HA{Cx/U|a`20C(.+t6kAIE9gL?z/~J/ R_cSl}P?i?jE) %8J FY|fM)R/-? ph-2̈ KU8`Jsf]oeY*$>\"ߍZH]}.>hKG[OU y齎L-vْwK>GQ;2r&F[3vM:7^b'"VTÿG? Gvތ,E|PMП9ƴN;+hym#l*V^LJ'/Y+Six'Ƃ9? j AOle 0ibDob# *j1–T#?D=/^1:2>h hU55A')*βl1$,\l)"Fn%m^EdRAuѽ+UDwx}-q! w?>_8_CIpOI([~ _?'B?OIO/~ػܶa CْSxӶ@2ļw,~0&)RԵ OU?u_ԵxWS@T_Su6OY6Oa?uS@S0'O]O]O]N|rk 蟪? )>?U) OU蟺¬ꟺ @S-Y.=xLcp9tW F 3`0&kSYsoY?]jSIyGǞ?%]Ft/L%MǗgc=`xk|ÚjxL#co;-8GoiF3`9m}18 4r\K)C?{\`ťvÏ?r;r/~wgY0ETÖ?HsujX5e@dq A#3]D*NOՍi@8Z~lԍ HdasyR/FǨDzÐVe.M=ɦ?GLF]wErd*+ M!?=L?*L8?\Fւ B?<c9T oKSy}caoSORV @0)䫙wD6ʡ^X s7X,A^ 68M KIm6oVl.}by$=c67+O=8ͻbxg*u@ܢ~MMu6SpSY4DTNu8E ΕQ-TmU0C-n#@-z~Vs</sW#  oj0.Fʀ`mV$̦[TDrQy _7g> mHw9B ` ˨ ؤ ]X cj0~~ \o6x[Ԑ` fV"K;+"K|PW9 t!I غ"G 6 DZH.oՙBSB AUIZIR EJm節x ?-Gq 0NjG͗}|l[QCL{Qo`"~䬅 o)u"kjn*kJ ]5"$TE|#`mS WE' VԒq$`XS@0^}d Ҹrs, -ckQ[ĭXjx[TꮺKm!" Ъ.Mm!{"hUG`X{&u854C "+jI(LK@*oUES]N馎CiQ}':(bpIj?Ց \j lafWuPEm7u k78Mj?[tdaVuX7@>ە]]lY=u`w;~+˽j!@Q^(`VjPt$Ӧ6ߡ𯽀~ q?n@b E#7B?`'譃]/9=u㓀Ǭ!^aQP@7Of  |a4Ns9ȼiW K~{7[u׸_/0ػ7a lN.VjJ+Le>} a \?bfCI߬ v-2Θ#Oc;F r:O} p}3oÒ"  5,-րY\OlM lf}g jZKzZ#d'h ֎. IJإ>&Krj [zmXHfk>8q{ {z}spV4'MP]M/R;sW4sM/P/Oi'g撞4+ PQ̋nIOX\Ack锚$3|ǴQ8HU^t{MQC \TRe9s3ty+e˝ӟM bvsז9| 燁eX4ʣ BJD!"# @`D sl351tv?ܡ9] F^  JWǙ75<:Cj PJh !0 Kl>#P/2UW՜?\70vņa(%v]*?g6BtԺJ Jn~`]kVKr mҨLqIHT)*摋XL(m~&9B;J{5 W ;yn"<IENDB`mozilla-vpn-client-2.2.0/ios/app/main.entitlements000066400000000000000000000007441404202232700221730ustar00rootroot00000000000000 com.apple.developer.networking.networkextension packet-tunnel-provider com.apple.security.application-groups $(GROUP_ID_IOS) com.apple.security.files.user-selected.read-write mozilla-vpn-client-2.2.0/ios/networkextension/000077500000000000000000000000001404202232700214535ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/ios/networkextension/MozillaVPNNetworkExtension.entitlements000066400000000000000000000007001404202232700313470ustar00rootroot00000000000000 com.apple.security.application-groups $(GROUP_ID_IOS) com.apple.developer.networking.networkextension packet-tunnel-provider mozilla-vpn-client-2.2.0/ios/utils/000077500000000000000000000000001404202232700171655ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/ios/utils/screenCaptureIOS.js000066400000000000000000000226341404202232700227100ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ const assert = require('assert'); const fs = require('fs'); const util = require('util'); const vpn = require('./helper.js'); const FirefoxHelper = require('./firefox.js'); const dir = '/tmp/screencapture'; describe('Take screenshots for each view', function() { let languages = []; let driver; this.timeout(100000); async function screenCapture(name) { for (let language of languages) { await vpn.setSetting('language-code', language); // we need to give time to the app to retranslate the UI. If the number // is too slow we have the UI in funny states (part in 1 language, part // in another language, ...). But if the number is too high, the // "connecting" state is faster and we do not take all the screen // captures for all the languages. await new Promise(r => setTimeout(r, 30)); const data = await vpn.screenCapture(); const buffer = Buffer.from(data, 'base64'); fs.writeFileSync(`${dir}/${name}_${language}.png`, buffer); } } before(async () => { // TODO: change this IP with a variable await vpn.connect('192.168.1.5'); driver = await FirefoxHelper.createDriver(); if (!fs.existsSync(dir)) { fs.mkdirSync(dir); } }); beforeEach(() => {}); afterEach(() => {}); after(async () => { await driver.quit(); vpn.disconnect(); }); it('Retrieve the list of languages', async () => { const codes = await vpn.languages(); for (let c of codes) { if (c !== 'en') languages.push(c); } // English at the end. languages.push('en'); }); it('reset the app', async () => await vpn.reset()); it('initial view', async () => { await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); await vpn.wait(); await screenCapture('initialize'); }); it('heartbeat', async () => { await vpn.forceHeartbeatFailure(); await vpn.waitForElement('heartbeatTryButton'); await vpn.waitForElementProperty('heartbeatTryButton', 'visible', 'true'); await screenCapture('heartbeat'); await vpn.wait(); await vpn.clickOnElement('heartbeatTryButton'); await vpn.wait(); await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); }); it('help view', async () => { await vpn.clickOnElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'false'); await vpn.waitForElement('getHelpBack'); await vpn.waitForElementProperty('getHelpBack', 'visible', 'true'); await screenCapture('help'); }); it('Go back to the main view', async () => { await vpn.clickOnElement('getHelpBack'); await vpn.waitForElement('getHelpLink'); await vpn.waitForElementProperty('getHelpLink', 'visible', 'true'); await vpn.wait(); }); it('Onboarding', async () => { assert(await vpn.getElementProperty('learnMoreLink', 'visible') === 'true'); await vpn.clickOnElement('learnMoreLink'); await vpn.wait(); await vpn.waitForElement('skipOnboarding'); await vpn.waitForElementProperty('skipOnboarding', 'visible', 'true'); let onboardingView = 0; while (true) { await screenCapture(`onboarding_${++onboardingView}`); assert(await vpn.hasElement('onboardingNext')); assert( await vpn.getElementProperty('onboardingNext', 'visible') === 'true'); await vpn.setSetting('language-code', 'en'); if (await vpn.getElementProperty('onboardingNext', 'text') !== 'Next') { break; } await vpn.clickOnElement('onboardingNext'); await vpn.wait(); } }); it('Authenticating', async () => { await vpn.clickOnElement('onboardingNext'); await vpn.wait(); await screenCapture('authenticating'); }); it('Wait for user-interaction', async () => { while (1) { console.log(1); await vpn.wait(); if (!(await vpn.hasElement('controllerTitle'))) continue; if (await vpn.getElementProperty('controllerTitle', 'visible') !== 'true') { continue; } break; } }); it('main view', async () => { await screenCapture('vpn_off'); }); it('connecting', async () => { await vpn.activate(); await vpn.waitForCondition(async () => { let connectingMsg = await vpn.getElementProperty('controllerTitle', 'text'); return connectingMsg === 'Connecting…'; }); await screenCapture('vpn_connecting'); }); it('connected', async () => { await vpn.waitForCondition(async () => { return await vpn.getElementProperty('controllerTitle', 'text') == 'VPN is on'; }); await screenCapture('vpn_on'); }); it('connection info', async () => { await vpn.waitForElement('connectionInfoButton'); await vpn.clickOnElement('connectionInfoButton'); await vpn.wait(); await screenCapture('connection_info'); await vpn.waitForElement('connectionInfoBackButton'); await vpn.clickOnElement('connectionInfoBackButton'); await vpn.wait(); }); it('settings', async () => { await vpn.waitForElement('settingsButton'); await vpn.clickOnElement('settingsButton'); await vpn.wait(); await screenCapture('settings'); const contentHeight = parseInt(await vpn.getElementProperty('settingsView', 'contentHeight')) const height = parseInt(await vpn.getElementProperty('settingsView', 'height')); let contentY = parseInt(await vpn.getElementProperty('settingsView', 'contentY')); let scrollId = 0; while (true) { if (contentHeight <= (contentY + height)) { break; } contentY += height; await vpn.setElementProperty('settingsView', 'contentY', 'i', contentY); await screenCapture(`settings_${++scrollId}`); } }); it('settings / networking', async () => { await vpn.waitForElement('settingsNetworking'); await vpn.waitForElementProperty('settingsNetworking', 'visible', 'true'); await vpn.clickOnElement('settingsNetworking'); await vpn.wait(); await screenCapture('settings_networking'); await vpn.clickOnElement('settingsNetworkingBackButton'); await vpn.wait(); await vpn.waitForElement('manageAccountButton'); await vpn.waitForElementProperty('manageAccountButton', 'visible', 'true'); }); it('settings / languages', async () => { await vpn.waitForElement('settingsLanguages'); await vpn.waitForElementProperty('settingsLanguages', 'visible', 'true'); await vpn.clickOnElement('settingsLanguages'); await vpn.wait(); await screenCapture('settings_languages'); const contentHeight = parseInt( await vpn.getElementProperty('settingsLanguagesView', 'contentHeight')) const height = parseInt( await vpn.getElementProperty('settingsLanguagesView', 'height')); let contentY = parseInt( await vpn.getElementProperty('settingsLanguagesView', 'contentY')); let scrollId = 0; while (true) { if (contentHeight <= (contentY + height)) { break; } contentY += height; await vpn.setElementProperty( 'settingsLanguagesView', 'contentY', 'i', contentY); await screenCapture(`settings_languages_${++scrollId}`); } await vpn.clickOnElement('settingsLanguagesBackButton'); await vpn.wait(); await vpn.waitForElement('manageAccountButton'); await vpn.waitForElementProperty('manageAccountButton', 'visible', 'true'); await vpn.waitForElementProperty( 'manageAccountButton', 'text', 'Manage account'); }); // TODO: app-permission it('setting / about us', async () => { await vpn.waitForElement('settingsAboutUs'); await vpn.waitForElementProperty('settingsAboutUs', 'visible', 'true'); await vpn.clickOnElement('settingsAboutUs'); await vpn.wait(); await screenCapture('settings_about'); await vpn.clickOnElement('aboutUsBackButton'); await vpn.wait(); await vpn.waitForElement('manageAccountButton'); await vpn.waitForElementProperty('manageAccountButton', 'visible', 'true'); }); it('settings / help', async () => { await vpn.waitForElement('settingsGetHelp'); await vpn.waitForElementProperty('settingsGetHelp', 'visible', 'true'); await vpn.clickOnElement('settingsGetHelp'); await vpn.wait(); await screenCapture('settings_help'); await vpn.clickOnElement('getHelpBack'); await vpn.wait(); await vpn.waitForElement('settingsGetHelp'); await vpn.waitForElementProperty('settingsGetHelp', 'visible', 'true'); }); it('closing the settings view', async () => { await vpn.clickOnElement('settingsCloseButton'); await vpn.wait(); await vpn.waitForElement('controllerTitle'); await vpn.waitForElementProperty('controllerTitle', 'visible', 'true'); }); it('disconnecting', async () => { await vpn.deactivate(); await vpn.waitForCondition(async () => { return await vpn.getElementProperty('controllerTitle', 'text') === 'Disconnecting…'; }); await screenCapture('vpn_disconnecting'); await vpn.waitForCondition(async () => { return await vpn.getElementProperty('controllerTitle', 'text') === 'VPN is off'; }); }); it('quit the app', async () => await vpn.quit()); }); mozilla-vpn-client-2.2.0/linux/000077500000000000000000000000001404202232700163725ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/linux/bionic/000077500000000000000000000000001404202232700176355ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/linux/bionic/Dockerfile000066400000000000000000000001061404202232700216240ustar00rootroot00000000000000FROM ubuntu:bionic AS mozillavpn_bionic COPY qt_ppa.sh /tmp/qt_ppa.sh mozilla-vpn-client-2.2.0/linux/bionic/README.md000066400000000000000000000005301404202232700211120ustar00rootroot00000000000000# Steps to create QT packages for bionic From _this_ directory, run: ``` $ docker build -t mozillavpnbionic . ``` Now start the container with type mozillavpnbionic and run the script /tmp/qt_ppa.sh: ``` $ docker run -it mozillavpnbionic /tmp/qt_ppa.sh ``` Finally, copy all the files in /tmp/qt_ppa_final from the container to launchpad. mozilla-vpn-client-2.2.0/linux/bionic/qt_ppa.sh000077500000000000000000000172671404202232700214750ustar00rootroot00000000000000#!/bin/bash # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. printv() { if [ -t 1 ]; then NCOLORS=$(tput colors) if test -n "$NCOLORS" && test "$NCOLORS" -ge 8; then NORMAL="$(tput sgr0)" RED="$(tput setaf 1)" GREEN="$(tput setaf 2)" YELLOW="$(tput setaf 3)" fi fi if [[ $2 = 'G' ]]; then # shellcheck disable=SC2086 echo $1 -e "${GREEN}$3${NORMAL}" elif [[ $2 = 'Y' ]]; then # shellcheck disable=SC2086 echo $1 -e "${YELLOW}$3${NORMAL}" elif [[ $2 = 'N' ]]; then # shellcheck disable=SC2086 echo $1 -e "$3" else # shellcheck disable=SC2086 echo $1 -e "${RED}$3${NORMAL}" fi } print() { printv '' "$1" "$2" } printn() { printv "-n" "$1" "$2" } error() { printv '' R "$1" } die() { if [[ "$1" ]]; then error "$1" else error Failed fi exit 1 } print Y "Installing dependencies..." apt-get update || die apt-get install -y \ wget \ xz-utils \ devscripts || die printn Y "Creating /tmp/qt_ppa_final... " rm -rf /tmp/qt_ppa_final || die mkdir /tmp/qt_ppa_final || die print G "done." printn Y "Creating /tmp/qt_ppa... " rm -rf /tmp/qt_ppa || die mkdir /tmp/qt_ppa || die cd /tmp/qt_ppa || die print G "done." magic() { NAME=$1 FOLDER=$2 ORIGURL=$3 ORIG=$4 DEBURL=$5 DEB=$6 if ! [[ -f $ORIG ]]; then print Y "Downloading the orig... " wget $ORIGURL || die tar xf $ORIG || die fi [[ -d $FOLDER ]] || die "$FOLDER doesn't exist." print Y "Downloading the deb for qt base... " wget $DEBURL || die tar xf $DEB || die rm -f $DEB || die print G "done." print Y "Patching debian files... " cat > tmp << EOF $NAME (5.15.2-bionic1) bionic; urgency=low * Bionic build -- Andrea Marchesini $(date -R) EOF cat debian/changelog >> tmp || die mv tmp debian/changelog || die print G "done." print Y "Installing dependencies... " BD= LIST=$( cat debian/control | while read LINE; do if [[ $(echo $LINE | grep "^Build-Depends:") ]]; then BD=1 LINE=$(echo $LINE | cut -d: -f2) elif [[ $(echo $LINE | grep ":") ]]; then BD= fi if [[ $BD ]]; then echo $LINE | cut -d, -f1 | cut -d\[ -f1 | cut -d\( -f1 | grep -v g++-4.6 fi done ) echo $LIST apt-get install -y $LIST print Y "Configuring the source folder... " mv debian $FOLDER || die cd $FOLDER || die print Y "Creating the debian package... " debuild -S --no-sign || die dpkg-buildpackage -b -rfakeroot -us -uc || die dpkg -i ../"$NAME"_*.deb print Y "Clean up... " cd .. || die rm -rf $FOLDER || die mv * /tmp/qt_ppa_final || die cp /tmp/qt_ppa_final/*orig.tar.xz . || die print G "done." } magic \ qt515base \ qtbase-everywhere-src-5.15.2 \ https://launchpad.net/~mozillacorp/+archive/ubuntu/mozillavpn/+sourcefiles/qt515base/5.15.2-1basyskom4/qt515base_5.15.2.orig.tar.xz \ qt515base_5.15.2.orig.tar.xz \ https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515base/5.15.2-1basyskom4/qt515base_5.15.2-1basyskom4.debian.tar.xz \ qt515base_5.15.2-1basyskom4.debian.tar.xz || die magic \ qt515xmlpatterns \ qtxmlpatterns-everywhere-src-5.15.2 \ https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515xmlpatterns/5.15.2-1basyskom1/qt515xmlpatterns_5.15.2.orig.tar.xz \ qt515xmlpatterns_5.15.2.orig.tar.xz \ https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515xmlpatterns/5.15.2-1basyskom1/qt515xmlpatterns_5.15.2-1basyskom1.debian.tar.xz \ qt515xmlpatterns_5.15.2-1basyskom1.debian.tar.xz || die magic \ qt515declarative \ qtdeclarative-everywhere-src-5.15.2 \ https://launchpad.net/~mozillacorp/+archive/ubuntu/mozillavpn/+sourcefiles/qt515declarative/5.15.2-1basyskom1/qt515declarative_5.15.2.orig.tar.xz \ qt515declarative_5.15.2.orig.tar.xz \ https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515declarative/5.15.2-1basyskom1/qt515declarative_5.15.2-1basyskom1.debian.tar.xz \ qt515declarative_5.15.2-1basyskom1.debian.tar.xz || die magic \ qt515charts-no-lgpl \ qtcharts-everywhere-src-5.15.2 \ https://launchpad.net/~mozillacorp/+archive/ubuntu/mozillavpn/+sourcefiles/qt515charts-no-lgpl/5.15.2-1basyskom1/qt515charts-no-lgpl_5.15.2.orig.tar.xz \ qt515charts-no-lgpl_5.15.2.orig.tar.xz \ https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515charts-no-lgpl/5.15.2-1basyskom1/qt515charts-no-lgpl_5.15.2-1basyskom1.debian.tar.xz \ qt515charts-no-lgpl_5.15.2-1basyskom1.debian.tar.xz || die magic \ qt515graphicaleffects \ qtgraphicaleffects-everywhere-src-5.15.2 \ https://launchpad.net/~mozillacorp/+archive/ubuntu/mozillavpn/+sourcefiles/qt515graphicaleffects/5.15.2-1basyskom1/qt515graphicaleffects_5.15.2.orig.tar.xz \ qt515graphicaleffects_5.15.2.orig.tar.xz \ https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515graphicaleffects/5.15.2-1basyskom1/qt515graphicaleffects_5.15.2-1basyskom1.debian.tar.xz \ qt515graphicaleffects_5.15.2-1basyskom1.debian.tar.xz || die magic \ qt515imageformats \ qtimageformats-everywhere-src-5.15.2 \ https://launchpad.net/~mozillacorp/+archive/ubuntu/mozillavpn/+sourcefiles/qt515imageformats/5.15.2-1basyskom1/qt515imageformats_5.15.2.orig.tar.xz \ qt515imageformats_5.15.2.orig.tar.xz \ https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515imageformats/5.15.2-1basyskom2/qt515imageformats_5.15.2-1basyskom2.debian.tar.xz \ qt515imageformats_5.15.2-1basyskom2.debian.tar.xz || die magic \ qt515networkauth-no-lgpl \ qtnetworkauth-everywhere-src-5.15.2 \ https://launchpad.net/~mozillacorp/+archive/ubuntu/mozillavpn/+sourcefiles/qt515networkauth-no-lgpl/5.15.2-1basyskom1/qt515networkauth-no-lgpl_5.15.2.orig.tar.xz \ qt515networkauth-no-lgpl_5.15.2.orig.tar.xz \ https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515networkauth-no-lgpl/5.15.2-1basyskom1/qt515networkauth-no-lgpl_5.15.2-1basyskom1.debian.tar.xz \ qt515networkauth-no-lgpl_5.15.2-1basyskom1.debian.tar.xz || die magic \ qt515quickcontrols \ qtquickcontrols-everywhere-src-5.15.2 \ https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515quickcontrols/5.15.2-1basyskom1/qt515quickcontrols_5.15.2.orig.tar.xz \ qt515quickcontrols_5.15.2.orig.tar.xz \ https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515quickcontrols/5.15.2-1basyskom1/qt515quickcontrols_5.15.2-1basyskom1.debian.tar.xz \ qt515quickcontrols_5.15.2-1basyskom1.debian.tar.xz || die magic \ qt515quickcontrols2 \ qtquickcontrols2-everywhere-src-5.15.2 \ https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515quickcontrols2/5.15.2-1basyskom1/qt515quickcontrols2_5.15.2.orig.tar.xz \ qt515quickcontrols2_5.15.2.orig.tar.xz \ https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515quickcontrols2/5.15.2-1basyskom1/qt515quickcontrols2_5.15.2-1basyskom1.debian.tar.xz \ qt515quickcontrols2_5.15.2-1basyskom1.debian.tar.xz || die magic \ qt515svg \ qtsvg-everywhere-src-5.15.2 \ https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515svg/5.15.2-1basyskom1/qt515svg_5.15.2.orig.tar.xz \ qt515svg_5.15.2.orig.tar.xz \ https://launchpad.net/~beineri/+archive/ubuntu/opt-qt-5.15.2-bionic/+sourcefiles/qt515svg/5.15.2-1basyskom1/qt515svg_5.15.2-1basyskom1.debian.tar.xz \ qt515svg_5.15.2-1basyskom1.debian.tar.xz || die print G "Now, upload all the files from /tmp/qt_ppa_final" mozilla-vpn-client-2.2.0/linux/daemon/000077500000000000000000000000001404202232700176355ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/linux/daemon/helper.sh000077500000000000000000000153451404202232700214630ustar00rootroot00000000000000#!/bin/bash # SPDX-License-Identifier: GPL-2.0 # # Copyright (C) 2015-2020 Jason A. Donenfeld . All Rights Reserved. # echo "Running mozillavpn forked copy of wg-quick script." set -e -o pipefail shopt -s extglob export LC_ALL=C SELF="$(readlink -f "${BASH_SOURCE[0]}")" export PATH="${SELF%/*}:$PATH" WG_CONFIG="" INTERFACE="" ADDRESSES=( ) DNS=( ) CONFIG_FILE="" die() { echo "MozillaVPN WGQuick Fatal Error: $*" >&2 exit 1 } parse_options() { local interface_section=0 line key value stripped v CONFIG_FILE="$1" [[ $CONFIG_FILE =~ ^[a-zA-Z0-9_=+.-]{1,15}$ ]] && CONFIG_FILE="/etc/wireguard/$CONFIG_FILE.conf" [[ -e $CONFIG_FILE ]] || die "\`$CONFIG_FILE' does not exist" [[ $CONFIG_FILE =~ (^|/)([a-zA-Z0-9_=+.-]{1,15})\.conf$ ]] || die "The config file must be a valid interface name, followed by .conf" CONFIG_FILE="$(readlink -f "$CONFIG_FILE")" ((($(stat -c '0%#a' "$CONFIG_FILE") & $(stat -c '0%#a' "${CONFIG_FILE%/*}") & 0007) == 0)) || echo "Warning: \`$CONFIG_FILE' is world accessible" >&2 INTERFACE="${BASH_REMATCH[2]}" shopt -s nocasematch while read -r line || [[ -n $line ]]; do stripped="${line%%\#*}" key="${stripped%%=*}"; key="${key##*([[:space:]])}"; key="${key%%*([[:space:]])}" value="${stripped#*=}"; value="${value##*([[:space:]])}"; value="${value%%*([[:space:]])}" [[ $key == "["* ]] && interface_section=0 [[ $key == "[Interface]" ]] && interface_section=1 if [[ $interface_section -eq 1 ]]; then case "$key" in Address) ADDRESSES+=( ${value//,/ } ); continue ;; DNS) for v in ${value//,/ }; do [[ $v =~ (^[0-9.]+$)|(^.*:.*$) ]] && DNS+=( $v ) done; continue ;; esac fi WG_CONFIG+="$line"$'\n' done < "$CONFIG_FILE" shopt -u nocasematch } add_if() { local ret if ! ip link add "$INTERFACE" type wireguard; then ret=$? [[ -e /sys/module/wireguard ]] || ! command -v "${WG_QUICK_USERSPACE_IMPLEMENTATION:-wireguard-go}" >/dev/null && exit $ret echo "[!] Missing WireGuard kernel module. Falling back to slow userspace implementation." >&2 "${WG_QUICK_USERSPACE_IMPLEMENTATION:-wireguard-go}" "$INTERFACE" fi } del_if() { local table [[ $HAVE_SET_DNS -eq 0 ]] || unset_dns [[ $HAVE_SET_FIREWALL -eq 0 ]] || remove_firewall if get_fwmark table && [[ $(wg show "$INTERFACE" allowed-ips) =~ /0(\ |$'\n'|$) ]]; then while [[ $(ip -4 rule show 2>/dev/null) == *"lookup $table"* ]]; do ip -4 rule delete table $table done while [[ $(ip -4 rule show 2>/dev/null) == *"from all lookup main suppress_prefixlength 0"* ]]; do ip -4 rule delete table main suppress_prefixlength 0 done while [[ $(ip -6 rule show 2>/dev/null) == *"lookup $table"* ]]; do ip -6 rule delete table $table done while [[ $(ip -6 rule show 2>/dev/null) == *"from all lookup main suppress_prefixlength 0"* ]]; do ip -6 rule delete table main suppress_prefixlength 0 done fi ip link delete dev "$INTERFACE" } add_addr() { local proto=-4 [[ $1 == *:* ]] && proto=-6 ip $proto address add "$1" dev "$INTERFACE" } set_mtu_up() { # Using default MTU of 1420 ip link set mtu 1420 up dev "$INTERFACE" } resolvconf_iface_prefix() { [[ -f /etc/resolvconf/interface-order ]] || return 0 local iface while read -r iface; do [[ $iface =~ ^([A-Za-z0-9-]+)\*$ ]] || continue echo "${BASH_REMATCH[1]}." && return 0 done < /etc/resolvconf/interface-order } HAVE_SET_DNS=0 set_dns() { # Iterate through values of DNS array and pipe "nameserver" string to # resolvconf command which adds nameserver rows for each dns entry to /etc/resolv.conf # (maybe amongst other things) printf 'nameserver %s\n' "${DNS[@]}" | resolvconf -a "$(resolvconf_iface_prefix)$INTERFACE" -m 0 -x HAVE_SET_DNS=1 } unset_dns() { resolvconf -d "$(resolvconf_iface_prefix)$INTERFACE" -f } add_route() { local proto=-4 [[ $1 == *:* ]] && proto=-6 [[ $TABLE != off ]] || return 0 if [[ -n $TABLE && $TABLE != auto ]]; then ip $proto route add "$1" dev "$INTERFACE" table "$TABLE" elif [[ $1 == */0 ]]; then add_default "$1" else [[ -n $(ip $proto route show dev "$INTERFACE" match "$1" 2>/dev/null) ]] || ip $proto route add "$1" dev "$INTERFACE" fi } get_fwmark() { local fwmark fwmark="$(wg show "$INTERFACE" fwmark)" || return 1 [[ -n $fwmark && $fwmark != off ]] || return 1 printf -v "$1" "%d" "$fwmark" return 0 } remove_firewall() { local line iptables found restore for iptables in iptables ip6tables; do restore="" found=0 while read -r line; do [[ $line == "*"* || $line == COMMIT || $line == "-A "*"-m comment --comment \"wg-quick(8) rule for $INTERFACE\""* ]] || continue [[ $line == "-A"* ]] && found=1 printf -v restore '%s%s\n' "$restore" "${line/#-A/-D}" done < <($iptables-save 2>/dev/null) [[ $found -ne 1 ]] || echo -n "$restore" | $iptables-restore -n done } HAVE_SET_FIREWALL=0 add_default() { local table line if ! get_fwmark table; then table=51820 while [[ -n $(ip -4 route show table $table 2>/dev/null) || -n $(ip -6 route show table $table 2>/dev/null) ]]; do ((table++)) done wg set "$INTERFACE" fwmark $table fi local proto=-4 iptables=iptables pf=ip [[ $1 == *:* ]] && proto=-6 iptables=ip6tables pf=ip6 ip $proto route add "$1" dev "$INTERFACE" table $table ip $proto rule add not fwmark $table table $table ip $proto rule add table main suppress_prefixlength 0 # Things work without this, but it looks important # local marker="-m comment --comment \"wg-quick(8) rule for $INTERFACE\"" restore=$'*raw\n' # while read -r line; do # [[ $line =~ .*inet6?\ ([0-9a-f:.]+)/[0-9]+.* ]] || continue # printf -v restore '%s-I PREROUTING ! -i %s -d %s -m addrtype ! --src-type LOCAL -j DROP %s\n' "$restore" "$INTERFACE" "${BASH_REMATCH[1]}" "$marker" # done < <(ip -o $proto addr show dev "$INTERFACE" 2>/dev/null) # printf -v restore '%sCOMMIT\n*mangle\n-I POSTROUTING -m mark --mark %d -p udp -j CONNMARK --save-mark %s\n-I PREROUTING -p udp -j CONNMARK --restore-mark %s\nCOMMIT\n' "$restore" $table "$marker" "$marker" # [[ $proto == -4 ]] && sysctl -q net.ipv4.conf.all.src_valid_mark=1 # echo -n "$restore" | $iptables-restore -n HAVE_SET_FIREWALL=1 return 0 } set_config() { wg setconf "$INTERFACE" <(echo "$WG_CONFIG") } cmd_up() { local i trap 'del_if; exit' INT TERM EXIT add_if set_config for i in "${ADDRESSES[@]}"; do add_addr "$i" done set_mtu_up set_dns # Get the allowed ips from wg show (added to peer using set_conf) for i in $(while read -r _ i; do for i in $i; do [[ $i =~ ^[0-9a-z:.]+/[0-9]+$ ]] && echo "$i"; done; done < <(wg show "$INTERFACE" allowed-ips) | sort -nr -k 2 -t /); do add_route "$i" done trap - INT TERM EXIT } cmd_down() { del_if unset_dns || true remove_firewall || true } if [[ $# -eq 2 && $1 == up ]]; then parse_options "$2" cmd_up elif [[ $# -eq 2 && $1 == down ]]; then parse_options "$2" cmd_down else exit 1 fi exit 0 mozilla-vpn-client-2.2.0/linux/debian/000077500000000000000000000000001404202232700176145ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/linux/debian/changelog.template000066400000000000000000000002041404202232700232740ustar00rootroot00000000000000mozillavpn (SHORTVERSION-VERSION) RELEASE; urgency=medium * First ubuntu package -- Andrea Marchesini DATE mozilla-vpn-client-2.2.0/linux/debian/compat000066400000000000000000000000031404202232700210130ustar00rootroot0000000000000010 mozilla-vpn-client-2.2.0/linux/debian/control.prod.bionic000066400000000000000000000063401404202232700234270ustar00rootroot00000000000000Source: mozillavpn Section: net Priority: optional Maintainer: mozilla Build-Depends: debhelper (>= 8.1.3), cdbs, quilt, flex, qt515base (>=5.15.2-1basyskom4), qt515charts-no-lgpl (>=5.15.2-1basyskom1), qt515declarative (>= 5.15.2-1basyskom1), qt515graphicaleffects (>= 5.15.2-1basyskom1), qt515imageformats (>= 5.15.2-1basyskom), qt515networkauth-no-lgpl (>= 5.15.2-1basyskom1), qt515quickcontrols2 (>=5.15.2-1basyskom1), qt515svg (>=5.15.2-1basyskom1), qt515tools (>=5.15.2-1basyskom1), libxcb-render0-dev, libxcb-image0-dev, libxcb-shape0-dev, libxcb-sync0-dev, libxcb-render-util0-dev, libxcb1-dev, libxcb-xfixes0-dev, libxcb-icccm4-dev, libxcb1-dev, libx11-xcb-dev, libxcb-keysyms1-dev, libxcb-image0-dev, libxcb-shm0-dev, libxcb-icccm4-dev, libxcb-sync0-dev, libxcb-xfixes0-dev, libxrender-dev, libxcb-shape0-dev, libasound2-dev, libaudio-dev, libcups2-dev, libdbus-1-dev, libfreetype6-dev, libgl1-mesa-dev [!armel !armhf] | libgl-dev [!armel !armhf], libgles2-mesa-dev [armel armhf] | libgles2-dev [armel armhf], libglib2.0-dev, libglu1-mesa-dev [!armel !armhf] | libglu-dev [!armel !armhf], libice-dev, libjpeg-dev, libmng-dev, libpng-dev, libsm-dev, libsqlite3-dev, libssl-dev, libtiff5-dev, libx11-dev, libxcursor-dev, libxext-dev, libxft-dev, libxi-dev, libxinerama-dev, libxmu-dev, libxrandr-dev, libxrender-dev, libxt-dev, libxv-dev, zlib1g-dev, libedit-dev, libvulkan-dev, libpolkit-gobject-1-dev Standards-Version: 4.4.1 Homepage: https://vpn.mozilla.org/ Vcs-Git: https://github.com/mozilla-mobile/mozilla-vpn-client Package: mozillavpn Architecture: any Depends: libpolkit-gobject-1-0 (>=0.105-20), wireguard (>=1.0.20200513-1~18.04.2), wireguard-tools (>=1.0.20200513-1~18.04.2), libicu60 (>=60.2-3ubuntu3), libxcb-xinerama0 (>=1.13-1), resolvconf (>=1.79ubuntu10), qt515base (>=5.15.2-1basyskom4), qt515charts-no-lgpl (>=5.15.2-1basyskom1), qt515declarative (>= 5.15.2-1basyskom1), qt515graphicaleffects (>= 5.15.2-1basyskom1), qt515imageformats (>= 5.15.2-1basyskom), qt515networkauth-no-lgpl (>= 5.15.2-1basyskom1), qt515quickcontrols2 (>=5.15.2-1basyskom1), qt515svg (>=5.15.2-1basyskom1) Description: A fast, secure and easy to use VPN. Built by the makers of Firefox. Read more on https://vpn.mozilla.org mozilla-vpn-client-2.2.0/linux/debian/control.prod.focal000066400000000000000000000064411404202232700232520ustar00rootroot00000000000000Source: mozillavpn Section: net Priority: optional Maintainer: mozilla Build-Depends: debhelper (>= 8.1.3), cdbs, quilt, flex, qt515base (>=5.15.2-1basyskom4), qt515charts-no-lgpl (>=5.15.2-1basyskom1), qt515declarative (>= 5.15.2-1basyskom1), qt515graphicaleffects (>= 5.15.2-1basyskom1), qt515imageformats (>= 5.15.2-1basyskom), qt515networkauth-no-lgpl (>= 5.15.2-1basyskom1), qt515quickcontrols2 (>=5.15.2-1basyskom1), qt515svg (>=5.15.2-1basyskom1), qt515tools (>=5.15.2-1basyskom1), libxcb-render0-dev, libxcb-image0-dev, libxcb-shape0-dev, libxcb-sync0-dev, libxcb-render-util0-dev, libxcb1-dev, libxcb-xfixes0-dev, libxcb-icccm4-dev, libxcb1-dev, libx11-xcb-dev, libxcb-keysyms1-dev, libxcb-image0-dev, libxcb-shm0-dev, libxcb-icccm4-dev, libxcb-sync0-dev, libxcb-xfixes0-dev, libxrender-dev, libxcb-shape0-dev, g++-4.6 (>= 4.6.0-7~) [armel], libasound2-dev [linux-any], libaudio-dev, libcups2-dev, libdbus-1-dev, libfreetype6-dev, libgl1-mesa-dev [!armel !armhf] | libgl-dev [!armel !armhf], libgles2-mesa-dev [armel armhf] | libgles2-dev [armel armhf], libglib2.0-dev, libglu1-mesa-dev [!armel !armhf] | libglu-dev [!armel !armhf], libice-dev, libjpeg-dev, libmng-dev, libpng-dev, libsm-dev, libsqlite3-dev, libssl-dev, libtiff5-dev, libx11-dev, libxcursor-dev, libxext-dev, libxft-dev, libxi-dev, libxinerama-dev, libxmu-dev, libxrandr-dev, libxrender-dev, libxt-dev, libxv-dev, zlib1g-dev, libedit-dev, libvulkan-dev, libpolkit-gobject-1-dev Standards-Version: 4.4.1 Homepage: https://vpn.mozilla.org/ Vcs-Git: https://github.com/mozilla-mobile/mozilla-vpn-client Package: mozillavpn Architecture: any Depends: libpolkit-gobject-1-0 (>=0.105-20ubuntu0.18.04.5), wireguard (>=1.0.20200513-1~18.04.2), wireguard-tools (>=1.0.20200513-1~18.04.2), libicu66 (>=66.1-2ubuntu2), libxcb-xinerama0 (>=1.14-2), resolvconf (>=1.82), qt515base (>=5.15.2-1basyskom4), qt515charts-no-lgpl (>=5.15.2-1basyskom1), qt515declarative (>= 5.15.2-1basyskom1), qt515graphicaleffects (>= 5.15.2-1basyskom1), qt515imageformats (>= 5.15.2-1basyskom), qt515networkauth-no-lgpl (>= 5.15.2-1basyskom1), qt515quickcontrols2 (>=5.15.2-1basyskom1), qt515svg (>=5.15.2-1basyskom1) Description: A fast, secure and easy to use VPN. Built by the makers of Firefox. Read more on https://vpn.mozilla.org mozilla-vpn-client-2.2.0/linux/debian/control.prod.groovy000066400000000000000000000075141404202232700235150ustar00rootroot00000000000000Source: mozillavpn Section: net Priority: optional Maintainer: mozilla Build-Depends: debhelper (>= 8.1.3), cdbs, quilt, flex, libqt5charts5-dev (>=5.14.2-2), libqt5networkauth5-dev (>=5.14.2-2), qt5-default (>=5.14.2+dfsg-6), qtbase5-dev (>=5.14.2+dfsg-6), qtbase5-dev-tools (>=5.14.2+dfsg-6), qtdeclarative5-dev (>=5.14.2+dfsg-3ubuntu1), qtdeclarative5-dev-tools (>=5.14.2+dfsg-3ubuntu1), qt5-qmake-bin (>=5.14.2+dfsg-6), qttools5-dev-tools (>=5.14.2-3), libxcb-render0-dev, libxcb-image0-dev, libxcb-shape0-dev, libxcb-sync0-dev, libxcb-render-util0-dev, libxcb1-dev, libxcb-xfixes0-dev, libxcb-icccm4-dev, libxcb1-dev, libx11-xcb-dev, libxcb-keysyms1-dev, libxcb-image0-dev, libxcb-shm0-dev, libxcb-icccm4-dev, libxcb-sync0-dev, libxcb-xfixes0-dev, libxrender-dev, libxcb-shape0-dev, g++-4.6 (>= 4.6.0-7~) [armel], libasound2-dev [linux-any], libaudio-dev, libcups2-dev, libdbus-1-dev, libfreetype6-dev, libgl1-mesa-dev [!armel !armhf] | libgl-dev [!armel !armhf], libgles2-mesa-dev [armel armhf] | libgles2-dev [armel armhf], libglib2.0-dev, libglu1-mesa-dev [!armel !armhf] | libglu-dev [!armel !armhf], libice-dev, libjpeg-dev, libmng-dev, libpng-dev, libsm-dev, libsqlite3-dev, libssl-dev, libtiff5-dev, libx11-dev, libxcursor-dev, libxext-dev, libxft-dev, libxi-dev, libxinerama-dev, libxmu-dev, libxrandr-dev, libxrender-dev, libxt-dev, libxv-dev, zlib1g-dev, libedit-dev, libvulkan-dev, libpolkit-gobject-1-dev Standards-Version: 4.4.1 Homepage: https://vpn.mozilla.org/ Vcs-Git: https://github.com/mozilla-mobile/mozilla-vpn-client Package: mozillavpn Architecture: any Depends: libpolkit-gobject-1-0 (>=0.105-20ubuntu0.18.04.5), wireguard (>=1.0.20200513-1~18.04.2), wireguard-tools (>=1.0.20200513-1~18.04.2), resolvconf (>=1.82), libqt5charts5 (>=5.14.2-2), libqt5quick5 (>=5.14.2+dfsg-3ubuntu1), libqt5widgets5 (>=5.14.2+dfsg-6), libqt5gui5 (>=5.14.2+dfsg-6), libqt5qml5 (>=5.14.2+dfsg-3ubuntu1), libqt5network5 (>=5.14.2+dfsg-6), libqt5networkauth5 (>=5.14.2-2), libqt5dbus5 (>=5.14.2+dfsg-6), libqt5core5a (>=5.14.2+dfsg-6), libqt5qmlmodels5 (>=5.14.2+dfsg-3ubuntu1), libqt5svg5 (>=5.14.2-2), libqt5quickcontrols2-5 (>=5.14.2+dfsg-2), qml-module-qtcharts (>=5.14.2-2), qml-module-qtgraphicaleffects (>=5.14.2-2), qml-module-qtquick-controls (>=5.14.2-2), qml-module-qtquick-controls2 (>=5.14.2+dfsg-2), qml-module-qtquick-extras (>=5.14.2-2), qml-module-qtquick-layouts (>=5.14.2+dfsg-3ubuntu1), qml-module-qtquick-window2 (>=5.14.2+dfsg-3ubuntu1), qml-module-qtquick2 (>=5.14.2+dfsg-3ubuntu1), qml-module-qtqml-models2 (>=5.14.2+dfsg-3ubuntu1), qml-module-qtqml (>=5.14.2+dfsg-3ubuntu1) Description: A fast, secure and easy to use VPN. Built by the makers of Firefox. Read more on https://vpn.mozilla.org mozilla-vpn-client-2.2.0/linux/debian/control.stage.bionic000066400000000000000000000065061404202232700235720ustar00rootroot00000000000000Source: mozillavpn Section: net Priority: optional Maintainer: mozilla Build-Depends: debhelper (>= 8.1.3), cdbs, quilt, flex, qt515base (>=5.15.2-1basyskom4), qt515charts-no-lgpl (>=5.15.2-1basyskom1), qt515declarative (>= 5.15.2-1basyskom1), qt515graphicaleffects (>= 5.15.2-1basyskom1), qt515imageformats (>= 5.15.2-1basyskom), qt515networkauth-no-lgpl (>= 5.15.2-1basyskom1), qt515quickcontrols2 (>=5.15.2-1basyskom1), qt515svg (>=5.15.2-1basyskom1), qt515tools (>=5.15.2-1basyskom1), qt515websockets (>=5.15.2-1basyskom1), libxcb-render0-dev, libxcb-image0-dev, libxcb-shape0-dev, libxcb-sync0-dev, libxcb-render-util0-dev, libxcb1-dev, libxcb-xfixes0-dev, libxcb-icccm4-dev, libxcb1-dev, libx11-xcb-dev, libxcb-keysyms1-dev, libxcb-image0-dev, libxcb-shm0-dev, libxcb-icccm4-dev, libxcb-sync0-dev, libxcb-xfixes0-dev, libxrender-dev, libxcb-shape0-dev, libasound2-dev, libaudio-dev, libcups2-dev, libdbus-1-dev, libfreetype6-dev, libgl1-mesa-dev [!armel !armhf] | libgl-dev [!armel !armhf], libgles2-mesa-dev [armel armhf] | libgles2-dev [armel armhf], libglib2.0-dev, libglu1-mesa-dev [!armel !armhf] | libglu-dev [!armel !armhf], libice-dev, libjpeg-dev, libmng-dev, libpng-dev, libsm-dev, libsqlite3-dev, libssl-dev, libtiff5-dev, libx11-dev, libxcursor-dev, libxext-dev, libxft-dev, libxi-dev, libxinerama-dev, libxmu-dev, libxrandr-dev, libxrender-dev, libxt-dev, libxv-dev, zlib1g-dev, libedit-dev, libvulkan-dev, libpolkit-gobject-1-dev Standards-Version: 4.4.1 Homepage: https://vpn.mozilla.org/ Vcs-Git: https://github.com/mozilla-mobile/mozilla-vpn-client Package: mozillavpn Architecture: any Depends: libpolkit-gobject-1-0 (>=0.105-20), wireguard (>=1.0.20200513-1~18.04.2), wireguard-tools (>=1.0.20200513-1~18.04.2), libicu60 (>=60.2-3ubuntu3), libxcb-xinerama0 (>=1.13-1), resolvconf (>=1.79ubuntu10), qt515base (>=5.15.2-1basyskom4), qt515charts-no-lgpl (>=5.15.2-1basyskom1), qt515declarative (>= 5.15.2-1basyskom1), qt515graphicaleffects (>= 5.15.2-1basyskom1), qt515imageformats (>= 5.15.2-1basyskom), qt515networkauth-no-lgpl (>= 5.15.2-1basyskom1), qt515quickcontrols2 (>=5.15.2-1basyskom1), qt515svg (>=5.15.2-1basyskom1), qt515websockets (>=5.15.2-1basyskom1) Description: A fast, secure and easy to use VPN. Built by the makers of Firefox. Read more on https://vpn.mozilla.org mozilla-vpn-client-2.2.0/linux/debian/control.stage.focal000066400000000000000000000066071404202232700234150ustar00rootroot00000000000000Source: mozillavpn Section: net Priority: optional Maintainer: mozilla Build-Depends: debhelper (>= 8.1.3), cdbs, quilt, flex, qt515base (>=5.15.2-1basyskom4), qt515charts-no-lgpl (>=5.15.2-1basyskom1), qt515declarative (>= 5.15.2-1basyskom1), qt515graphicaleffects (>= 5.15.2-1basyskom1), qt515imageformats (>= 5.15.2-1basyskom), qt515networkauth-no-lgpl (>= 5.15.2-1basyskom1), qt515quickcontrols2 (>=5.15.2-1basyskom1), qt515svg (>=5.15.2-1basyskom1), qt515tools (>=5.15.2-1basyskom1), qt515websockets (>=5.15.2-1basyskom1), libxcb-render0-dev, libxcb-image0-dev, libxcb-shape0-dev, libxcb-sync0-dev, libxcb-render-util0-dev, libxcb1-dev, libxcb-xfixes0-dev, libxcb-icccm4-dev, libxcb1-dev, libx11-xcb-dev, libxcb-keysyms1-dev, libxcb-image0-dev, libxcb-shm0-dev, libxcb-icccm4-dev, libxcb-sync0-dev, libxcb-xfixes0-dev, libxrender-dev, libxcb-shape0-dev, g++-4.6 (>= 4.6.0-7~) [armel], libasound2-dev [linux-any], libaudio-dev, libcups2-dev, libdbus-1-dev, libfreetype6-dev, libgl1-mesa-dev [!armel !armhf] | libgl-dev [!armel !armhf], libgles2-mesa-dev [armel armhf] | libgles2-dev [armel armhf], libglib2.0-dev, libglu1-mesa-dev [!armel !armhf] | libglu-dev [!armel !armhf], libice-dev, libjpeg-dev, libmng-dev, libpng-dev, libsm-dev, libsqlite3-dev, libssl-dev, libtiff5-dev, libx11-dev, libxcursor-dev, libxext-dev, libxft-dev, libxi-dev, libxinerama-dev, libxmu-dev, libxrandr-dev, libxrender-dev, libxt-dev, libxv-dev, zlib1g-dev, libedit-dev, libvulkan-dev, libpolkit-gobject-1-dev Standards-Version: 4.4.1 Homepage: https://vpn.mozilla.org/ Vcs-Git: https://github.com/mozilla-mobile/mozilla-vpn-client Package: mozillavpn Architecture: any Depends: libpolkit-gobject-1-0 (>=0.105-20ubuntu0.18.04.5), wireguard (>=1.0.20200513-1~18.04.2), wireguard-tools (>=1.0.20200513-1~18.04.2), libicu66 (>=66.1-2ubuntu2), libxcb-xinerama0 (>=1.14-2), resolvconf (>=1.82), qt515base (>=5.15.2-1basyskom4), qt515charts-no-lgpl (>=5.15.2-1basyskom1), qt515declarative (>= 5.15.2-1basyskom1), qt515graphicaleffects (>= 5.15.2-1basyskom1), qt515imageformats (>= 5.15.2-1basyskom), qt515networkauth-no-lgpl (>= 5.15.2-1basyskom1), qt515quickcontrols2 (>=5.15.2-1basyskom1), qt515svg (>=5.15.2-1basyskom1), qt515websockets (>=5.15.2-1basyskom1) Description: A fast, secure and easy to use VPN. Built by the makers of Firefox. Read more on https://vpn.mozilla.org mozilla-vpn-client-2.2.0/linux/debian/control.stage.groovy000066400000000000000000000076511404202232700236560ustar00rootroot00000000000000Source: mozillavpn Section: net Priority: optional Maintainer: mozilla Build-Depends: debhelper (>= 8.1.3), cdbs, quilt, flex, libqt5charts5-dev (>=5.14.2-2), libqt5networkauth5-dev (>=5.14.2-2), libqt5websockets5-dev (>=5.14.2-2), qt5-default (>=5.14.2+dfsg-6), qtbase5-dev (>=5.14.2+dfsg-6), qtbase5-dev-tools (>=5.14.2+dfsg-6), qtdeclarative5-dev (>=5.14.2+dfsg-3ubuntu1), qtdeclarative5-dev-tools (>=5.14.2+dfsg-3ubuntu1), qt5-qmake-bin (>=5.14.2+dfsg-6), qttools5-dev-tools (>=5.14.2-3), libxcb-render0-dev, libxcb-image0-dev, libxcb-shape0-dev, libxcb-sync0-dev, libxcb-render-util0-dev, libxcb1-dev, libxcb-xfixes0-dev, libxcb-icccm4-dev, libxcb1-dev, libx11-xcb-dev, libxcb-keysyms1-dev, libxcb-image0-dev, libxcb-shm0-dev, libxcb-icccm4-dev, libxcb-sync0-dev, libxcb-xfixes0-dev, libxrender-dev, libxcb-shape0-dev, g++-4.6 (>= 4.6.0-7~) [armel], libasound2-dev [linux-any], libaudio-dev, libcups2-dev, libdbus-1-dev, libfreetype6-dev, libgl1-mesa-dev [!armel !armhf] | libgl-dev [!armel !armhf], libgles2-mesa-dev [armel armhf] | libgles2-dev [armel armhf], libglib2.0-dev, libglu1-mesa-dev [!armel !armhf] | libglu-dev [!armel !armhf], libice-dev, libjpeg-dev, libmng-dev, libpng-dev, libsm-dev, libsqlite3-dev, libssl-dev, libtiff5-dev, libx11-dev, libxcursor-dev, libxext-dev, libxft-dev, libxi-dev, libxinerama-dev, libxmu-dev, libxrandr-dev, libxrender-dev, libxt-dev, libxv-dev, zlib1g-dev, libedit-dev, libvulkan-dev, libpolkit-gobject-1-dev Standards-Version: 4.4.1 Homepage: https://vpn.mozilla.org/ Vcs-Git: https://github.com/mozilla-mobile/mozilla-vpn-client Package: mozillavpn Architecture: any Depends: libpolkit-gobject-1-0 (>=0.105-20ubuntu0.18.04.5), wireguard (>=1.0.20200513-1~18.04.2), wireguard-tools (>=1.0.20200513-1~18.04.2), resolvconf (>=1.82), libqt5charts5 (>=5.14.2-2), libqt5quick5 (>=5.14.2+dfsg-3ubuntu1), libqt5widgets5 (>=5.14.2+dfsg-6), libqt5gui5 (>=5.14.2+dfsg-6), libqt5qml5 (>=5.14.2+dfsg-3ubuntu1), libqt5network5 (>=5.14.2+dfsg-6), libqt5networkauth5 (>=5.14.2-2), libqt5dbus5 (>=5.14.2+dfsg-6), libqt5core5a (>=5.14.2+dfsg-6), libqt5qmlmodels5 (>=5.14.2+dfsg-3ubuntu1), libqt5svg5 (>=5.14.2-2), libqt5quickcontrols2-5 (>=5.14.2+dfsg-2), libqt5websockets5 (>= 5.14.2-2), qml-module-qtcharts (>=5.14.2-2), qml-module-qtgraphicaleffects (>=5.14.2-2), qml-module-qtquick-controls (>=5.14.2-2), qml-module-qtquick-controls2 (>=5.14.2+dfsg-2), qml-module-qtquick-extras (>=5.14.2-2), qml-module-qtquick-layouts (>=5.14.2+dfsg-3ubuntu1), qml-module-qtquick-window2 (>=5.14.2+dfsg-3ubuntu1), qml-module-qtquick2 (>=5.14.2+dfsg-3ubuntu1), qml-module-qtqml-models2 (>=5.14.2+dfsg-3ubuntu1), qml-module-qtqml (>=5.14.2+dfsg-3ubuntu1) Description: A fast, secure and easy to use VPN. Built by the makers of Firefox. Read more on https://vpn.mozilla.org mozilla-vpn-client-2.2.0/linux/debian/copyright000066400000000000000000000035721404202232700215560ustar00rootroot00000000000000Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: mozillavpn Upstream-Contact: vpn@mozilla.com Source: https://vpn.mozilla.org Files: * Copyright: 2020 Mozilla License: MPL-2.0 License: MPL-2.0 Unless otherwise specified below, all files in the Mozilla VPN project are licensed under the Mozilla Public License version 2.0 (*MPL2*), with copyright belonging to the Mozilla Foundation. The full text of the MPL is [available on Mozilla's website.] (https://www.mozilla.org/en-US/MPL/2.0/) and a copy is provided below. . This project relies on [QT Quick] (https://doc.qt.io/qt-5/qtquick-index.html), provided by [the QT Company] (http://www.qt.io/about-us/) and used unmodified under the terms of the Lesser GNU Public License 3.0 (*LGPL-3.0*). The full text of the LGPL3 is [available here.](https://opensource.org/licenses/lgpl-3.0) and a copy is provided below. . On MacOS and iOS, this project depends on several files provided by the [WireGuard project] (https://github.com/WireGuard/wireguard-apple/) under the terms of the MIT Public License (*MIT*), a copy of which is [available here.](https://opensource.org/licenses/MIT) and a copy is provided below. . The Linux release of this project also relies on several files from the [WireGuard Tools] (https://github.com/WireGuard/wireguard-tools/) repository, as well as the FreeDesktop project's [polkit library](https://gitlab.freedesktop.org/polkit/polkit/), all of which are provided under the terms of the Lesser GNU Public License 2.0 (*LGPL-2.0*). A copy of the LGPL-2.0 is [available here.](https://opensource.org/licenses/LGPL-2.0) and a copy is provided below. . Finally, this project uses EC25519 and CHACHA-POLY implementations from HACL\*, available under the terms of the MIT Public License (*MIT*) and copyright (c) 2016-2020 INRIA, CMU and Microsoft Corporation. mozilla-vpn-client-2.2.0/linux/debian/rules.prod.bionic000077500000000000000000000007021404202232700231000ustar00rootroot00000000000000#!/usr/bin/make -f export DH_VERBOSE=1 export QTDIR := /opt/qt515 export PATH := $(QTDIR)/bin:$(PATH) export LD_LIBRARY_PATH := $(QTDIR)/lib:$(LD_LIBRARY_PATH) DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) %: dh $@ --warn-missing override_dh_auto_configure: qmake CONFIG+=production CONFIG-=debug CONFIG+=release CONFIG-=debug_and_release QT+=svg BUILD_ID=FULLVERSION override_dh_installdocs: override_dh_installinfo: mozilla-vpn-client-2.2.0/linux/debian/rules.prod.focal000077500000000000000000000007021404202232700227210ustar00rootroot00000000000000#!/usr/bin/make -f export DH_VERBOSE=1 export QTDIR := /opt/qt515 export PATH := $(QTDIR)/bin:$(PATH) export LD_LIBRARY_PATH := $(QTDIR)/lib:$(LD_LIBRARY_PATH) DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) %: dh $@ --warn-missing override_dh_auto_configure: qmake CONFIG+=production CONFIG-=debug CONFIG+=release CONFIG-=debug_and_release QT+=svg BUILD_ID=FULLVERSION override_dh_installdocs: override_dh_installinfo: mozilla-vpn-client-2.2.0/linux/debian/rules.prod.groovy000077500000000000000000000005011404202232700231570ustar00rootroot00000000000000#!/usr/bin/make -f export DH_VERBOSE=1 DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) %: dh $@ --warn-missing override_dh_auto_configure: qmake CONFIG+=production CONFIG-=debug CONFIG+=release CONFIG-=debug_and_release BUILD_ID=FULLVERSION override_dh_installdocs: override_dh_installinfo: mozilla-vpn-client-2.2.0/linux/debian/rules.stage.bionic000077500000000000000000000007011404202232700232360ustar00rootroot00000000000000#!/usr/bin/make -f export DH_VERBOSE=1 export QTDIR := /opt/qt515 export PATH := $(QTDIR)/bin:$(PATH) export LD_LIBRARY_PATH := $(QTDIR)/lib:$(LD_LIBRARY_PATH) DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) %: dh $@ --warn-missing override_dh_auto_configure: qmake CONFIG-=debug CONFIG+=release CONFIG-=debug_and_release QT+=svg CONFIG+=inspector BUILD_ID=FULLVERSION override_dh_installdocs: override_dh_installinfo: mozilla-vpn-client-2.2.0/linux/debian/rules.stage.focal000077500000000000000000000007011404202232700230570ustar00rootroot00000000000000#!/usr/bin/make -f export DH_VERBOSE=1 export QTDIR := /opt/qt515 export PATH := $(QTDIR)/bin:$(PATH) export LD_LIBRARY_PATH := $(QTDIR)/lib:$(LD_LIBRARY_PATH) DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) %: dh $@ --warn-missing override_dh_auto_configure: qmake CONFIG-=debug CONFIG+=release CONFIG-=debug_and_release QT+=svg CONFIG+=inspector BUILD_ID=FULLVERSION override_dh_installdocs: override_dh_installinfo: mozilla-vpn-client-2.2.0/linux/debian/rules.stage.groovy000077500000000000000000000005001404202232700233150ustar00rootroot00000000000000#!/usr/bin/make -f export DH_VERBOSE=1 DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) %: dh $@ --warn-missing override_dh_auto_configure: qmake CONFIG-=debug CONFIG+=release CONFIG-=debug_and_release CONFIG+=inspector BUILD_ID=FULLVERSION override_dh_installdocs: override_dh_installinfo: mozilla-vpn-client-2.2.0/linux/debian/source/000077500000000000000000000000001404202232700211145ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/linux/debian/source/format000066400000000000000000000000141404202232700223220ustar00rootroot000000000000003.0 (quilt) mozilla-vpn-client-2.2.0/linux/extra/000077500000000000000000000000001404202232700175155ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/linux/extra/MozillaVPN-startup.desktop000066400000000000000000000004201404202232700245770ustar00rootroot00000000000000[Desktop Entry] Name=MozillaVPN Version=1.0 Exec=/usr/bin/mozillavpn ui -m -s Comment=A fast, secure and easy to use VPN. Built by the makers of Firefox. Type=Application Icon=mozillavpn OnlyShowIn=GNOME;Unity;MATE; X-GNOME-AutoRestart=false X-GNOME-Autostart-Notify=true mozilla-vpn-client-2.2.0/linux/extra/MozillaVPN.desktop000066400000000000000000000004001404202232700230750ustar00rootroot00000000000000[Desktop Entry] Name=MozillaVPN Version=1.0 Exec=/usr/bin/mozillavpn Comment=A fast, secure and easy to use VPN. Built by the makers of Firefox. Type=Application Icon=mozillavpn Terminal=false StartupNotify=true Encoding=UTF-8 Categories=Network;Security; mozilla-vpn-client-2.2.0/linux/extra/icons/000077500000000000000000000000001404202232700206305ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/linux/extra/icons/128x128/000077500000000000000000000000001404202232700215655ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/linux/extra/icons/128x128/mozillavpn.png000066400000000000000000000244711404202232700244760ustar00rootroot00000000000000PNG  IHDR>agAMA a cHRMz&u0`:pQ<bKGD pHYs  ~tIME #U6 ((IDATx}y|Uսwϐ$'!@C 2(2([m*A}OXpxEz_ߧjŪW2@0err3z!9}> Y{ wo6AhSB8ҳHBA~1:pSז!;};hu5@D~?|@j5 ލg1X2طEj` (VUIASͯ1I$*3ŦجiJVfK6j@'(LDR0 ={kNc"X.x p;|9| EiT:#I ~:ÑՔ12Emhs߂{f]XzO%w)R_R! f1!g)˒e̙5Nd8g kECBv}hŘHp1W@ I!QJ$Inslv^QM@]Ifz_R|0/նx3Ʈ@N!oU5pK!|OfcBYMFAL)7>]ynOz 3B*S~Ϩ4q!<_÷A`|XYXb= 999X,` _ 'Q[S$ig!U މ陕@0ZoT s9ł p?݈GSv۶nŦMp8$cg_с BT50s*G[c[,ԥ0}]cp8;O>;{ƌ>c͛l'jkO-rhh$olOqHlRJ/`R|\l-.3 N%cة*xl*aÆz(\.;;^{-j9};[BhBN0վ"[ !i(>ݼH1Ć ?|Tx?A…$)0 0c |u NԄ@`bOT".5Fyr(t۬F@ݙȘ1Gcݺu1c8g1PXXcjz@9N"e UH77{3slg6V|SMc$f1ÇWdܸݵwL$p xH!LBIIRb_>Va%K7.ԭ{~c %Xb9tDA߁@P!c:ur9x0]+I?ǎ]wY?]p%ԝ= .@Q]{~9. s1UCs]@ \*|r3 GԜ|S8r<, FXP^!p8xwx!-=Z!}F Z\Φ~8ł 4+%ҥKxcZ].PJ@Gß? !V>(cs2L8 +*A7tw'.X1'`Q1Fڧ߾vXVX,VȲ 9>xpogc!tb֬{sY3jˁYFafɓsG)} !$G])Xسw^~%`87h$K} X-VEl[ 7 2b~Pl!NjjÇ#++ WZc4u'$g'>X=X,0pΝ;*ȲNIhllÇc BNvvu[cdA`O x'5Kt+'FOGC]ftx<siYp8Qzy] NUi%o+I3!XE1v }d@B+T?CrsZIG͆Fj:&W1q<[JfuXVBEU9iS͌q='N R+ڂ|2Tk0g~>cǎizmv;.]!PUC"9,%#//O\mOѽb6 Rbnn`BPYYK.4c:m23P%Bt,cfcؿv N!=ͺ$Q5#y)Xv2<:\9@ @ 1yyxG~ MRcc#***%>4(8 0=ݎomČ3#($I[1u4ڹ _~y@II f͞bPJ5I!ؾ};N< Ic0l6XAƎxXJ#--MM9 %E`RED)Ex7(J^ACFt shHǜٳO ;;[WaSc 8XE#ۜS?U( @h$xSy|8d֭Xj5Nt*tNBdH UU#DF-`&>w\L8'OFQX 8tpq|=JF ٳVÁ"^Vl|k#}9455bCA0#++ # 1n81ŋPWWǫq--"l YHXPc[nƂ 0r(t℃7=iWp /(13kwg@[nNINl$TmmD,Ժ@Ө  IdY\UU1Sk0id0_ӧgʕ+_6 `-9qŦt<ӮhTwBA\$UUQZRubĉ1#vZ-[A]Tѝ퀤BsÁ|ӧOO)SO&'1pxGL~W >=%R?M$ :9o_tW$w!M'B~P2;\9]K8A_yp.}CzzzI,~}i2(|ƹs I0$7C5ERܱ;v,[L~qzÕXdI(`z<°| 8m9Ǎ M0M(/_Ҹz<㏱qۨBJsr0mT,]SM<B`>l7R[nÆ +زe+CUUXmV(n /ڹXdq\D3Ig7p@8WoSka[`9v؉_ew.yQcpcku3{-|̛wfIBv؉UVc JEK!KܹO>$ƕh"^s58a"*wjmǞ,__AjRJ .\5k ;ۉ[n5f,{Ə/E*l<8# cR=g8qV5N)? fqfgc֬س'Tyc4B#'''ʖ(ŧw77DXǾ,ݎ{SMv9[\q%%f'TË/'O;mbbذ-ܷuB0idPTդ G(xB),xu5vܥi=j~4}IBɓرcGWn`6mڄ .hh](%% ''Gm69r$@QT|塔%8ZZZ^.$I8{ug4QNgT4s\ @'QE/{@SST Gm6wf9I`Z@Θ ! @ GFJxfQVV$J %I Zp:m(GJ%%%.o**F^AOcA Eg=+|u+5v;DeGq YgejCS"9JJKqPUE8nTp6=F)EAjn8Ψ㜣n}PU J)-[yq~Mc j™sHேեEآ1Xv2ȲXS9'8QJQ[SZ\s5Q6!srGra}sp@Et5$w /ЅA)rB<`1~ܹxKBTTUEXz5J4<@v~X%҂m[bW,9Gqq1{YشC466BUl6ƒ \=Bn豪8Q<ɑOAݸbJ$I]߻ Æ K/CD#tb̙Gbʔ)q{wEJB4Qشi /(q/(UtR>|ΝbQ#1qD͠ǁl=~?'l۾[vguϹ cF3K_.bޫcXz|š `&eNH9s&%IPQQ_^mtA"-y7܀5O]M(ϟwYJI$I}a#+QWWgKţ@ݹkcwpUgOi:=xrTj+Uz?fݾ˗/ǑGaqN ;CEp81 '9NmVΡ(| (^qlټ8ug>&N¬Y31yd >YYϏKp)T~^ݻwxu5@\0UU99Xd1-# v:r͛ӧCQЅ_UUx薋8rr`@ ymm8{,7oFhoo_jZ !+=/vWT`wEӑx9##3prGyy-3?X`A\{E$ Cl~Ȳ%x*rssrGkk+p. з z1[hgY*́MMMhll0;*2(tm#/ܮpXn-L\İxAV/qԞ:/rt¹]otj#5ZWpv.Ơbda!֬Y L-My{xXȽ+!~Bl FeddDԲ~>748su77/nk~J)|>jNDUq455!++ EEcPRZ өf޽@X!wz@xe(/qE>{+Yt_r7c#0K tgr]w,*PRZ믿GuIԌN,%KhmmLST-ۈ63ZN`G ~ƄQ0"f !MMزe+ΓHYqЗ8rfրH(w"Oƺ)Ɨ $=P@ec0PJձ{p0 GchD3>xtwJ;pٰE^jjmFqriPBQ9 Jp 0K fMCKkQfV6Iᯋ 4H~϶5Qz)w $IUUq⅘%9#t !tT UURYT _ A@]]]}M7!V-b̙:Ԥ@'deЏN}5Bǫ&M޾v̞5,aw((1ЧwRg\} :s$#cImNޣ4xtO'uss%SD@‰/#Ztp\c3‡h !|Jȉ`.b3n݆W]69ƕ[¦Mpl@<͙ew.CIIH͛#<={LqF݇?L9 shqssssqxbԝC v򑗗+|R{A׻_ &0"d4,6:ΫOX]SA!77 #RK ^yhoo%G*I|iXXCA~kE왯gdk׮= xcr'K QT͡%d I;sSJE)Ŷ[/@QY[ ><0N pŦez3Z\ijgMͧ =Ux}7 % ( `)8' |[.OzE4>W@MMm|1LBJRBBR4c*ia;#Q-}B-[- .DFF\phCCֿ{\꒫g\}) h,Y. UB9y|.[UBcMZ0tmێUǑp`HKKR=4޻' ϧEɜ-|ȖCM*@MX-~oBlS[4 8Qm=q℉5k&&M8Έ܌ښZؿ+*poJpI&; dzRxEWEd8:n\%z'O1)>.vTn v1h`;zB 3?!*76F:V51љ*IZ-Bbnw|F)JY"׳n L$[pY@w a|g'Aj%9!D;ڸ3k`$ɛBG(X`E6|TI|6͞Q!h̙>z@&DN!NRp`0 qžjW MGtj!8v"[6Pm `NdQlAD'v;u/f}$IMO@O p8ߡTa131w}7o_6܅.,+cWv[s>e50ܺYnjt;?\,91iPIMkIm\ޯ#?5dr,+sodZ ft !X,39>M'}=ПJu/ۅ 2.t*^gZ@V+IIU pJo  z(3ߦe}, 7[D@ץ1m4,UprE{)#3rz$*e8u;?\O1 1f؎LA&&K}KodR? =s64 -ma\=9%T~9)s\n&߇%kmК3_S@uQJ=i' !\ Xb$צe!+sdZ2P8&/ U%Kvx\S˄#b$Y-O3?Y퇃|ц.~x . $. U ! BKZŶ/͞Qaګ4<|t9#| 5 h錱+~(;QUcE\f{v 4yʔ bL9w !Bp & BHSIj%EYPB[wU$0{)"BeBq(t6*0!*W n\ޕR"(HY[$zi{s>u1p~%tEXtdate:create2020-12-11T09:27:35+01:00m%tEXtdate:modify2020-12-11T09:27:35+01:00^ZIENDB`mozilla-vpn-client-2.2.0/linux/extra/icons/16x16/000077500000000000000000000000001404202232700214155ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/linux/extra/icons/16x16/mozillavpn.png000066400000000000000000000015261404202232700243220ustar00rootroot00000000000000PNG  IHDR(-SgAMA a cHRMz&u0`:pQ<&PLTE ++,qqrlll%%'̖,,-TSTeef$$%@@Aϑ !nnodde::;Θ$$&SST̽UUVccc̬aaaʒ99:sstzz{˓##$335&&'tRNS3˛/-Ó.+v:>bKGDaL pHYs  ~tIME #U6 IDATMYS@GE6gDHDP+-| "[2"ZDL6' Nn25HV;~mBtW?8lMM Y+!{~qy\;ݞe2`pwCJO/74[c2ٟ4 4X 7OuHm`>VJ&Vj%rS%tEXtdate:create2020-12-11T09:27:35+01:00m%tEXtdate:modify2020-12-11T09:27:35+01:00^ZIENDB`mozilla-vpn-client-2.2.0/linux/extra/icons/32x32/000077500000000000000000000000001404202232700214115ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/linux/extra/icons/32x32/mozillavpn.png000066400000000000000000000032061404202232700243130ustar00rootroot00000000000000PNG  IHDR DgAMA a cHRMz&u0`:pQ<PLTE$#)qj!! &.,685B#.,5"! %##"("*(1 $('/=:IMI]%%&##$BBC˒334DDF//1kklxxy⅄++,dde**+iijyyzWVX๹--.''($$&ǘ..0FFG̓778 " HHI//0GFHJJKJIK^]^srsVVWccd556779668؂}}~,,.XXYḸ.-/GGHկŗ,,-EEF ! dcdjGtRNS#]Ysh d#NqypXM_ OM[bKGD hV pHYs  ~tIME #U6 IDAT8mw[A׎h@T&*f^Ƅ( ^ATPr_3yfдeץB3f/[NG+| )%m ƹr;hOLbZ|(S3L'41y9JZ uMkS>K He IV.vÐjtt/l[[EɂѦv7u$\Jm_5cC:WvoaҰ-ug_ ^ֵ߾sq=AvhBsIIOR8%tEXtdate:create2020-12-11T09:27:35+01:00m%tEXtdate:modify2020-12-11T09:27:35+01:00^ZIENDB`mozilla-vpn-client-2.2.0/linux/extra/icons/48x48/000077500000000000000000000000001404202232700214275ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/linux/extra/icons/48x48/mozillavpn.png000066400000000000000000000067461404202232700243450ustar00rootroot00000000000000PNG  IHDR00WgAMA a cHRMz&u0`:pQ<bKGD pHYs  ~tIME #U6 IDATh޽{pT}?ܻO `1;2i @e l!4&iN!cb;h1 6R 1b$["zFZi=tٕvd|f՞{9$|훜o:eϲ&+@ %\JϛϮ^_u>ֈ.,Z,[YOh+~ГK$!D'4~wvƂJ~s?͡S~_٥~p}cu&,[U=Zy\=SgpY+ |em !.\ZZA8{bva9q< <C\ !/z=57n+/~4%.;K){?P2^ࣃI/*/n:-'k[=a iz ǀd!E5J)RhE;7&R]3S}}ϗɴP/m9icYۋa1:ն"K|wf$?YkL YlH('` zZC(b߾?)elzH[s4JҮ׳@~:7^B0BB֭]˞=>}:i"DJe֬Y̝;K.<ĐǶ3q#hMRr~g+`}}}\rWm3fw5'D£K 70XAkMkk+/W|JG?J)xGQʎ|Wp"I["iv$!AJJJAy]npZ n d$F͖cK@J5eQW_.apӐH k4YYmp[!0M CƲl3(,,ttuuELj?G.$TPhYj[9 >k֮|iV!@E]2~hu!w'].7aFkMFF;wdM\.233N8uaƀ^C4- ܃R6a%۸\nͬ_ђEAAAe/\믿Nww7Rضׇ2 Äh$xrqIT3z˗S\RL %@ww75z!hll>TRtvuqIx'%K~:;;Us][WWm b^J5k Ҏ?*544m=wi%?/eLK5,FJa]-o!77wk/ϩ)DFחSmK`nYi:>/~%k0 cՎrr~HIIq)8RRWWdž |2Rʤb\(dNƍHMME)Ekk+y:JJJXj!… xGkE^n.6n E[[+ͧrY%>3g|ǎcd`'BJ) )**rzݻ;pZsNLs?` iZ}3gۿG((<3h7yyy\v iHJ@kM~A|4?8͛'pBqS[[˹sp܀&ǎ=#33,ZZZ*eJlY{;El}~\+ƿ5 È!;z=0G[222AhysYp!  -xQL ґiYUUU ]ssh,՜~{QIOO3PUu[ݷ+r)=3g:|:u*n)%W\aGEy@vBjNOE٣lˎdi_&)^LO9}[?}qăPf^%tEXtdate:create2020-12-11T09:27:35+01:00m%tEXtdate:modify2020-12-11T09:27:35+01:00^ZIENDB`mozilla-vpn-client-2.2.0/linux/extra/icons/64x64/000077500000000000000000000000001404202232700214235ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/linux/extra/icons/64x64/mozillavpn.png000066400000000000000000000113671404202232700243340ustar00rootroot00000000000000PNG  IHDR@@iqgAMA a cHRMz&u0`:pQ<bKGD pHYs  ~tIME #U6 IDATx[pUݙ0L2$h@DAdKCP-ZW DYK:seT4{@ $LHdcfz3:>>{ Eq%Y*\0QB"7=چ?m|_[ Z*< y3fs "!BOt8=]i ob_#`/ڿ-CJJi?1u%|67FJ$.gvމ.7obO (/ֶ&RF*We2oݸBݑas;u=.R5ͶtƿnQ d5VQ|9?0f~@;"~Rʘ:'ٜ_|r#hn.Dso#EJ (orE䜕)lλfo"\ PT/RS-)%ցMMYe)9Ix ½3[ǘIq?$kg.v]Ƀr⓽z@ fï%&$kYo)_!E$ǟ Ƞ*8D&S @UU(u}@zkbWW'rrF(04.9!@Ii 6UE{[;Em  >#4I'|-zwd%)]=9[|cb!33OXO,ѣ##|pΠ ,XUUAŶm{ǫnc 1L>EEoЂ߽#E#!61҄Id;#90(-+jPJqA. I"A@flݺ GՊb]P %_7 q 0_Q@pQ /… aI [Px$[5h>98c:"EI R#2$$c1 0fgW=g̞=/&Lвc z$f!F+6u׋u(UU1c ré?֯_ͦen>}Ҹ}o5  |GCqjsQ|r̚5 M999QR֢y-zx_ܽ;l̉ f3+t jRfZ5zDQ ZQ " $ ovY0qD0`2co=55!vQU])ԩcITs@$m6u˅>\։M: H,ɓ$ /ʕ+Z}J)9N- ]q%L%zfy3Fj+WloIKV )))H [S>m͜9c|Y8aXVX,M?,x`>>3Ch }wT_NX[ъhQ,^( 1W7ampѿQ\\i†~}a-Ac1WU,Z֬YBXn-yg<0޹x~0LZ]~i H~ )w}(.)ƅ  "&Lͦ ^G/A WY,#XӰfd$I= U5Ê &l6;A%PUUެzS9Dnn.HUގގ@`Fp> ^???cǎ_ss3v؉8W4sA&\vMA_ ZE0nK8ynWWU0bԨTdgen!E dW[^Gee`aE63gE/Z@x\ؽwhsaժ6}V61\~;qQ<(EYy9,x%%%c WU'sϢBg]]]xPp]QU䡯Fuԩ(0uD UUEZEE(..Vhlt&xf?6Y :^̓RtvvbKqL6eeep8eV47tc#t', <8K0y;(xUdf_E#PA5 #d?H? Q\C98NBM9X?x%hkz!Z-KJCmO/$)C$B,Y򸶶{>ރ'O’܊ HcWbAt+/QQ1B /j-g~ubٓM஻0mT|駺6hed!3 L")4U'I;v5s7L(++1>ُK@d#Z,8HI1!+;+t1;wAtxnu,pԦ#Dod`eYJ(Ej9HM5a]eW\AɄKcłBt8{xOFq73A !}udg;j2dg =!6ұ>&9R]Hh"6+;ADNKYUd`Flv􇴩k}0gIDATx읉vPEmʠRp*+ P`3ߔ !/{CϾ޼/B $KU%@ P+^ 285{_Nk)ीNB}I~)m#@<NBR~.o-[K% i9$KʑxY R%} !. Ha%:I?k>8:!"U+@E/ܻٗďFGZn,@{@, ~ d}|{0[MOcu0X 6 0\]]z&wQTChQ} k'Jr fɟF;y pa vAg")d쫙 6 z8&XD}k\<\o?C pl7[o,* H\,8+X6O?2 u@O.I1>l;#?Mѿj&8]* \eۛ臚V5PgNjͶkfh$/8 (,H?xC0T(Ԁ+c dܗxH/'R}?%w hR]W?f`P~~2nܫ'CC̦zPw ho>D8Ex~ l)?@oIR;ΈC3ٮ"|l6?!Xk{T2%KOm O7:U hB {o%@ƿA-c>+BXNz,<0,+Az7/cn>=#%/%0 w ) )^@?;c=9t (?=`G 3o?P,5?!_^j} G~Fz (?J]$3S움i`_ 3!%U$_ .ǧ^T ׀}F\lq/T [`ݾdD͂ NԔ뿹N5&(\;#ó#3_5@? zn9;Ý4( DŽ&4@ ښP# V.w/6df~ { DBǀRPQoX[>.2JAhTn\>6ZVA!ϲj|r`T@(6b w%HmGϺq䷳(LK?F;@]!?FV`??B82@ JС?&h$MP?&~vԏ n :@]?|8ޮSsY~<@ 7rRP?ԏ(~L1?Gx@S,I9@ԏ4:@2I G[ɇ;8]@Q?'GjsAx@#Gz+~O x@\`y)y_@ f0*|PsbmO ФyDat}Sh0)= C<l_ mM,G3y)y@/h,i0,Np bF?\RBGBPAC(45h, wXx4P_"D`Y8$j/@ٳ7gKVr?8 : < 0ttUb@me`@m7`,m 40:Hocz[n:H oZ$7K@ '6 'd>r8$lt0{x$ A;dinA{]`!6`^9@ (~n `܌pBε2 Z& n%xd>|p BPkv~/?V,0?0V]:ЁjT-GB {WӅ@ `܉&54S_Կ_V` `'Z@Ȉ,*`N70 FQ}xI`F!gjMl(2(~ \ijX%00VȿY'KM, gm(qeT*Y4PjPz> )"9]2lNOPk~t+`yXD :6me'&&p5C6mp$V'BD+@硗/Qb,V8NS!`Suw"\Kw(UoL,%Gp@ !CdI$uVF 7 ,(   v4ż ,Sľf'(Հ&b5ωx:aЊX ` (DV#.Y U:KkSQq,LRQRдS3 ` pi9NRs`XHͻ V@#"o'(O5ǮY fFS1s`@ '?vFFxA tߟMSs3О4h0րyZ߻!A K 1*@d sf>co_c $ c2tkCXR/I"kH^5O+;`G51@ wi0J` 1c׻[oˊIHIlUvH'9s_ׂp "*M@b>X5W |땽NJLrTF|VKLIOOSWE]JzqvU%𛀥cj] 1]4/G Rw% h 9Ob 犥 `t)/VֲF;p @`k9 ic =7f&R ?Pb?2ECSzгPfSO:y @.>@x/[nd/^gtgD-.`=F @]4(7vJ] @UA` ?489@ -мLP'Fe2rH X<e P'-e302`$ e)T QY?i3)@&wЗ*zY@3N,,Q74E t 28> 8XA 0<\ ud _~ `ƒ);a #H \+co&WȲ'.?$ىo!4^[( 9Ij`S `|;07TBaP} B>L(:$'8 @AS<%`zZϲb(Pz$LFPtt0} /x:\ %PZkL>~. (2hJثp'<oENQ  ,Oc@Mx -$IF 0R\~-64"M xvJ Λ"$Am$~z([Q06v ;@m#pe$?&W1-[o&y <@c\f)ɚ5Vx4L D_M@ Gn`Bx@G 4M<  _ac mP1 ᚌɿ)3@h)ɿg!Pi\Ɣg"nd1X.s\;qkk^ D_ ` n >H=RsMwL1>`iAlo[DD `0l"7?:+@{Йh[ p͔<0 ckTdh,oC;?nOd}70&ϹXZ:8dvLF0:nh)Nj%0YWP'S@ J3f|"%#xb,|Bc@Ӓ+W:kx$Ɣž :׋q R Y:NHs*`ik/l ) Q:'t(`>e @0GiYZ8kyg'{7tK#\] OnҚg}yX`q uT)|s pO @&{^Zy5/Jb- SZTh , <g#gT`K& єq ` zG2>v p:#hML(wJ7`bb@I<ěm 1@'D%Ϛ fՙ8b-"w$ 82@K:&b;++"Dq@ Н:"ٮ Cbo NS>&&E=@#-|c]I7c  ` p\QT*쿚mk|\@?8k["`' YƓ;h\" `<xsw ``_Ti?P@10]_+aоDU 008{߻?ڎG/P"g2a8=0D X @o4A(hj@9=G|"whT8W"[H-@ C4 @pQ`_2 T֕>>b$/(tzQE좊vo{# _P .שaQ{`EHN;;@'k)B)c)׌Q 8P 8[_=`mEr S W9{ p{w]ȁ7 -t(@x@ VHnaSOœP: V)T;< Vv*}k +>nΧ~5X_[,?SqK:j@ Y&H$/6%'ۆ2?Z@j/phŘ>bSM/}/TIb0??= P4D D5-[>SC@j0S$gpuM_*uUo,B@#d jUKߓx&pH@0g$x?;M\B GK?y # ?H½ۯ>`?a9K-6`t t{۷oπ># w0Zu6?X&T>|a7D]0 58KeLb-A=o4zx6Cj2{wM/8aq&N0Հ U *xhOŴOWc @ CXgtj U@ _fl/whm pn>c_#!@`W#pHob P-@AM?On4okPHv([s@tl ~Y+CB.R%@&?م @߿=_;] *hl35}lۨ1] ';Gs ך0jV|7ey%߷4M&4}n;-ٱw.{ `X?ώW ׇ[Ya_j&L?{nB:lC,sg.LmuALGQ@F/?/TqWS6y??ga`&)te7s[s]сm "RWG]]oBhhMXf2t|o'zh_KajW>[[^ x58a9tEW:m(pv/-\7f# ?0̿@4a @h}t} ^+]tG7@*AgZG~w@'s  RDw(_ @ . d܇78 Q!2xC*G,nr7ԲCk2^ _NJ7$K_翟:OpXH~R@X'OP<vp6dqP℣ !M #@TuGh-D,*, [ *;Hxw H@. j HOm` \  V4;"ofG 3},( i-;~l8]'zv% I{% SE$gu% QDD Wm5W>Žhh9,( BIX /7|8" `@uPyC X` 8~·LVYq< &AE9:}}ߝ8&&)Y8/iz&@_˜~UjLP8H7ergvIK_6z^?CngQApCYgrvG?.B3!zkY+r]ˀ >@]ξ @06ytȭB) 7)^hWo17d2? "{3R@39%~ " A<)4/_p3 tX'rFp>j `3^o9D5@]pY)87sJa j;$:n E un=\pX,wO*#C^rU)'/ [.iPOhP@N8XS8EOI ԝ 0z oUtS78 paF 0\|zZPuׂԻ֊7=3N[Vp. /@(+&J7WZ̑SV,7 p Ԃ}ׇE\>Kh1T ;Nr>vu:x)9R(lϭq6P3nbkCt֕.c}{DQ`{u<4_nIS2@9-XF+`s^L )ƌj9jI 5@'޽ZK V nz7$vI 0 !5(v|ǿ➄ 70^=My`6c#Ϊ>$[bx.0"`6Lx;  @ @#@+0* |!=X+k: 7:|b@-4,AQi ,95~ M 0YQ+Xi @s?k+R@{!N`(@/Y,.8NL P8HH R @#6mFu@F@ (+ S{)V0Pqɬ0b&Z$NX@F:3) S&TιK@!XS "9@P˶ ,90Q4@P@P( CP @HB;fF\  0 x.0&:745J@4aN0M W)()i  .͛A&,`h*ԁ s @}UMeJ 2fU3 u&=`1q۫&PAu7lp/N05DP=`6,#@%X6wT} :n̋lt`w;;@3s|9BP|R  Q}(Hv`Lh np+V 2I `.0v&@w"PGKp]OXdwOxh 0ePe_EY]xȳӌfO jG U)^%W@@Dm)/ clَxݧ?4 (`DIz"&3[O[iE\-h6?(EFr&x~OU#,/w B$.`; \nt.M|E eW@R"%z._H0/ RDtxJb {jbUe >Un?50y;@K$4ȽA@5 b͛lHF?\pi@L,\?E P9Kk@Z'@hQ(?@ ?~ogk:|V@ >0 7tf~`8pֻh88xhs@ۗ; Dsx" 0S-0f4⏏eM"A݅㿡:)]j o`C@@gU0@WBD?d1P3;% |0Axyh -H2c-̱i\܂> @'GB Gw+0Cu V,wvI0(riq'\.^/obooO/`&G_; ʛu3'0zo~@Orob 4FC i^(ps̀w+@*ڋ=zS\/*oܚ@M"<.a=A*@^!MJ| W ~ԯ,DA ~\3@׀w` C?p6c3(0%@ǂ '/Y!p'Vyz.E{LYe\S``(YN`Ź?Q (0[6`p2w<纬2@_N'ҋ&>k`Aϓh,G51=. 0:>MQ$DfCm<6/ ;!eh/' P`wA @ S`s´:0XPwO@*B^ >G,79pSO{/Rtmh-@V '10+>y@𗃀ʂqncZD 'cwWxf$>p~.E ]7VD=`q{@DA<(`G ixL8nCH l] aЖ {6,#@n-chnZx yn[@ '8hM5v >(1paʲ'70@jNgcɿ= 㻥ړ  v~T칮^3H*v{` @ x<-0l)u8<$x1\(E`w-ڔkp[8 3Zz^Xo5$@@7X}?)U%.w@}+ȔREeյ}XŁ(*6pPK_@ș? A>࿒i @M;=T7$u<@ Po\{QZߔ/AU2p Зw9[ ԆP.@>]&oXj80W#XP!FSh~<:.N\z0 z[j(=_3]eSf9L&gϟxUz_)XÀwMɠ%|z`ٟl=f_{Ϝo"W@?@pȟ4į~-/!kϥ'lEGl&xk:2 ?9Tp'IqQE 6`^9Q)oFBuWt151q?U *'$|:yG@?(`aWTe<dX_}t:mf&as\(8ѼɞA8N ?LQ߽@=\V})79WS& #2p= -@E#_rߧi'Jwmmk~?X$s_hD& b W'"?TXx/K`3$)937Źx}!࿯J@N*@ 36l%=_) MF%  Z*s`6-ΛV~G7ڀ p ؼio?Õ(Fq;B)=-8",d&kC"a6pq2qK7 *(\M}W?ߘT/" (⿱p>,Y l__AyQw˳È@uI |ϯ+x~)KOK>GP0LÉ  @l u1>pCS?'v{ @@=#1Y\(!Nz8ymR*@].-W _;._cu @O pm1@KGg/dܴ^_ ?.w3w]kv[r_?.k!< p^UY|7<4zF Kk RnWCvL8p7hVG(rRe!4ߞsG)Bqhu iE\  _vYЍaӖB2Fjs!/f> QS4-h7f?@N{{碤6 Cѿ4PJضi' cvAIj$|3{Zvb3@ L6`#tM @|3˿ ##?  $0a Ēae`7z?r$`,#@0t3HdM&Af<& Ё C'_h-8d-3@?WSLep`V? 4(6axQodb%ꯀ)XM-r2],8忔  \(MAM @2(nU_E0Q/̋d ěLE{`7.;<.w<,6Sw#I@(K @ܹg'UlRfx&7LW!/(.).D"& oL?g9`\:&<p0 !>Po]232vIL_أvS/ |p)]_z6\e)H0 !c+7. {_D< @ pYHV*0e HiFq꣱(̥`w 0(7yZ7OC%`+PXYݎ?'d9?Or.>0 @ ]{_<$4RFs{eON9i P)Vqnxdd@_@ι@`_kGz"`X`)uOki ;gV.@.& @7۷=0Zػ'r8 <0h7@7YXl' x& +hܾ5~ 0w1Y))W.(4:|6hPo@|}BsT poG2*!0O?p^^1&11?OB $pM sP ̘ Df[}t5)#@+`L& Yxr \@T#3[@Eq#@؀hPno|=6 nqp(|s6@xR @ 𗽻n0|WDi֪*!a6^Ai`TS@"L \%aeNINw> y?~  ;$94`_Tw0j|^{Qm/>Uo,/|=@  3i |%HPN&i!:`Kȁ)@w>0% $AC%@"!eHY6xJ$)whPl걚!L+}1m ob=r @rM@'} ;ne@rM0贘 0@U ^,tkHTBL 'HVp.X"?$+N#&@ vt@r 4 -iI/ u2#BتX4ι+-\/ \r`oM ^$-O+&@rW* q~h:@~W~yH34`  R?q`A2# 2rPlϿ ?> hdHd@2Jf _!@>ưZp '7amf``cz(n̿ dFS{Ɣ g%?Hs#)hZ=@aə<8m`' }/2+a i%Ӊ}Uk5_qyIluDGy 7xQ̿ "lT@6D2&R@4T,~ `?jlNJ 5S Ȯ'e %l&늅^d iɰ>~9#<Ǫ*o/ah %C ?dOÝRd("x +xdlOGlG&/lʑ@#(">3~DՋ@@XPKAL/9!vAH | #jMb~4c;! }|2 W !2vJ:xsliԾUL2 "v1{x9 `TS?>* ]ӰNr Wp~ hC/?{+@jTv7;ws.>q>@GDyܧPѲNbY+@EaYH =u3`5`Ws' >( i84xG 7 hH B?** Xd:hl[xsYSJrh@H'Z hj fJz D 0fJcV(kkd@0p}V@? BWI!-H-{C7 zH$@0`Ss#ܳ B`8njBPdTf7?2{xH/4 `Ks0?6} Z(Փd@ŎG9:x7op"=@PADk`?ݿ{*)ؼN BzN͟=x# `,pc_`.'}fLJ, jt8  ](:p'Gz`yt?(v`/ýz>f^*m&N;q^\ 9^|>P+hXW/ܻs3/Μ]X<6 Fo w+Gz`9vcB @>k4s{ 8wVND 0`Gdw { \է /q@aDp{W.p.+K+wy$|* @e>GDuaisk  3.[G.PVq ıGmb4WWUw 71 @ #l5b< # P^o]u l_xeFسF'`py1;6*TyO{^D ^5󶟂fh^1@˫o~## [)3ןk=)`f[% Ov'z|%cA) S @c!?oi'>]S@N'z4X8cd|& YҶIJ;ަ $(7A@Cˎ DQ*%R+7Y@|}t̜ þ 75]@o˂MJ/X!Njn 1lrf//CH6}]c1k94l08+b5dGa`~H8<8iԫA/fY{C-Sg@#QE_`/^S@7hqEj)^ `y C4:@ώP g߭`1Vgx/(Prnp/F߅G 03ɥӹ艛?tB- @ҶWտa O ("/gSMHFH.~$8/ErxN':@^`G51FH0 F_"矞[2 `t&[EOZxीTCIk"Z@=9a`1_^OQiP&F[(Va|_?I(by9[=_Ҁi?ف$Q@?N8,yxտX?Qg0~$\gvoBOUok"p@R+NNNy~<!&Z@M1/Ӊݤ~_)Td/s_ ֝j919M$ܖį,Yн %"M*&l6+Hf?0@A/@A+z~*; MHR eV#` DUE$~O;Л@R (znZA8mn@ݼpsC@|d.I\A @ d# s/!`R`_4"F?R{@~/.uA V#;<0"PI0W>?|w H?ts ?I.P ~a+:"Q&?pg( C7^\9 aE]= Vv1:"Izbc@'y܎.DZ"Y1=>@uE>e"#@j@w"aO c@V@uE 0>[ƀ\(b1 DPb 0Flf uEoF@(<_.]16(.^I`bO 0bȶP 08%nm  JV(:.l<Ӵd]xu@:`w@o.Qu@W.@ . Jz M\L.T2z ((`׀SX2t|` :@9 xt 0X8Dy-A^_EjYm^v 0 x/N/ QLtNۀcX: `h.6A-l\ZT|F@ o@F4Xl3 @R@1& 2!: 0j>'5$ -sF A},ւ V[V2t78͝\I]t#` Pokp >r Vt :O]|V `x|Q~j'oP0 *9@ $a m]?k !n_mdH3rG_<H`@/G-4kBֹ5 !`0@H*G߽q/~B>!;ӿ0 &{TQBHB/%?,MUI̓ i9}`Ux6/?7jh@ ϖI`{--/[߀`U@@IN~oLU-&_?,4[=U@%+` U{84OF[<`{R@g-Fsy_?9 x Xd_ϧ3 x,4n@9?f{-]ԁ5c7KoOW8 f-@/&ՖwA8={*thǁnp1:*@qu*g?/. Ƴ=ԜeǣV_-  R_d/ٿ+" &`1w[vv PC3Y QAV%]^a#Sz?3ß[mB0xvo:b ◭_Gfʿ1`h $Ş=?Ϫ?7)R } jHxB@*h_?b?FZmS;  qUGDŽE\/b>sßRpfpd4ޭDvحOڗcG!Cx :pr|`Z@ H"a^ovн0!}hfc)ߋyț0q0'r-‡M>bS??{@ @@; )c DPh?ZbpY@|ͭ0r )D"||Hk?L0< kު #}(spCH s‡} ~?F??t `&\6 >@@nz'W;17[G?``r\R>P0t7{gO? `.  24 +_覵o #.K>^{(҇)'cJL\@7G6x^]zIb l,1$[w>P *ajGğK:nV6wğk@jN'⿏|H{?BVXҎ_G@* L?q0IENDB`mozilla-vpn-client-2.2.0/macos/app/Images-beta.xcassets/AppIcon.appiconset/macos_appIcon_128.png000066400000000000000000000147451404202232700324520ustar00rootroot00000000000000PNG  IHDR>a pHYs IDATx]yE]}wՌ60sD 4;;18;;npy1"":p@lDnL,4rWޫ#*^3hߗ_~Y ;C{*v4J#NcD'$%n0;TWtd3D`bWtWQf3Ss:T5=" EH)X,~,oPk럫i|٫/Qb/5xɸXCUN>YZ|g Ӄ;RGYfҼy,ghyffK>+ ^8r̃DֶYE_ R/ez ^sz4uTaEw^&\1O9LoAD)0zȩj׈_H "m̙">@omذ\Xh?AX:AwϵgH ~˷0?s ڸq~ꊢ_\&ȩz0kOeYo%@ᅡ[%@\w[EHv5?,x瞵av١tI{F(gY5 h vԶM9zneKyr&S9@iHdLxYfرcVbR(5 ? |G;E0,y2lvEwsT={67.&L@'O4I~\Y>涨A|w{ NŋБ#GϏڵ^|%zztDJYrZi`AvyϙRD?x< l")ִ{xvxvY'1cƸ:&&>kb1k.A15/{ ,=a3AP`#QȰq4D=Se?B^hieeJ +l[7H{gΜ,_]/e-]=xfWΥkhc~ǚ%#G>g$f祿ޘ1M4yd?04G0 ؅Sv\(_V9A0A%Kq"WP,V?6mbzzzA53GF*sj_ygEN[By\ ZvemXnL8|B?{, , ,D6"|1Zbݻβ2@vƎ`CWH/Bv}8IJϘ1VoIS }Ѣ%撷s$څEZf@OfVW(c&qLcXO9bRGOmxb[mZqlDј{5Q.\G)u* t J=ňjξy^yJ7oʕ:˒%NrJP(f?Z`b~a+W?Moҥrv_;D_G@s_`d[06Dm_W:[Mzb\C^jV.yQR`:^ ~l4v(cfo@grDm"u.3X^9C*2 ({+ 3>A `Coޫq `p`8r0b1 @vfUaAOJI&\"EaM{gSƁ~<&N.|i,BtgyZxF̅G%%  9t<Ν+$&(X?V [x:p\DcŊǛE soHz,˗n 6pvhğ1Btb m@[ 6Щh0By~ٿ?m-u2ßs(@|s4j?jȲl6pOUz8#HW@c?g[:XGȑ#/Ǐg'#ybI@'v!Z.^vuvvX$>`i {daF8ժU_lsX Ja(ms2};vL89H."B/_NL̹CF be0i\ 2dޮ]rd"<]l_H<0,a/388%.(gرchqll_Y8)WE5_>}-Z(/70$[_@---FZ[8#1/ x8b#KBjK̹ak9\DC,cgRAO ]{1Aco>["~"aݐXGĬm휏GdW{ᩧMMchhs!.\l "jGzLŸdUϏz_mj[ r2*o;3A˂@BD?a&5Zd)p+/CȑhԨFvw_ -[yَun3O`(7-x"Lr~JHd}E שS詧+nW/B`:moϱ1fI< !aA!W/a:=ĸoH.x=V0/OC!/@tW`.mrjzܻ^Zvv QO I|`Da},t݅<p&< a~*eҲY˓ s @8w:'k_g K &gδ^ٷ{DD ܞ#n* 0?;9?8\ 3EZ TI#<@vps2Jq?QxQ?5$pQ" NpSѢ"lBa1s󌁏:%~VDREWs.~~B9R^EBF~ (#Dtwy&WO9ħx,%`'"E"*$)0sez^(iC(&>'i45gbrR`eE|ŗTT?٣L3o!>PYQ}Jg!NQ&$X!vڵ,/jHq 7# +JR2Y%`vKwvkFӿxaA݆\D/CBJrrNT&OF_ckь;ˋ[U՛A H*#̀\=7!)`QQl "|86ʊWnjֶ9Ĕ؁ʛ9S̊`=G02f<-n+уp;Q h" Rʈ>$~^o0…= cu ; Of9"}Sè=JR*X*7Cx'шʚ&O6@pFgMusDJw$-RތC 720~rWƦ\*Uyu`*˖3\~&a(uM_W ruޚϪj(ʗ5eyJKD/?GDznsl|jit&K'^F6 GDcuxWP$Uufr[vH-D9ن@cWUY^'Q`ځv\{r038H ҁx,~t#O>N (a k3陪6ޠeHUv47 iRSR0?f3r썏J¸x["/u%8t{N7$K*`|B=kUUN)J\/gjD6wuW!itu0ER`w&Gfά)@h01g=nf%[- 6ܢIENDB`mozilla-vpn-client-2.2.0/macos/app/Images-beta.xcassets/AppIcon.appiconset/macos_appIcon_16.png000066400000000000000000000004321404202232700323520ustar00rootroot00000000000000PNG  IHDR(-SHPLTE%%&DDEoopaab445&&(~~~~~556n7tRNS@1UzIDAT] Ch7'J/OW^bHLU$yDZ<~UVH5@tV&ׁ~H(5Ŷ{XZ6=^#E`bIENDB`mozilla-vpn-client-2.2.0/macos/app/Images-beta.xcassets/AppIcon.appiconset/macos_appIcon_256-1.png000066400000000000000000000115301404202232700325770ustar00rootroot00000000000000PNG  IHDRkXTPLTE-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5+)3+*3+)3-+5-+5,*3#!(",*4-+5!!&'%-"('/==>zzz $$%OOP!!"llmLLM112@@@jjkAAB--.KKK]]^kkliijmmniiiZZ[](tRNS ($N 2GA-7d|;&nSè薌Y߼YIDATxkO@wf ^˴$m#ư*u7:=t&细|ORP16 d0ƒ48:l2MS$YmXJ?f[hbV9jZiwJz9f5,i)-&H:6XF_T ́Q -ۣGWt?4ex & `~evMI= auSKb( ly l̼߄&4 Z]Na < BR<\[ Ui \|Q\ 5`ߨhm5$;T59R19# st[@ U0?87e#o1{4-x,+(VM`AFӷ56MAtZdoprn.,(1X)PۇIgzJ^[tg?|of6W:xW0ݥ+n= "xq6*ECPz-Dt@@?EWL{0 %U1cTނQq#O\f2gQ|G ?i [@^A_ :dL(Ut@|A_!l С|A賗*d hwi([AAX!r^E@t8!(a!?f/w L<+!6~$t5 k@rJnSaT  )`:7z),Ju?o*˻)l gBx_@ - g@S`dW(,5uZ*0pPpp/6q } PNd'o ,/ "  9a -G^2IC3 ,*-=m>D4cm_y'O$ sBrEE*v~Z-k @Ig Lp<$_?? ʅME>>?vmbΑi(YB).gD.܋QYWyB2E:7 cCXK?)>/A)] G&e Qbń_g6B(k!;9z@([ *P8[| PSR+ XH<^ dX\^ޔtf&z08{Ru ,)~*@B"@-K ^7(w#׬ ( F;2R,H  O% E W1 ɝbK"rMCz_k΢w8+*Krr10ڭt?#dZ%J5Ӗ'EQ|Tí @޳¨oڇ8b3<2MM-[#ㇶd?f}u?bdwvL_l !`|quFʫWіH  ER< 2?vg+TNmoZX;? o?}gxS͟^{Kq:3G0L ݎzŎe|ѽ]s*o#:O JN(~[?g5 hSOW2KW%i mExPڵ$7巩{C&#{p"QͣHmApL9Dv>EG 6Yل~?Ά>&cP67*H md ?mHvZmm&t 3lg0eя {+ⁿ/$x ]8B+f?@')[H3%7!3` Y9 ̺8p\A[3)z^(UB,nK0n]eG_$pz 0gWXWq S< Ӥw~w3ێQϦ?'޽.RYa!ρU|?|0+0y 5es/έm $$$Nt+ِP iKizyH_j;[FNwx睇edfG@el0ʡ-32z A (L{30ZLΦ)vCSh5F;kL@@3mZRPyu{LrY8'@* PNP נa)U軵uL }Cű$7]WcK_[5>.W-\.Ltl@zNiNXTǥo ʠ 0Sbr]kO> iH]P&K?>S+@}kZR5 FrzOpJ H5eOmk@q?K@RPF A0fGm%C2ĭvnvV^lJIh|5hvd%o!p $tc}W<M(~妁~"?)E_%Pn#O)B@5P;Fr>W@Cr>ЯB}.GGj}Q z!Mt#AGG imoӃ=9>R8Ua>R7J:6˾wzrRGZdpnU;Gz9+JRHk56H` vB *VL*Rc;h2cUdv?.l9f/ U^ R+*o rKrh"" Q|řњT>S3CCߦ#>0Pp6500MY &V2?2qjph%OVM(9#yogR@n  Bcs;MWIkC\{_~ cE> $?7?12bi{ ]t6coY=1&蓮Y_ Eq)HTt/]5iMl IR`1l1KN='T‰,kaiLB<ҿ[B9'2ΓIv3O^g57[IENDB`mozilla-vpn-client-2.2.0/macos/app/Images-beta.xcassets/AppIcon.appiconset/macos_appIcon_256.png000066400000000000000000000115301404202232700324410ustar00rootroot00000000000000PNG  IHDRkXTPLTE-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5+)3+*3+)3-+5-+5,*3#!(",*4-+5!!&'%-"('/==>zzz $$%OOP!!"llmLLM112@@@jjkAAB--.KKK]]^kkliijmmniiiZZ[](tRNS ($N 2GA-7d|;&nSè薌Y߼YIDATxkO@wf ^˴$m#ư*u7:=t&细|ORP16 d0ƒ48:l2MS$YmXJ?f[hbV9jZiwJz9f5,i)-&H:6XF_T ́Q -ۣGWt?4ex & `~evMI= auSKb( ly l̼߄&4 Z]Na < BR<\[ Ui \|Q\ 5`ߨhm5$;T59R19# st[@ U0?87e#o1{4-x,+(VM`AFӷ56MAtZdoprn.,(1X)PۇIgzJ^[tg?|of6W:xW0ݥ+n= "xq6*ECPz-Dt@@?EWL{0 %U1cTނQq#O\f2gQ|G ?i [@^A_ :dL(Ut@|A_!l С|A賗*d hwi([AAX!r^E@t8!(a!?f/w L<+!6~$t5 k@rJnSaT  )`:7z),Ju?o*˻)l gBx_@ - g@S`dW(,5uZ*0pPpp/6q } PNd'o ,/ "  9a -G^2IC3 ,*-=m>D4cm_y'O$ sBrEE*v~Z-k @Ig Lp<$_?? ʅME>>?vmbΑi(YB).gD.܋QYWyB2E:7 cCXK?)>/A)] G&e Qbń_g6B(k!;9z@([ *P8[| PSR+ XH<^ dX\^ޔtf&z08{Ru ,)~*@B"@-K ^7(w#׬ ( F;2R,H  O% E W1 ɝbK"rMCz_k΢w8+*Krr10ڭt?#dZ%J5Ӗ'EQ|Tí @޳¨oڇ8b3<2MM-[#ㇶd?f}u?bdwvL_l !`|quFʫWіH  ER< 2?vg+TNmoZX;? o?}gxS͟^{Kq:3G0L ݎzŎe|ѽ]s*o#:O JN(~[?g5 hSOW2KW%i mExPڵ$7巩{C&#{p"QͣHmApL9Dv>EG 6Yل~?Ά>&cP67*H md ?mHvZmm&t 3lg0eя {+ⁿ/$x ]8B+f?@')[H3%7!3` Y9 ̺8p\A[3)z^(UB,nK0n]eG_$pz 0gWXWq S< Ӥw~w3ێQϦ?'޽.RYa!ρU|?|0+0y 5es/έm $$$Nt+ِP iKizyH_j;[FNwx睇edfG@el0ʡ-32z A (L{30ZLΦ)vCSh5F;kL@@3mZRPyu{LrY8'@* PNP נa)U軵uL }Cű$7]WcK_[5>.W-\.Ltl@zNiNXTǥo ʠ 0Sbr]kO> iH]P&K?>S+@}kZR5 FrzOpJ H5eOmk@q?K@RPF A0fGm%C2ĭvnvV^lJIh|5hvd%o!p $tc}W<M(~妁~"?)E_%Pn#O)B@5P;Fr>W@Cr>ЯB}.GGj}Q z!Mt#AGG imoӃ=9>R8Ua>R7J:6˾wzrRGZdpnU;Gz9+JRHk56H` vB *VL*Rc;h2cUdv?.l9f/ U^ R+*o rKrh"" Q|řњT>S3CCߦ#>0Pp6500MY &V2?2qjph%OVM(9#yogR@n  Bcs;MWIkC\{_~ cE> $?7?12bi{ ]t6coY=1&蓮Y_ Eq)HTt/]5iMl IR`1l1KN='T‰,kaiLB<ҿ[B9'2ΓIv3O^g57[IENDB`mozilla-vpn-client-2.2.0/macos/app/Images-beta.xcassets/AppIcon.appiconset/macos_appIcon_32-1.png000066400000000000000000000007741404202232700325170ustar00rootroot00000000000000PNG  IHDR DoPLTEBBC``aSSTԋDDEoopTTU&&'㚚}}~Ar tRNSqX!ѰЬg L/IDAT8˅r0E#mnPElFiaz?{\ؘf43r}2 g1a!=v&סGKIu|jKCQ7pXY,B@Gt*-,90Y D,łAZ^ɷ),mh @ca fG9J;.mk]=y~JXn RLw`m>0bF#?fwGXn~#>pSdf4IENDB`mozilla-vpn-client-2.2.0/macos/app/Images-beta.xcassets/AppIcon.appiconset/macos_appIcon_32.png000066400000000000000000000007741404202232700323610ustar00rootroot00000000000000PNG  IHDR DoPLTEBBC``aSSTԋDDEoopTTU&&'㚚}}~Ar tRNSqX!ѰЬg L/IDAT8˅r0E#mnPElFiaz?{\ؘf43r}2 g1a!=v&סGKIu|jKCQ7pXY,B@Gt*-,90Y D,łAZ^ɷ),mh @ca fG9J;.mk]=y~JXn RLw`m>0bF#?fwGXn~#>pSdf4IENDB`mozilla-vpn-client-2.2.0/macos/app/Images-beta.xcassets/AppIcon.appiconset/macos_appIcon_512-1.png000066400000000000000000000260601404202232700325760ustar00rootroot00000000000000PNG  IHDRæ$)PLTE-+5-+5-+5-+5-+5-+5-+5,*4-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5,*4-+5,*4,*3+)3-+5,*4*(1,*3,*4,*4##")%$+ $)(1!ҵKKL<<=!@@A""$NNOlll...iii112ZZZyyyhhijjk]]^ӆ,,,zz{MMNwwwOOPJJK8r3tRNS "2[F.&,)0:6(nWI=7+;o;$~)$QgR 0N/hHy?<0 ;;W-_4(.ǀ/]oJTb:U.s9ܾD:IPP_9ɪ ,PKrB.GE;()Vu EZu9 W)Q+V9쯟-,ާgؗWQ5W=0 ]ŃCK&w(;P|t1+%C t十,o&h%,Zg(%a JN?x(0HhHElSNC( BڒE*Pf)aGpVBů%,"M2P/El2џ?('mcSoݯOBrݣ?2F}`w"-eqpt cfCM k9,NYVSzb 4d`C7ބ{*OCXM+0d;%!6o8~8#m!X A?3yM?6xMA`N@ d4=LXȻV {4qD`Z_0wo&!)EElAE 5\Lb !<.݃G`ցj̷q%xYu s/-@[@K@{ EkiN [@`^;@0^*@U FD&3 XƁ!(󶱲n@ VR0{DyӬϨ;@dfR ~&[pdy0Y5*a@2>@$kk E7ױfxhB-@94$(Ε?D B觗3c<4 0-G<*[ @@n@ A +Rg.`TP]LOUe9P;Eyo@DWv)cV(6|J3X\\@ RO !@?9M4-I矏[1w="U@#̶ݟͿ^sKL$~ @«Ҧi_DB0HF@W!)Q()*Bj¤p ^P& Mk{@hAIrwXրF&aO y  hBmQq Q5{ UB$"gMߜ&am8 AW4pl2K/W)m_85W@ H725;o _y)| *hKD@: X /󇔓@0ys{0 q~k>0`@"c̢P4K p+kTx0mxBǕB$*Hw)Iv/@"ǣY A?"@Pa=]R,M;:5"lǟQ&&@bѕŝPfnTa98DtR Z "K ]$| p3A`ac 8 'j # @mR t.7 ?]yU@BS }Q"v9Q3|;%Fn8##@M)~Ppn/圝&T28߫ ?b(Ji7AHeP8,f'΁?$~E)5ZB?}R@E)U>6![|  aoxB>/ HaD<+T6Zv0KL8 @|HP.Hw$c .* `!ݟpz~R$FX*[H K 0t?`V Fz*K`Gtun0dFuQ>gg_| uCI05 1}H% ʀ SȤ| /?2EMJ x-KLpc3x4HK j DL hA=Ɖ:+X\ x+tjGp#ZtG͋Bm@ KPw|K@;LK@GhF5/.A ^X @D ЪdxB-jqH76tt!@vOV%v cP?C<YyD>xJիdi<KL ^\;.r?HqO)HCuL>_ni OMh#|請IR?lL_c2H}A$ Q!btk'jA,CͿwG7~cy/!&ՁgKmT:m t{_Q_]ZǵA:7.e.S3Qo?79_]_ 74f&_k+ IӁA&`aޗlW|m+"Co$Hy M6'?[hQs< `7 ށ qo-) ?򜞾 oF>}o!H'@/1j@jI>7(9?%@ 錿Cockf-2 lXqr }vӅܠtAy\FMB͇߶h` y3!e\W]`|B'WgdJG-] B@ LEL wRxw}}kTǀv5ig]nQVb?)Hܔp@TȕQ}|gUn i x e^|Ly `.7WyJ9BՅ0|WN|@/ x|('9 J9`S_x=Q\HmQ*3̣[Bx,"\$ݧd|W5dudeق,<)۬eƋMV #3V>I<_gM!۹H6KIu~ : M}V)i(>&9:QRz <잁LJW80_U%!e8]MSJ}:r@mo9Bqb@ HycO%" %] mh/rT1@A @[@ ~):ߢ}Mԩ&r?4㘱"+n 7p" C5q0\'B7~_]qshVr0ZW&KHHDl [њL;V>_J]R:z^pg X?%D@^puC@)K>"nƈ{^ BiA U7` " oWEd+ve#"rAG@m\j'%e pݐ6Δ,Y6R(< ScS/G}b>*7mec#a[9 g`Tr5G@|wb?oA J @=dg \(x#^2`0:?25ԟ~R@"A {Ex%o**ώ\ '@ohe0.(3 tG>^owzv\M_M`ԕ(j;Fv}ht#s.0A$c䯁#| @(&2%7e/#ckt3֪ 7Y4LN6: @gHm Z/qaߗ X<1шh$mS?7Cco)xB@ԝ?1G6KN1@}@ZvC`>ߝtK**] oH/^+~??i% &<^İT>#fCtUQF@P<г{=Vz:-F뢥ۢ8J'+ko /" =ޛ-4V j[p ϝm\.9P޹{-6ۈ~&Hz'Bn@zģ_@PCO`0j(76'+ _0Zzt )d(gltWFQqjI=u +TLIXIɾu`yg_/PD )-U0=Q[=s_`Lu\$ R;/0Uu`?"wxi` kO 'Jкn_P4 }k[&tE`" 65I-@8BItZhD@g@+0@=gg G@VN_h @I@ k{OG@A@' GX 94(h~n {`@I@As s`hg~ﶟOn0 ~/~4:|}믧ϰ;h~zvPVAc|9Grr`_2"y+_;'dn葙?Xaw(ˀA_"`o&&cd܄d5?w3O_.-U@#~__ѧ1pky@&@@M'tm?]AP_7`JUYF?|+R$?,==3B/s#; .}iߟ/3/Rl 63UFm*Ιe0ػ$'0>UɷA-?_fq%p>,/~uQ`Xs}Jr?cz9>pf@bC-'4u@ypa(x;[ȫJ:}C|kѴpwO'!rN ^G\##o{'b@]m[* B@ŤIEV!.wZῡh#@LkQ h`\Ibh0P}gUXJ΅`5o/>ˊ5 @Spyt?Kz8 0xnW`[`V|ϰ0Q ]JO*3{15r:$,D@;ZIVY@}rdvQRT]vC 0m@;C! a*`cX@L_+MS4| wγ2EA^vI@96k̞#X#@;vmH m`@ `;0K5ŝF`mC`_g.L&)@4Bk4 ['@{[B{@A 3 :b ?G%N ^ͲI )f*ϸmN67K '8/ |37(!NVd_ @N47@ `s Yςt\ebR?*x3cCB郍@X@PG5}qtb('nc>rYMI?mЌ #@6kĮ.hFW BH.ENM$ wvrhw :2¾+4ӿ2]_Nd_CZ6~5WHJkhu4ܴτ :(J  9)*R> p;+6 lNl,?NdV@t 3Z{^ 4  &`8d&mY?9BNև5qOc3/ ~b (X=CnZ Eܔj~B~N?9}3u)W&l3NS֗`D!?/!Ez $MˏX?;E2t  "͌}^7P'%`Y:6@=._ʧv<,CO9'5,_ I/@~E|b" זN.=scO`ڿwp$Fr7/-_j8u"Im|n^8svd2Ō+m:xJJQX`x|ӧ"p{)a p&ϝ\I/,YP*>Lj1m>Qp"7/CƤ𓽳[J#p"c%%G D+ASEURބ++酴2^N>T&FP/f&B4.ߡ)@֋ +?Х!`f 1Z N?4'CldrmG+j"?@} m`ؽ_Ѧ!`vA|5#]_ !}+p}_!9z@,Y0 J ]};+%^2LbxsߍR,P @LWCT_sE4*z6dW}Upzş^hS=ZՃ`jXavA;kTwR 3@wc9nv(m@!&*?q;+w o@ u˅*?Y4XF?\p8dr5JhxT 4,ZH,(h`B;/ln` y60Ʉ|J %l'@@A+{\ <6pːY neW/Td BY OI6J적S|xdXC̀s&A[$LBՏs0Ȫ' j!zh>H]Np3+? `,(/.AA`)!ëqT9,.y(eoCZyUZhzkѬp4BV۞DtU,+ )>pQHteXQ@䁰R!:Z80,#Գ@,^H+Gm}B@|M Mt0<,TL=w8f v=(Cb7ւ;4t@Im(P%i s'k4j>E"6 ({#h1M4lL`|KU 4`ˏ IDFm@ 鴲Jf\6/nT@WuK6 wL> mEBeG_vaBtK~|7w3wiBQ+h#"rըEGIjݺ)f^[v̐P_o 6sS~ŁSo&k1FS?,7S<#_s Sā9Ql!}9׼ *,lԿ0c?=P?s^'APjX<?%oYc`,fpP ?"@c@W ~]Ykc_t"s@ .$R'@6?x(?3jכ -? ="Goכ39ac/9@s.46R?F7;c k D)/\ϒݿ7"PMr.=E 3؜ߖ{8XHw;i讖n>G=8 }[@x#*~߃~|l+7(< Zbۓ}#ޯ*r 4d9Mh.ϨWA_p4x= p,vAO[ D&̀4A&yZ.q9m=')joj'K :AD,;O8g^@>:}?0m]`L)A$q >ܯ,}Պk0B!]ԓ{T>WMP ]Ҳf+0fˊ ~ 'v:k\m;сcۦTa AbsCb.͗%6y0[| 0[yMhIENDB`mozilla-vpn-client-2.2.0/macos/app/Images-beta.xcassets/AppIcon.appiconset/macos_appIcon_512.png000066400000000000000000000260601404202232700324400ustar00rootroot00000000000000PNG  IHDRæ$)PLTE-+5-+5-+5-+5-+5-+5-+5,*4-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5,*4-+5,*4,*3+)3-+5,*4*(1,*3,*4,*4##")%$+ $)(1!ҵKKL<<=!@@A""$NNOlll...iii112ZZZyyyhhijjk]]^ӆ,,,zz{MMNwwwOOPJJK8r3tRNS "2[F.&,)0:6(nWI=7+;o;$~)$QgR 0N/hHy?<0 ;;W-_4(.ǀ/]oJTb:U.s9ܾD:IPP_9ɪ ,PKrB.GE;()Vu EZu9 W)Q+V9쯟-,ާgؗWQ5W=0 ]ŃCK&w(;P|t1+%C t十,o&h%,Zg(%a JN?x(0HhHElSNC( BڒE*Pf)aGpVBů%,"M2P/El2џ?('mcSoݯOBrݣ?2F}`w"-eqpt cfCM k9,NYVSzb 4d`C7ބ{*OCXM+0d;%!6o8~8#m!X A?3yM?6xMA`N@ d4=LXȻV {4qD`Z_0wo&!)EElAE 5\Lb !<.݃G`ցj̷q%xYu s/-@[@K@{ EkiN [@`^;@0^*@U FD&3 XƁ!(󶱲n@ VR0{DyӬϨ;@dfR ~&[pdy0Y5*a@2>@$kk E7ױfxhB-@94$(Ε?D B觗3c<4 0-G<*[ @@n@ A +Rg.`TP]LOUe9P;Eyo@DWv)cV(6|J3X\\@ RO !@?9M4-I矏[1w="U@#̶ݟͿ^sKL$~ @«Ҧi_DB0HF@W!)Q()*Bj¤p ^P& Mk{@hAIrwXրF&aO y  hBmQq Q5{ UB$"gMߜ&am8 AW4pl2K/W)m_85W@ H725;o _y)| *hKD@: X /󇔓@0ys{0 q~k>0`@"c̢P4K p+kTx0mxBǕB$*Hw)Iv/@"ǣY A?"@Pa=]R,M;:5"lǟQ&&@bѕŝPfnTa98DtR Z "K ]$| p3A`ac 8 'j # @mR t.7 ?]yU@BS }Q"v9Q3|;%Fn8##@M)~Ppn/圝&T28߫ ?b(Ji7AHeP8,f'΁?$~E)5ZB?}R@E)U>6![|  aoxB>/ HaD<+T6Zv0KL8 @|HP.Hw$c .* `!ݟpz~R$FX*[H K 0t?`V Fz*K`Gtun0dFuQ>gg_| uCI05 1}H% ʀ SȤ| /?2EMJ x-KLpc3x4HK j DL hA=Ɖ:+X\ x+tjGp#ZtG͋Bm@ KPw|K@;LK@GhF5/.A ^X @D ЪdxB-jqH76tt!@vOV%v cP?C<YyD>xJիdi<KL ^\;.r?HqO)HCuL>_ni OMh#|請IR?lL_c2H}A$ Q!btk'jA,CͿwG7~cy/!&ՁgKmT:m t{_Q_]ZǵA:7.e.S3Qo?79_]_ 74f&_k+ IӁA&`aޗlW|m+"Co$Hy M6'?[hQs< `7 ށ qo-) ?򜞾 oF>}o!H'@/1j@jI>7(9?%@ 錿Cockf-2 lXqr }vӅܠtAy\FMB͇߶h` y3!e\W]`|B'WgdJG-] B@ LEL wRxw}}kTǀv5ig]nQVb?)Hܔp@TȕQ}|gUn i x e^|Ly `.7WyJ9BՅ0|WN|@/ x|('9 J9`S_x=Q\HmQ*3̣[Bx,"\$ݧd|W5dudeق,<)۬eƋMV #3V>I<_gM!۹H6KIu~ : M}V)i(>&9:QRz <잁LJW80_U%!e8]MSJ}:r@mo9Bqb@ HycO%" %] mh/rT1@A @[@ ~):ߢ}Mԩ&r?4㘱"+n 7p" C5q0\'B7~_]qshVr0ZW&KHHDl [њL;V>_J]R:z^pg X?%D@^puC@)K>"nƈ{^ BiA U7` " oWEd+ve#"rAG@m\j'%e pݐ6Δ,Y6R(< ScS/G}b>*7mec#a[9 g`Tr5G@|wb?oA J @=dg \(x#^2`0:?25ԟ~R@"A {Ex%o**ώ\ '@ohe0.(3 tG>^owzv\M_M`ԕ(j;Fv}ht#s.0A$c䯁#| @(&2%7e/#ckt3֪ 7Y4LN6: @gHm Z/qaߗ X<1шh$mS?7Cco)xB@ԝ?1G6KN1@}@ZvC`>ߝtK**] oH/^+~??i% &<^İT>#fCtUQF@P<г{=Vz:-F뢥ۢ8J'+ko /" =ޛ-4V j[p ϝm\.9P޹{-6ۈ~&Hz'Bn@zģ_@PCO`0j(76'+ _0Zzt )d(gltWFQqjI=u +TLIXIɾu`yg_/PD )-U0=Q[=s_`Lu\$ R;/0Uu`?"wxi` kO 'Jкn_P4 }k[&tE`" 65I-@8BItZhD@g@+0@=gg G@VN_h @I@ k{OG@A@' GX 94(h~n {`@I@As s`hg~ﶟOn0 ~/~4:|}믧ϰ;h~zvPVAc|9Grr`_2"y+_;'dn葙?Xaw(ˀA_"`o&&cd܄d5?w3O_.-U@#~__ѧ1pky@&@@M'tm?]AP_7`JUYF?|+R$?,==3B/s#; .}iߟ/3/Rl 63UFm*Ιe0ػ$'0>UɷA-?_fq%p>,/~uQ`Xs}Jr?cz9>pf@bC-'4u@ypa(x;[ȫJ:}C|kѴpwO'!rN ^G\##o{'b@]m[* B@ŤIEV!.wZῡh#@LkQ h`\Ibh0P}gUXJ΅`5o/>ˊ5 @Spyt?Kz8 0xnW`[`V|ϰ0Q ]JO*3{15r:$,D@;ZIVY@}rdvQRT]vC 0m@;C! a*`cX@L_+MS4| wγ2EA^vI@96k̞#X#@;vmH m`@ `;0K5ŝF`mC`_g.L&)@4Bk4 ['@{[B{@A 3 :b ?G%N ^ͲI )f*ϸmN67K '8/ |37(!NVd_ @N47@ `s Yςt\ebR?*x3cCB郍@X@PG5}qtb('nc>rYMI?mЌ #@6kĮ.hFW BH.ENM$ wvrhw :2¾+4ӿ2]_Nd_CZ6~5WHJkhu4ܴτ :(J  9)*R> p;+6 lNl,?NdV@t 3Z{^ 4  &`8d&mY?9BNև5qOc3/ ~b (X=CnZ Eܔj~B~N?9}3u)W&l3NS֗`D!?/!Ez $MˏX?;E2t  "͌}^7P'%`Y:6@=._ʧv<,CO9'5,_ I/@~E|b" זN.=scO`ڿwp$Fr7/-_j8u"Im|n^8svd2Ō+m:xJJQX`x|ӧ"p{)a p&ϝ\I/,YP*>Lj1m>Qp"7/CƤ𓽳[J#p"c%%G D+ASEURބ++酴2^N>T&FP/f&B4.ߡ)@֋ +?Х!`f 1Z N?4'CldrmG+j"?@} m`ؽ_Ѧ!`vA|5#]_ !}+p}_!9z@,Y0 J ]};+%^2LbxsߍR,P @LWCT_sE4*z6dW}Upzş^hS=ZՃ`jXavA;kTwR 3@wc9nv(m@!&*?q;+w o@ u˅*?Y4XF?\p8dr5JhxT 4,ZH,(h`B;/ln` y60Ʉ|J %l'@@A+{\ <6pːY neW/Td BY OI6J적S|xdXC̀s&A[$LBՏs0Ȫ' j!zh>H]Np3+? `,(/.AA`)!ëqT9,.y(eoCZyUZhzkѬp4BV۞DtU,+ )>pQHteXQ@䁰R!:Z80,#Գ@,^H+Gm}B@|M Mt0<,TL=w8f v=(Cb7ւ;4t@Im(P%i s'k4j>E"6 ({#h1M4lL`|KU 4`ˏ IDFm@ 鴲Jf\6/nT@WuK6 wL> mEBeG_vaBtK~|7w3wiBQ+h#"rըEGIjݺ)f^[v̐P_o 6sS~ŁSo&k1FS?,7S<#_s Sā9Ql!}9׼ *,lԿ0c?=P?s^'APjX<?%oYc`,fpP ?"@c@W ~]Ykc_t"s@ .$R'@6?x(?3jכ -? ="Goכ39ac/9@s.46R?F7;c k D)/\ϒݿ7"PMr.=E 3؜ߖ{8XHw;i讖n>G=8 }[@x#*~߃~|l+7(< Zbۓ}#ޯ*r 4d9Mh.ϨWA_p4x= p,vAO[ D&̀4A&yZ.q9m=')joj'K :AD,;O8g^@>:}?0m]`L)A$q >ܯ,}Պk0B!]ԓ{T>WMP ]Ҳf+0fˊ ~ 'v:k\m;сcۦTa AbsCb.͗%6y0[| 0[yMhIENDB`mozilla-vpn-client-2.2.0/macos/app/Images-beta.xcassets/AppIcon.appiconset/macos_appIcon_64.png000066400000000000000000000017521404202232700323630ustar00rootroot00000000000000PNG  IHDR@@PLTE-+5-+5-+5"!!-+5-+5nno$$&QQR{{{⦦AAC223񉉊446DDEӶ}}~Է__`PPQbtRNS հ6ͦuhM&\bDIDATXÝk{0T[EkWlÀIL9_y'!0(rn#NFd4$ ]~Lc'w{AP_foOp^q*DMaz6RVĀF=bo1p_)#s:.V>}!VmC!c%ap&KI~p}JkvkI  ,P6P:AL wo5ߏ(J8U_UT_."xNэ?LE]Bunۺ1J`>gMڼk^H]#&&p4Rg#@o r,V2&/S9VHd6M_2S(lh5u v(+/K.r^ X}QZ"qAc#[gnW_VB SCI4nEhxѲ%8o_Æol)K6h DzH1ÏinU6-j=rC63ӏ5)dIls$k3r2ͿΠTdjrpIٌNOY am'loxIo#Nx<5/z3p}.o/AyHKWW~; @h׻wr/ٽ_c顑F۩mgnތIENDB`mozilla-vpn-client-2.2.0/macos/app/Images-beta.xcassets/Contents.json000066400000000000000000000000771404202232700255630ustar00rootroot00000000000000{ "info" : { "author" : "xcode", "version" : 1 } } mozilla-vpn-client-2.2.0/macos/app/Images.xcassets/000077500000000000000000000000001404202232700221565ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/macos/app/Images.xcassets/AppIcon.appiconset/000077500000000000000000000000001404202232700256535ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/macos/app/Images.xcassets/AppIcon.appiconset/Contents.json000066400000000000000000000025031404202232700303430ustar00rootroot00000000000000{ "images" : [ { "filename" : "macos_appIcon_16.png", "idiom" : "mac", "scale" : "1x", "size" : "16x16" }, { "filename" : "macos_appIcon_32.png", "idiom" : "mac", "scale" : "2x", "size" : "16x16" }, { "filename" : "macos_appIcon_32-1.png", "idiom" : "mac", "scale" : "1x", "size" : "32x32" }, { "filename" : "macos_appIcon_64.png", "idiom" : "mac", "scale" : "2x", "size" : "32x32" }, { "filename" : "macos_appIcon_128.png", "idiom" : "mac", "scale" : "1x", "size" : "128x128" }, { "filename" : "macos_appIcon_256.png", "idiom" : "mac", "scale" : "2x", "size" : "128x128" }, { "filename" : "macos_appIcon_256-1.png", "idiom" : "mac", "scale" : "1x", "size" : "256x256" }, { "filename" : "macos_appIcon_512.png", "idiom" : "mac", "scale" : "2x", "size" : "256x256" }, { "filename" : "macos_appIcon_512-1.png", "idiom" : "mac", "scale" : "1x", "size" : "512x512" }, { "filename" : "macos_appIcon_1024.png", "idiom" : "mac", "scale" : "2x", "size" : "512x512" } ], "info" : { "author" : "xcode", "version" : 1 } } mozilla-vpn-client-2.2.0/macos/app/Images.xcassets/AppIcon.appiconset/macos_appIcon_1024.png000066400000000000000000000646531404202232700316200ustar00rootroot00000000000000PNG  IHDRH۱&PLTE-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5,*4-+5-+5-+5-+5-+5-+5-+5,*4-+5-+5-+5,*4-+5,*4,*4,*4-+5&$, $ $#! &᧧NNOhhi""#;;< 001KKL??@--.wwxzzzJJJZZ[jjk]]]++,lllYYYllmҤV6tRNS $(0-3>9R]Hh"6+;ADNKYUd`Flv􇴩k}0gIDATx읉vPEmʠRp*+ P`3ߔ !/{CϾ޼/B $KU%@ P+^ 285{_Nk)ीNB}I~)m#@<NBR~.o-[K% i9$KʑxY R%} !. Ha%:I?k>8:!"U+@E/ܻٗďFGZn,@{@, ~ d}|{0[MOcu0X 6 0\]]z&wQTChQ} k'Jr fɟF;y pa vAg")d쫙 6 z8&XD}k\<\o?C pl7[o,* H\,8+X6O?2 u@O.I1>l;#?Mѿj&8]* \eۛ臚V5PgNjͶkfh$/8 (,H?xC0T(Ԁ+c dܗxH/'R}?%w hR]W?f`P~~2nܫ'CC̦zPw ho>D8Ex~ l)?@oIR;ΈC3ٮ"|l6?!Xk{T2%KOm O7:U hB {o%@ƿA-c>+BXNz,<0,+Az7/cn>=#%/%0 w ) )^@?;c=9t (?=`G 3o?P,5?!_^j} G~Fz (?J]$3S움i`_ 3!%U$_ .ǧ^T ׀}F\lq/T [`ݾdD͂ NԔ뿹N5&(\;#ó#3_5@? zn9;Ý4( DŽ&4@ ښP# V.w/6df~ { DBǀRPQoX[>.2JAhTn\>6ZVA!ϲj|r`T@(6b w%HmGϺq䷳(LK?F;@]!?FV`??B82@ JС?&h$MP?&~vԏ n :@]?|8ޮSsY~<@ 7rRP?ԏ(~L1?Gx@S,I9@ԏ4:@2I G[ɇ;8]@Q?'GjsAx@#Gz+~O x@\`y)y_@ f0*|PsbmO ФyDat}Sh0)= C<l_ mM,G3y)y@/h,i0,Np bF?\RBGBPAC(45h, wXx4P_"D`Y8$j/@ٳ7gKVr?8 : < 0ttUb@me`@m7`,m 40:Hocz[n:H oZ$7K@ '6 'd>r8$lt0{x$ A;dinA{]`!6`^9@ (~n `܌pBε2 Z& n%xd>|p BPkv~/?V,0?0V]:ЁjT-GB {WӅ@ `܉&54S_Կ_V` `'Z@Ȉ,*`N70 FQ}xI`F!gjMl(2(~ \ijX%00VȿY'KM, gm(qeT*Y4PjPz> )"9]2lNOPk~t+`yXD :6me'&&p5C6mp$V'BD+@硗/Qb,V8NS!`Suw"\Kw(UoL,%Gp@ !CdI$uVF 7 ,(   v4ż ,Sľf'(Հ&b5ωx:aЊX ` (DV#.Y U:KkSQq,LRQRдS3 ` pi9NRs`XHͻ V@#"o'(O5ǮY fFS1s`@ '?vFFxA tߟMSs3О4h0րyZ߻!A K 1*@d sf>co_c $ c2tkCXR/I"kH^5O+;`G51@ wi0J` 1c׻[oˊIHIlUvH'9s_ׂp "*M@b>X5W |땽NJLrTF|VKLIOOSWE]JzqvU%𛀥cj] 1]4/G Rw% h 9Ob 犥 `t)/VֲF;p @`k9 ic =7f&R ?Pb?2ECSzгPfSO:y @.>@x/[nd/^gtgD-.`=F @]4(7vJ] @UA` ?489@ -мLP'Fe2rH X<e P'-e302`$ e)T QY?i3)@&wЗ*zY@3N,,Q74E t 28> 8XA 0<\ ud _~ `ƒ);a #H \+co&WȲ'.?$ىo!4^[( 9Ij`S `|;07TBaP} B>L(:$'8 @AS<%`zZϲb(Pz$LFPtt0} /x:\ %PZkL>~. (2hJثp'<oENQ  ,Oc@Mx -$IF 0R\~-64"M xvJ Λ"$Am$~z([Q06v ;@m#pe$?&W1-[o&y <@c\f)ɚ5Vx4L D_M@ Gn`Bx@G 4M<  _ac mP1 ᚌɿ)3@h)ɿg!Pi\Ɣg"nd1X.s\;qkk^ D_ ` n >H=RsMwL1>`iAlo[DD `0l"7?:+@{Йh[ p͔<0 ckTdh,oC;?nOd}70&ϹXZ:8dvLF0:nh)Nj%0YWP'S@ J3f|"%#xb,|Bc@Ӓ+W:kx$Ɣž :׋q R Y:NHs*`ik/l ) Q:'t(`>e @0GiYZ8kyg'{7tK#\] OnҚg}yX`q uT)|s pO @&{^Zy5/Jb- SZTh , <g#gT`K& єq ` zG2>v p:#hML(wJ7`bb@I<ěm 1@'D%Ϛ fՙ8b-"w$ 82@K:&b;++"Dq@ Н:"ٮ Cbo NS>&&E=@#-|c]I7c  ` p\QT*쿚mk|\@?8k["`' YƓ;h\" `<xsw ``_Ti?P@10]_+aоDU 008{߻?ڎG/P"g2a8=0D X @o4A(hj@9=G|"whT8W"[H-@ C4 @pQ`_2 T֕>>b$/(tzQE좊vo{# _P .שaQ{`EHN;;@'k)B)c)׌Q 8P 8[_=`mEr S W9{ p{w]ȁ7 -t(@x@ VHnaSOœP: V)T;< Vv*}k +>nΧ~5X_[,?SqK:j@ Y&H$/6%'ۆ2?Z@j/phŘ>bSM/}/TIb0??= P4D D5-[>SC@j0S$gpuM_*uUo,B@#d jUKߓx&pH@0g$x?;M\B GK?y # ?H½ۯ>`?a9K-6`t t{۷oπ># w0Zu6?X&T>|a7D]0 58KeLb-A=o4zx6Cj2{wM/8aq&N0Հ U *xhOŴOWc @ CXgtj U@ _fl/whm pn>c_#!@`W#pHob P-@AM?On4okPHv([s@tl ~Y+CB.R%@&?م @߿=_;] *hl35}lۨ1] ';Gs ך0jV|7ey%߷4M&4}n;-ٱw.{ `X?ώW ׇ[Ya_j&L?{nB:lC,sg.LmuALGQ@F/?/TqWS6y??ga`&)te7s[s]сm "RWG]]oBhhMXf2t|o'zh_KajW>[[^ x58a9tEW:m(pv/-\7f# ?0̿@4a @h}t} ^+]tG7@*AgZG~w@'s  RDw(_ @ . d܇78 Q!2xC*G,nr7ԲCk2^ _NJ7$K_翟:OpXH~R@X'OP<vp6dqP℣ !M #@TuGh-D,*, [ *;Hxw H@. j HOm` \  V4;"ofG 3},( i-;~l8]'zv% I{% SE$gu% QDD Wm5W>Žhh9,( BIX /7|8" `@uPyC X` 8~·LVYq< &AE9:}}ߝ8&&)Y8/iz&@_˜~UjLP8H7ergvIK_6z^?CngQApCYgrvG?.B3!zkY+r]ˀ >@]ξ @06ytȭB) 7)^hWo17d2? "{3R@39%~ " A<)4/_p3 tX'rFp>j `3^o9D5@]pY)87sJa j;$:n E un=\pX,wO*#C^rU)'/ [.iPOhP@N8XS8EOI ԝ 0z oUtS78 paF 0\|zZPuׂԻ֊7=3N[Vp. /@(+&J7WZ̑SV,7 p Ԃ}ׇE\>Kh1T ;Nr>vu:x)9R(lϭq6P3nbkCt֕.c}{DQ`{u<4_nIS2@9-XF+`s^L )ƌj9jI 5@'޽ZK V nz7$vI 0 !5(v|ǿ➄ 70^=My`6c#Ϊ>$[bx.0"`6Lx;  @ @#@+0* |!=X+k: 7:|b@-4,AQi ,95~ M 0YQ+Xi @s?k+R@{!N`(@/Y,.8NL P8HH R @#6mFu@F@ (+ S{)V0Pqɬ0b&Z$NX@F:3) S&TιK@!XS "9@P˶ ,90Q4@P@P( CP @HB;fF\  0 x.0&:745J@4aN0M W)()i  .͛A&,`h*ԁ s @}UMeJ 2fU3 u&=`1q۫&PAu7lp/N05DP=`6,#@%X6wT} :n̋lt`w;;@3s|9BP|R  Q}(Hv`Lh np+V 2I `.0v&@w"PGKp]OXdwOxh 0ePe_EY]xȳӌfO jG U)^%W@@Dm)/ clَxݧ?4 (`DIz"&3[O[iE\-h6?(EFr&x~OU#,/w B$.`; \nt.M|E eW@R"%z._H0/ RDtxJb {jbUe >Un?50y;@K$4ȽA@5 b͛lHF?\pi@L,\?E P9Kk@Z'@hQ(?@ ?~ogk:|V@ >0 7tf~`8pֻh88xhs@ۗ; Dsx" 0S-0f4⏏eM"A݅㿡:)]j o`C@@gU0@WBD?d1P3;% |0Axyh -H2c-̱i\܂> @'GB Gw+0Cu V,wvI0(riq'\.^/obooO/`&G_; ʛu3'0zo~@Orob 4FC i^(ps̀w+@*ڋ=zS\/*oܚ@M"<.a=A*@^!MJ| W ~ԯ,DA ~\3@׀w` C?p6c3(0%@ǂ '/Y!p'Vyz.E{LYe\S``(YN`Ź?Q (0[6`p2w<纬2@_N'ҋ&>k`Aϓh,G51=. 0:>MQ$DfCm<6/ ;!eh/' P`wA @ S`s´:0XPwO@*B^ >G,79pSO{/Rtmh-@V '10+>y@𗃀ʂqncZD 'cwWxf$>p~.E ]7VD=`q{@DA<(`G ixL8nCH l] aЖ {6,#@n-chnZx yn[@ '8hM5v >(1paʲ'70@jNgcɿ= 㻥ړ  v~T칮^3H*v{` @ x<-0l)u8<$x1\(E`w-ڔkp[8 3Zz^Xo5$@@7X}?)U%.w@}+ȔREeյ}XŁ(*6pPK_@ș? A>࿒i @M;=T7$u<@ Po\{QZߔ/AU2p Зw9[ ԆP.@>]&oXj80W#XP!FSh~<:.N\z0 z[j(=_3]eSf9L&gϟxUz_)XÀwMɠ%|z`ٟl=f_{Ϝo"W@?@pȟ4į~-/!kϥ'lEGl&xk:2 ?9Tp'IqQE 6`^9Q)oFBuWt151q?U *'$|:yG@?(`aWTe<dX_}t:mf&as\(8ѼɞA8N ?LQ߽@=\V})79WS& #2p= -@E#_rߧi'Jwmmk~?X$s_hD& b W'"?TXx/K`3$)937Źx}!࿯J@N*@ 36l%=_) MF%  Z*s`6-ΛV~G7ڀ p ؼio?Õ(Fq;B)=-8",d&kC"a6pq2qK7 *(\M}W?ߘT/" (⿱p>,Y l__AyQw˳È@uI |ϯ+x~)KOK>GP0LÉ  @l u1>pCS?'v{ @@=#1Y\(!Nz8ymR*@].-W _;._cu @O pm1@KGg/dܴ^_ ?.w3w]kv[r_?.k!< p^UY|7<4zF Kk RnWCvL8p7hVG(rRe!4ߞsG)Bqhu iE\  _vYЍaӖB2Fjs!/f> QS4-h7f?@N{{碤6 Cѿ4PJضi' cvAIj$|3{Zvb3@ L6`#tM @|3˿ ##?  $0a Ēae`7z?r$`,#@0t3HdM&Af<& Ё C'_h-8d-3@?WSLep`V? 4(6axQodb%ꯀ)XM-r2],8忔  \(MAM @2(nU_E0Q/̋d ěLE{`7.;<.w<,6Sw#I@(K @ܹg'UlRfx&7LW!/(.).D"& oL?g9`\:&<p0 !>Po]232vIL_أvS/ |p)]_z6\e)H0 !c+7. {_D< @ pYHV*0e HiFq꣱(̥`w 0(7yZ7OC%`+PXYݎ?'d9?Or.>0 @ ]{_<$4RFs{eON9i P)Vqnxdd@_@ι@`_kGz"`X`)uOki ;gV.@.& @7۷=0Zػ'r8 <0h7@7YXl' x& +hܾ5~ 0w1Y))W.(4:|6hPo@|}BsT poG2*!0O?p^^1&11?OB $pM sP ̘ Df[}t5)#@+`L& Yxr \@T#3[@Eq#@؀hPno|=6 nqp(|s6@xR @ 𗽻n0|WDi֪*!a6^Ai`TS@"L \%aeNINw> y?~  ;$94`_Tw0j|^{Qm/>Uo,/|=@  3i |%HPN&i!:`Kȁ)@w>0% $AC%@"!eHY6xJ$)whPl걚!L+}1m ob=r @rM@'} ;ne@rM0贘 0@U ^,tkHTBL 'HVp.X"?$+N#&@ vt@r 4 -iI/ u2#BتX4ι+-\/ \r`oM ^$-O+&@rW* q~h:@~W~yH34`  R?q`A2# 2rPlϿ ?> hdHd@2Jf _!@>ưZp '7amf``cz(n̿ dFS{Ɣ g%?Hs#)hZ=@aə<8m`' }/2+a i%Ӊ}Uk5_qyIluDGy 7xQ̿ "lT@6D2&R@4T,~ `?jlNJ 5S Ȯ'e %l&늅^d iɰ>~9#<Ǫ*o/ah %C ?dOÝRd("x +xdlOGlG&/lʑ@#(">3~DՋ@@XPKAL/9!vAH | #jMb~4c;! }|2 W !2vJ:xsliԾUL2 "v1{x9 `TS?>* ]ӰNr Wp~ hC/?{+@jTv7;ws.>q>@GDyܧPѲNbY+@EaYH =u3`5`Ws' >( i84xG 7 hH B?** Xd:hl[xsYSJrh@H'Z hj fJz D 0fJcV(kkd@0p}V@? BWI!-H-{C7 zH$@0`Ss#ܳ B`8njBPdTf7?2{xH/4 `Ks0?6} Z(Փd@ŎG9:x7op"=@PADk`?ݿ{*)ؼN BzN͟=x# `,pc_`.'}fLJ, jt8  ](:p'Gz`yt?(v`/ýz>f^*m&N;q^\ 9^|>P+hXW/ܻs3/Μ]X<6 Fo w+Gz`9vcB @>k4s{ 8wVND 0`Gdw { \է /q@aDp{W.p.+K+wy$|* @e>GDuaisk  3.[G.PVq ıGmb4WWUw 71 @ #l5b< # P^o]u l_xeFسF'`py1;6*TyO{^D ^5󶟂fh^1@˫o~## [)3ןk=)`f[% Ov'z|%cA) S @c!?oi'>]S@N'z4X8cd|& YҶIJ;ަ $(7A@Cˎ DQ*%R+7Y@|}t̜ þ 75]@o˂MJ/X!Njn 1lrf//CH6}]c1k94l08+b5dGa`~H8<8iԫA/fY{C-Sg@#QE_`/^S@7hqEj)^ `y C4:@ώP g߭`1Vgx/(Prnp/F߅G 03ɥӹ艛?tB- @ҶWտa O ("/gSMHFH.~$8/ErxN':@^`G51FH0 F_"矞[2 `t&[EOZxीTCIk"Z@=9a`1_^OQiP&F[(Va|_?I(by9[=_Ҁi?ف$Q@?N8,yxտX?Qg0~$\gvoBOUok"p@R+NNNy~<!&Z@M1/Ӊݤ~_)Td/s_ ֝j919M$ܖį,Yн %"M*&l6+Hf?0@A/@A+z~*; MHR eV#` DUE$~O;Л@R (znZA8mn@ݼpsC@|d.I\A @ d# s/!`R`_4"F?R{@~/.uA V#;<0"PI0W>?|w H?ts ?I.P ~a+:"Q&?pg( C7^\9 aE]= Vv1:"Izbc@'y܎.DZ"Y1=>@uE>e"#@j@w"aO c@V@uE 0>[ƀ\(b1 DPb 0Flf uEoF@(<_.]16(.^I`bO 0bȶP 08%nm  JV(:.l<Ӵd]xu@:`w@o.Qu@W.@ . Jz M\L.T2z ((`׀SX2t|` :@9 xt 0X8Dy-A^_EjYm^v 0 x/N/ QLtNۀcX: `h.6A-l\ZT|F@ o@F4Xl3 @R@1& 2!: 0j>'5$ -sF A},ւ V[V2t78͝\I]t#` Pokp >r Vt :O]|V `x|Q~j'oP0 *9@ $a m]?k !n_mdH3rG_<H`@/G-4kBֹ5 !`0@H*G߽q/~B>!;ӿ0 &{TQBHB/%?,MUI̓ i9}`Ux6/?7jh@ ϖI`{--/[߀`U@@IN~oLU-&_?,4[=U@%+` U{84OF[<`{R@g-Fsy_?9 x Xd_ϧ3 x,4n@9?f{-]ԁ5c7KoOW8 f-@/&ՖwA8={*thǁnp1:*@qu*g?/. Ƴ=ԜeǣV_-  R_d/ٿ+" &`1w[vv PC3Y QAV%]^a#Sz?3ß[mB0xvo:b ◭_Gfʿ1`h $Ş=?Ϫ?7)R } jHxB@*h_?b?FZmS;  qUGDŽE\/b>sßRpfpd4ޭDvحOڗcG!Cx :pr|`Z@ H"a^ovн0!}hfc)ߋyț0q0'r-‡M>bS??{@ @@; )c DPh?ZbpY@|ͭ0r )D"||Hk?L0< kު #}(spCH s‡} ~?F??t `&\6 >@@nz'W;17[G?``r\R>P0t7{gO? `.  24 +_覵o #.K>^{(҇)'cJL\@7G6x^]zIb l,1$[w>P *ajGğK:nV6wğk@jN'⿏|H{?BVXҎ_G@* L?q0IENDB`mozilla-vpn-client-2.2.0/macos/app/Images.xcassets/AppIcon.appiconset/macos_appIcon_128.png000066400000000000000000000147451404202232700315410ustar00rootroot00000000000000PNG  IHDR>a pHYs IDATx]yE]}wՌ60sD 4;;18;;npy1"":p@lDnL,4rWޫ#*^3hߗ_~Y ;C{*v4J#NcD'$%n0;TWtd3D`bWtWQf3Ss:T5=" EH)X,~,oPk럫i|٫/Qb/5xɸXCUN>YZ|g Ӄ;RGYfҼy,ghyffK>+ ^8r̃DֶYE_ R/ez ^sz4uTaEw^&\1O9LoAD)0zȩj׈_H "m̙">@omذ\Xh?AX:AwϵgH ~˷0?s ڸq~ꊢ_\&ȩz0kOeYo%@ᅡ[%@\w[EHv5?,x瞵av١tI{F(gY5 h vԶM9zneKyr&S9@iHdLxYfرcVbR(5 ? |G;E0,y2lvEwsT={67.&L@'O4I~\Y>涨A|w{ NŋБ#GϏڵ^|%zztDJYrZi`AvyϙRD?x< l")ִ{xvxvY'1cƸ:&&>kb1k.A15/{ ,=a3AP`#QȰq4D=Se?B^hieeJ +l[7H{gΜ,_]/e-]=xfWΥkhc~ǚ%#G>g$f祿ޘ1M4yd?04G0 ؅Sv\(_V9A0A%Kq"WP,V?6mbzzzA53GF*sj_ygEN[By\ ZvemXnL8|B?{, , ,D6"|1Zbݻβ2@vƎ`CWH/Bv}8IJϘ1VoIS }Ѣ%撷s$څEZf@OfVW(c&qLcXO9bRGOmxb[mZqlDј{5Q.\G)u* t J=ňjξy^yJ7oʕ:˒%NrJP(f?Z`b~a+W?Moҥrv_;D_G@s_`d[06Dm_W:[Mzb\C^jV.yQR`:^ ~l4v(cfo@grDm"u.3X^9C*2 ({+ 3>A `Coޫq `p`8r0b1 @vfUaAOJI&\"EaM{gSƁ~<&N.|i,BtgyZxF̅G%%  9t<Ν+$&(X?V [x:p\DcŊǛE soHz,˗n 6pvhğ1Btb m@[ 6Щh0By~ٿ?m-u2ßs(@|s4j?jȲl6pOUz8#HW@c?g[:XGȑ#/Ǐg'#ybI@'v!Z.^vuvvX$>`i {daF8ժU_lsX Ja(ms2};vL89H."B/_NL̹CF be0i\ 2dޮ]rd"<]l_H<0,a/388%.(gرchqll_Y8)WE5_>}-Z(/70$[_@---FZ[8#1/ x8b#KBjK̹ak9\DC,cgRAO ]{1Aco>["~"aݐXGĬm휏GdW{ᩧMMchhs!.\l "jGzLŸdUϏz_mj[ r2*o;3A˂@BD?a&5Zd)p+/CȑhԨFvw_ -[yَun3O`(7-x"Lr~JHd}E שS詧+nW/B`:moϱ1fI< !aA!W/a:=ĸoH.x=V0/OC!/@tW`.mrjzܻ^Zvv QO I|`Da},t݅<p&< a~*eҲY˓ s @8w:'k_g K &gδ^ٷ{DD ܞ#n* 0?;9?8\ 3EZ TI#<@vps2Jq?QxQ?5$pQ" NpSѢ"lBa1s󌁏:%~VDREWs.~~B9R^EBF~ (#Dtwy&WO9ħx,%`'"E"*$)0sez^(iC(&>'i45gbrR`eE|ŗTT?٣L3o!>PYQ}Jg!NQ&$X!vڵ,/jHq 7# +JR2Y%`vKwvkFӿxaA݆\D/CBJrrNT&OF_ckь;ˋ[U՛A H*#̀\=7!)`QQl "|86ʊWnjֶ9Ĕ؁ʛ9S̊`=G02f<-n+уp;Q h" Rʈ>$~^o0…= cu ; Of9"}Sè=JR*X*7Cx'шʚ&O6@pFgMusDJw$-RތC 720~rWƦ\*Uyu`*˖3\~&a(uM_W ruޚϪj(ʗ5eyJKD/?GDznsl|jit&K'^F6 GDcuxWP$Uufr[vH-D9ن@cWUY^'Q`ځv\{r038H ҁx,~t#O>N (a k3陪6ޠeHUv47 iRSR0?f3r썏J¸x["/u%8t{N7$K*`|B=kUUN)J\/gjD6wuW!itu0ER`w&Gfά)@h01g=nf%[- 6ܢIENDB`mozilla-vpn-client-2.2.0/macos/app/Images.xcassets/AppIcon.appiconset/macos_appIcon_16.png000066400000000000000000000004321404202232700314410ustar00rootroot00000000000000PNG  IHDR(-SHPLTE%%&DDEoopaab445&&(~~~~~556n7tRNS@1UzIDAT] Ch7'J/OW^bHLU$yDZ<~UVH5@tV&ׁ~H(5Ŷ{XZ6=^#E`bIENDB`mozilla-vpn-client-2.2.0/macos/app/Images.xcassets/AppIcon.appiconset/macos_appIcon_256-1.png000066400000000000000000000355221404202232700316750ustar00rootroot00000000000000PNG  IHDR\rf pHYsRnQ IDATx}yՙWUՇ:{&ctt΄A˜؈],#6ibc$b5fZ0` /̮ԍ׌c$N!2sW2+*2_f/"խ/}{3z_Hj-PN}+O%q$N~3-5'M'AxO+y{#CVP ;.Bād{;(dmGb;, CD/G ѱW\<<"@VUKQ/H2Jhsgs(mkmzlG2 i(@녋ϩT)+_%茻2b+~ `8P2- d'otХ[doEZF IB>/SPeG{/J֏ A @AB8@H>JeEu2d|^zTl`bAB!~ ]RMUEէ D. uvNxTl=k%5 r؜{v_we&~AnX(9R4 B0bL "pyj];*r;34/rB:;'<_*B0J; yī=9>O4, %Eg^ZPí-xU࣏rE\,9G4i`܏E93@D.g2-pԘp0}cl>\oE :66xfG U\!!ooͲ\>~ډD ;}loG}7#!_relu웊ˆ@ p2zJdL C+s˕ K$W_5kf1gHӄ \+-]W| D1ob:Zeg*pFR.-MDpE#ߤ?>;Y4Jcc7IRB"m < 1ὸw¹kΥl VAӡTjzL v⫯zO?EQ\wb|M.*϶wږ\ijS0\W4*˕B_Ab_d4T-B/R@h^:|ipeOU*%fP`Xp*;qKos8D|M:@78tp͒W+@9 >̣R)fu`zHFZ!H/TUZQ[Z1 hdX&f. ,WƦ"W J.FٵFBT'k!(4ɴ`\):*HRPH2f#a/j҅rel)e6s2'<|AUUXl'˂TY} ,z?%aG$ & AZretq26ˊ@آ59{Ӓ $ bƕ5FVZ9'+щ=\W$掸F}O#Z=4. ̳i@19̇ğt8@Ulʔ)0}zL>L SNqى'~%+cP/j@9 Y"@Twiνɓ's= 1yfB ˊEF}&rC % (xG|EQ{_ -Z}G !/_iӦis EU@DѱW~S1r † r3bƌ_VǘF!rr k )"{FG/Mw\<<-nzO0[_ɨ3 ^{H}?x S[ 6N xKl(WʟYjt x᧑YOF}E+)Aggs0:1o ?Y\ (~)*mYhͨN;E1#шA&:TU@$:L!"`_EEmщn+QDY\cpULx}^8BD`޼uSU7]VTuA~)%}'DiQH. '| FޗX÷9DR.g'?8#??Q?,k{}֝3f̀{]Y B'K X{#D4;Rc۱vZͱiBvjb $?YeGRVK%+ 4Ta 3X G|7У?f'Noe+ϕUVmahh$I| ߇߀|}fBXQs֦'@92/ܹyfdѓx/coVGe\]~5P\0aɞ=/dP]^De6A)@դ#zqĿ}}} IK Iy$8#9g*<:x">@si^i[ĩ0>\eXG%KBooEp~iみ/$رc,[v+sɓ ~O8yDE~o/Vs! X'@^}? WGx99ǃ.f5ŋy#`E@4"?bK~TLxW@ =ȟ5W-^yL#L Y{siĦMW¦K2Q)' Y?M @dʀmme0,3""Lb2OmjO= " wPs= sc*QYf۰J<Ys ÇYtU)`<4"T wlɻ}|YEG!Ѵb4 ? `'OXÌd%8pby{V@D~gR2o#E/:!B..~\"o|&n 8Lj@DӟG zX ~͈>z'i"3gTKkv˖/Q;#dQ|lܹswT{ӁKtA:pD7iIޱpBfq @$[@4U?~YpY X3XʿuQY]"Pe,g 8?99X.a*/̼[(?v,Xm2|"5KX"*GHΜ* ;:IS[4(3{sLP^Oӧ-ɯwܞj#X_' E_tjeG3"%X>.TDU;e~#c׭[G(LZRdkxGg hz6-[VLL$ǹ S~7蟑10)L ,(S65z@___u,Q;/m٥"Pd nmًR_J!aDɴK웉+rկykVpٿt2r$?Ax 4Ku3Oc.Dɓ'kX wܴqGW[%/*71~ZoAEr݌eœ9sKX!q|nA|]U7F|f)LL ^ɭ ===Z& ;a_&Ϗy 4=vd& .\@# TaQQ>k\+(!7纜 l?EEQ⽵]{+<7ۀa pc &A>waݺ36Go3)Lk '<)EmvYzq A2-oڎ>sŤ5;x@)c'Уpyi܀/#70͛\kӦp*uJ&w"׬y@#~@`}~M W绝ڥ6>?n`]s޼)@1:Q D\1fiScYE LVMD1QG ӌr꣔9b.Z/_aLߍ>L100Xk>@NI`-߄e"Ҡi ɓ}T"z36@g=p`_G}0F={XKeRaʔ C>c 6qeڋ'Ễ>Xp?5@B@ oz5xJ~,eM( K|V'-^Yô' @@p4;kG"k0}DHGYm7m4??n-̙ZdN$?=22nߙ[ЂuCdi_7?LN\u@O sji&S? X|`$8k5wRݳOrWkF(n 4ɉp=QO{l7!@X!I!;yn'&Mx O?U!Dm{<ǑN?N;?>Gyg!-* g,c](x ؃2K~]gNoǪU4&)$b @^\3ڊE o"fD@tڵڊ>>`%Fc;wo.9s&L8&NB;/hp';eJbhco&L00i ~'sfHϫrK΄+pY#JN7< ̚U--/!@S;t-sfz1i…[S80̚Nz`5˦ @Ob_dΊ-q9JY*Y,׉YdYBMeY6͒xu~@/Y~ I;g4ώiߓ猖޲ejmԩSj1a| B$qs氝#WZCNwӞמdxaX(+Zu"s~g0&(aB~@FvMoo&/|bŝ~z'T8,""2b#e0h+V,gG\WvK@'H޵hӬ]gܭ+AbϛQ^zYTu6wDgmNfg$"5}![z1ffo̾'`>(( #h<ˠ^1=󚨬[Y_3X⊙kdʇmS1@x' QXɯN A*PiL+ 0wD$ ZĖS3ay0k^ &0!1j ^Ѕ p?n=0bMk6aiԠhUjlY?8q,N_ >M})M\ Л M#8$Cɘfqk=Usg]v%Q[>"4|ziKv\Hp~h~^,` jA`4 ^ ezz2` :OALЗzci:|!t od+Za˴Sѥ#X>u I Z'&w.fl %Eygb- tV}`2/k֤;gavRTa%9sf3M9G-,.9{qY)|tλ 6TgN -t'#?,m2"y3^lb[zl⥰kgg1VcSU-7 <^D"˗(V! p0Dc70 GD6yM6_>ux~{֕={h/֭O QG \ʳ(Rv?hթmU^p, lq^޽~E[6E 1?ƍ m8G Xs߈v{+t3]6b{vs׼ˏ˯G?}zO#ph@o|a^<#?YjD)аW2242֓_{#/'%]h!߿Oo[g?;1JVa̙^j<j5+U;!-`P_zOh'xժU\ܹ mۦm}5{_ux=?DBnowyfƍp!>C3ÎڰHP03'OUbP^|apNv';ӏױ`-Z4"E$j[M;0>;) |o> caoXmۢKJYT㥚?huԩ~KmQ;ڻ'wt"ˇ~VIВڬ:ĿVL>CȆ,3D''bn-(gX @F ˉfV}@{wyhxcIgB`yQL |3&?"ė> 7WK܈@eN:bd>BAX/7&#d#ZWYvpY ϓZ=_ \NF#_&ӂPnuג?EGNZxoFο` 3F_s":@FM`O#BNxFql{"{ѥ׬y%ђDڬCG Ӏ."s8~|GٴޞX| LV0| k(a?3'`;`{=Bx[(?؃$AvEo;80%(Ƚ+Y?z罻7-a, =k>͓9@`_ .@,c'7QObvuuq٧TܩL#5t|5]ǎ ĥ+N5pOu8 읂r.K?!ͦ`[kYҒmd+,-h5È ?3G'O +~{O~{Y_O=.Us7.^au|}ӦZjEUg#$lc->PT{̊5~zJL xGZ#V@gYuxP_0(?@$+Y86,0sY&LJDLpx]ݳ|Qo~E!s:piصk7b/Q>,ɹX\ wX+ O<6 b1zj*8V@m[lo U 0*OWx;qj "s͗y‹sX2j 0A'|ۋ^b ()LwW]{SLfkњ$| ??NXlہŜ ԶY[U` 陮mU`j F5k0k#Y"GbSdocm0Ų>Aڙ{.fK@DGC*dWAxWg*&~£XE]lB);a˖',+e kwvv { -$ϲJ/4Ke`PobQ9VW"5:> 8(mb*n<8eT!qj zVm/N(^ m-}vxYt.X{СC Et9y, :,bbDi}$U 1 Xnˎ o۶n5gk ]L8-/>/޽{ Qzjz`O[z$t9.>"_Ho$ Zi `bʃ9|A~aT@jk~(bE1JQ/SNKTc7ndT/É5/e%~D(Kœ8صkNEbV"7yO DVrюhE n!+`=>4T,^z{{})C|hr wAj-\>w6.Ct"wwk9t<)3 {ӌ IS޷grr*?0OrJP@KCp뭷&F vgpmysG ehOBDb%9E`/C^1/9hO}o.#JwLY} v,;$zH_{LIl䏎 !?X,}cv?-CTj=Cu x@\@m?H,Р978H%|#pÍ_267» "?!'' yc|$Bg̏N@@#ݿ`>8OKs> ?=2F5W~K:4`>J@Q>/(]9Bp~Rx肉>'kRj#FQ"ӿ&;UP*,W^E+5Z9{Do%H¨Ro0߬!eN@pIBw\ "- nrݝWT."i sTd[*1X@7}?-}ur /q&I!飽4:;' &FՀh=[,_x!) /y!=p'(J Rsh%ˁ  )_~;S?%>W{/p1Sy}|joN "ϊZ""f(s-D?;ƍ9o$i'T 'E Vk@3[e>R|, 4i9e8gxf WpR&kZnb)>A[[ G1wCZu!)/y1 5(sč\lPK/1Gwd1;]_ڬ b#ew4 4шOt] 5h+FX -lmmYq;[Jm^n@] \֖Gʟ{`52@,4#X0g6ZXc ww]D.=ˋ9dfďڕt]W7X'QFb@$ZJmpB#|JW>^*`5ר?'xJ5+ܼ" "4os֖qoS%?"fDd|R)ҀLX#k;ƍN!z?+| (PkKrelH+s9^wAP8V2#:~y~y?@5:JyP˙X+ X | 8OY?T5O H 8GۙF~@uDT.)zpf~!IqQsl !KŖsTuyl*R }>p o?f(CXgrPX*+G?Yܐ8%ȼ i}Տu2CXԱ$0kY߯T_Ϧa<_cF0cxo>/sΞۏ@@N?I*Y.~IUիY3A&AFrжv]q\{ׯQ##ϒןD҅q]+h /8ΐ!0F&z* eN~9ts`6rel˕C "_*i R1?olfN~m;蹎#o!,#LЅʕřdh&3FOpqip!WC#iA+F8SnaElY|=eX u ;/zdn;!rmZ@d8C *. +Gעg a/}_/Hŏ _ƈ/Ga;!>VaH _+ΐ+EU|D_-Zzǵwcd2KAFJ'=G@MutҤrre trPqZ~Uٞ ,> wI TPRbAK}c>b1Bn$6![:(QND䐌-}} d:҉d jc؉O ) !J"DD}?#~0 BIplGg“vD!>䰉,Rp2g`ZTJ Tӟ4EAw2}}z`^dۑ P@?X IDwqG HIENDB`mozilla-vpn-client-2.2.0/macos/app/Images.xcassets/AppIcon.appiconset/macos_appIcon_256.png000066400000000000000000000355221404202232700315370ustar00rootroot00000000000000PNG  IHDR\rf pHYsRnQ IDATx}yՙWUՇ:{&ctt΄A˜؈],#6ibc$b5fZ0` /̮ԍ׌c$N!2sW2+*2_f/"խ/}{3z_Hj-PN}+O%q$N~3-5'M'AxO+y{#CVP ;.Bād{;(dmGb;, CD/G ѱW\<<"@VUKQ/H2Jhsgs(mkmzlG2 i(@녋ϩT)+_%茻2b+~ `8P2- d'otХ[doEZF IB>/SPeG{/J֏ A @AB8@H>JeEu2d|^zTl`bAB!~ ]RMUEէ D. uvNxTl=k%5 r؜{v_we&~AnX(9R4 B0bL "pyj];*r;34/rB:;'<_*B0J; yī=9>O4, %Eg^ZPí-xU࣏rE\,9G4i`܏E93@D.g2-pԘp0}cl>\oE :66xfG U\!!ooͲ\>~ډD ;}loG}7#!_relu웊ˆ@ p2zJdL C+s˕ K$W_5kf1gHӄ \+-]W| D1ob:Zeg*pFR.-MDpE#ߤ?>;Y4Jcc7IRB"m < 1ὸw¹kΥl VAӡTjzL v⫯zO?EQ\wb|M.*϶wږ\ijS0\W4*˕B_Ab_d4T-B/R@h^:|ipeOU*%fP`Xp*;qKos8D|M:@78tp͒W+@9 >̣R)fu`zHFZ!H/TUZQ[Z1 hdX&f. ,WƦ"W J.FٵFBT'k!(4ɴ`\):*HRPH2f#a/j҅rel)e6s2'<|AUUXl'˂TY} ,z?%aG$ & AZretq26ˊ@آ59{Ӓ $ bƕ5FVZ9'+щ=\W$掸F}O#Z=4. ̳i@19̇ğt8@Ulʔ)0}zL>L SNqى'~%+cP/j@9 Y"@Twiνɓ's= 1yfB ˊEF}&rC % (xG|EQ{_ -Z}G !/_iӦis EU@DѱW~S1r † r3bƌ_VǘF!rr k )"{FG/Mw\<<-nzO0[_ɨ3 ^{H}?x S[ 6N xKl(WʟYjt x᧑YOF}E+)Aggs0:1o ?Y\ (~)*mYhͨN;E1#шA&:TU@$:L!"`_EEmщn+QDY\cpULx}^8BD`޼uSU7]VTuA~)%}'DiQH. '| FޗX÷9DR.g'?8#??Q?,k{}֝3f̀{]Y B'K X{#D4;Rc۱vZͱiBvjb $?YeGRVK%+ 4Ta 3X G|7У?f'Noe+ϕUVmahh$I| ߇߀|}fBXQs֦'@92/ܹyfdѓx/coVGe\]~5P\0aɞ=/dP]^De6A)@դ#zqĿ}}} IK Iy$8#9g*<:x">@si^i[ĩ0>\eXG%KBooEp~iみ/$رc,[v+sɓ ~O8yDE~o/Vs! X'@^}? WGx99ǃ.f5ŋy#`E@4"?bK~TLxW@ =ȟ5W-^yL#L Y{siĦMW¦K2Q)' Y?M @dʀmme0,3""Lb2OmjO= " wPs= sc*QYf۰J<Ys ÇYtU)`<4"T wlɻ}|YEG!Ѵb4 ? `'OXÌd%8pby{V@D~gR2o#E/:!B..~\"o|&n 8Lj@DӟG zX ~͈>z'i"3gTKkv˖/Q;#dQ|lܹswT{ӁKtA:pD7iIޱpBfq @$[@4U?~YpY X3XʿuQY]"Pe,g 8?99X.a*/̼[(?v,Xm2|"5KX"*GHΜ* ;:IS[4(3{sLP^Oӧ-ɯwܞj#X_' E_tjeG3"%X>.TDU;e~#c׭[G(LZRdkxGg hz6-[VLL$ǹ S~7蟑10)L ,(S65z@___u,Q;/m٥"Pd nmًR_J!aDɴK웉+rկykVpٿt2r$?Ax 4Ku3Oc.Dɓ'kX wܴqGW[%/*71~ZoAEr݌eœ9sKX!q|nA|]U7F|f)LL ^ɭ ===Z& ;a_&Ϗy 4=vd& .\@# TaQQ>k\+(!7纜 l?EEQ⽵]{+<7ۀa pc &A>waݺ36Go3)Lk '<)EmvYzq A2-oڎ>sŤ5;x@)c'Уpyi܀/#70͛\kӦp*uJ&w"׬y@#~@`}~M W绝ڥ6>?n`]s޼)@1:Q D\1fiScYE LVMD1QG ӌr꣔9b.Z/_aLߍ>L100Xk>@NI`-߄e"Ҡi ɓ}T"z36@g=p`_G}0F={XKeRaʔ C>c 6qeڋ'Ễ>Xp?5@B@ oz5xJ~,eM( K|V'-^Yô' @@p4;kG"k0}DHGYm7m4??n-̙ZdN$?=22nߙ[ЂuCdi_7?LN\u@O sji&S? X|`$8k5wRݳOrWkF(n 4ɉp=QO{l7!@X!I!;yn'&Mx O?U!Dm{<ǑN?N;?>Gyg!-* g,c](x ؃2K~]gNoǪU4&)$b @^\3ڊE o"fD@tڵڊ>>`%Fc;wo.9s&L8&NB;/hp';eJbhco&L00i ~'sfHϫrK΄+pY#JN7< ̚U--/!@S;t-sfz1i…[S80̚Nz`5˦ @Ob_dΊ-q9JY*Y,׉YdYBMeY6͒xu~@/Y~ I;g4ώiߓ猖޲ejmԩSj1a| B$qs氝#WZCNwӞמdxaX(+Zu"s~g0&(aB~@FvMoo&/|bŝ~z'T8,""2b#e0h+V,gG\WvK@'H޵hӬ]gܭ+AbϛQ^zYTu6wDgmNfg$"5}![z1ffo̾'`>(( #h<ˠ^1=󚨬[Y_3X⊙kdʇmS1@x' QXɯN A*PiL+ 0wD$ ZĖS3ay0k^ &0!1j ^Ѕ p?n=0bMk6aiԠhUjlY?8q,N_ >M})M\ Л M#8$Cɘfqk=Usg]v%Q[>"4|ziKv\Hp~h~^,` jA`4 ^ ezz2` :OALЗzci:|!t od+Za˴Sѥ#X>u I Z'&w.fl %Eygb- tV}`2/k֤;gavRTa%9sf3M9G-,.9{qY)|tλ 6TgN -t'#?,m2"y3^lb[zl⥰kgg1VcSU-7 <^D"˗(V! p0Dc70 GD6yM6_>ux~{֕={h/֭O QG \ʳ(Rv?hթmU^p, lq^޽~E[6E 1?ƍ m8G Xs߈v{+t3]6b{vs׼ˏ˯G?}zO#ph@o|a^<#?YjD)аW2242֓_{#/'%]h!߿Oo[g?;1JVa̙^j<j5+U;!-`P_zOh'xժU\ܹ mۦm}5{_ux=?DBnowyfƍp!>C3ÎڰHP03'OUbP^|apNv';ӏױ`-Z4"E$j[M;0>;) |o> caoXmۢKJYT㥚?huԩ~KmQ;ڻ'wt"ˇ~VIВڬ:ĿVL>CȆ,3D''bn-(gX @F ˉfV}@{wyhxcIgB`yQL |3&?"ė> 7WK܈@eN:bd>BAX/7&#d#ZWYvpY ϓZ=_ \NF#_&ӂPnuג?EGNZxoFο` 3F_s":@FM`O#BNxFql{"{ѥ׬y%ђDڬCG Ӏ."s8~|GٴޞX| LV0| k(a?3'`;`{=Bx[(?؃$AvEo;80%(Ƚ+Y?z罻7-a, =k>͓9@`_ .@,c'7QObvuuq٧TܩL#5t|5]ǎ ĥ+N5pOu8 읂r.K?!ͦ`[kYҒmd+,-h5È ?3G'O +~{O~{Y_O=.Us7.^au|}ӦZjEUg#$lc->PT{̊5~zJL xGZ#V@gYuxP_0(?@$+Y86,0sY&LJDLpx]ݳ|Qo~E!s:piصk7b/Q>,ɹX\ wX+ O<6 b1zj*8V@m[lo U 0*OWx;qj "s͗y‹sX2j 0A'|ۋ^b ()LwW]{SLfkњ$| ??NXlہŜ ԶY[U` 陮mU`j F5k0k#Y"GbSdocm0Ų>Aڙ{.fK@DGC*dWAxWg*&~£XE]lB);a˖',+e kwvv { -$ϲJ/4Ke`PobQ9VW"5:> 8(mb*n<8eT!qj zVm/N(^ m-}vxYt.X{СC Et9y, :,bbDi}$U 1 Xnˎ o۶n5gk ]L8-/>/޽{ Qzjz`O[z$t9.>"_Ho$ Zi `bʃ9|A~aT@jk~(bE1JQ/SNKTc7ndT/É5/e%~D(Kœ8صkNEbV"7yO DVrюhE n!+`=>4T,^z{{})C|hr wAj-\>w6.Ct"wwk9t<)3 {ӌ IS޷grr*?0OrJP@KCp뭷&F vgpmysG ehOBDb%9E`/C^1/9hO}o.#JwLY} v,;$zH_{LIl䏎 !?X,}cv?-CTj=Cu x@\@m?H,Р978H%|#pÍ_267» "?!'' yc|$Bg̏N@@#ݿ`>8OKs> ?=2F5W~K:4`>J@Q>/(]9Bp~Rx肉>'kRj#FQ"ӿ&;UP*,W^E+5Z9{Do%H¨Ro0߬!eN@pIBw\ "- nrݝWT."i sTd[*1X@7}?-}ur /q&I!飽4:;' &FՀh=[,_x!) /y!=p'(J Rsh%ˁ  )_~;S?%>W{/p1Sy}|joN "ϊZ""f(s-D?;ƍ9o$i'T 'E Vk@3[e>R|, 4i9e8gxf WpR&kZnb)>A[[ G1wCZu!)/y1 5(sč\lPK/1Gwd1;]_ڬ b#ew4 4шOt] 5h+FX -lmmYq;[Jm^n@] \֖Gʟ{`52@,4#X0g6ZXc ww]D.=ˋ9dfďڕt]W7X'QFb@$ZJmpB#|JW>^*`5ר?'xJ5+ܼ" "4os֖qoS%?"fDd|R)ҀLX#k;ƍN!z?+| (PkKrelH+s9^wAP8V2#:~y~y?@5:JyP˙X+ X | 8OY?T5O H 8GۙF~@uDT.)zpf~!IqQsl !KŖsTuyl*R }>p o?f(CXgrPX*+G?Yܐ8%ȼ i}Տu2CXԱ$0kY߯T_Ϧa<_cF0cxo>/sΞۏ@@N?I*Y.~IUիY3A&AFrжv]q\{ׯQ##ϒןD҅q]+h /8ΐ!0F&z* eN~9ts`6rel˕C "_*i R1?olfN~m;蹎#o!,#LЅʕřdh&3FOpqip!WC#iA+F8SnaElY|=eX u ;/zdn;!rmZ@d8C *. +Gעg a/}_/Hŏ _ƈ/Ga;!>VaH _+ΐ+EU|D_-Zzǵwcd2KAFJ'=G@MutҤrre trPqZ~Uٞ ,> wI TPRbAK}c>b1Bn$6![:(QND䐌-}} d:҉d jc؉O ) !J"DD}?#~0 BIplGg“vD!>䰉,Rp2g`ZTJ Tӟ4EAw2}}z`^dۑ P@?X IDwqG HIENDB`mozilla-vpn-client-2.2.0/macos/app/Images.xcassets/AppIcon.appiconset/macos_appIcon_32-1.png000066400000000000000000000007741404202232700316060ustar00rootroot00000000000000PNG  IHDR DoPLTEBBC``aSSTԋDDEoopTTU&&'㚚}}~Ar tRNSqX!ѰЬg L/IDAT8˅r0E#mnPElFiaz?{\ؘf43r}2 g1a!=v&סGKIu|jKCQ7pXY,B@Gt*-,90Y D,łAZ^ɷ),mh @ca fG9J;.mk]=y~JXn RLw`m>0bF#?fwGXn~#>pSdf4IENDB`mozilla-vpn-client-2.2.0/macos/app/Images.xcassets/AppIcon.appiconset/macos_appIcon_32.png000066400000000000000000000007741404202232700314500ustar00rootroot00000000000000PNG  IHDR DoPLTEBBC``aSSTԋDDEoopTTU&&'㚚}}~Ar tRNSqX!ѰЬg L/IDAT8˅r0E#mnPElFiaz?{\ؘf43r}2 g1a!=v&סGKIu|jKCQ7pXY,B@Gt*-,90Y D,łAZ^ɷ),mh @ca fG9J;.mk]=y~JXn RLw`m>0bF#?fwGXn~#>pSdf4IENDB`mozilla-vpn-client-2.2.0/macos/app/Images.xcassets/AppIcon.appiconset/macos_appIcon_512-1.png000066400000000000000000000260601404202232700316650ustar00rootroot00000000000000PNG  IHDRæ$)PLTE-+5-+5-+5-+5-+5-+5-+5,*4-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5,*4-+5,*4,*3+)3-+5,*4*(1,*3,*4,*4##")%$+ $)(1!ҵKKL<<=!@@A""$NNOlll...iii112ZZZyyyhhijjk]]^ӆ,,,zz{MMNwwwOOPJJK8r3tRNS "2[F.&,)0:6(nWI=7+;o;$~)$QgR 0N/hHy?<0 ;;W-_4(.ǀ/]oJTb:U.s9ܾD:IPP_9ɪ ,PKrB.GE;()Vu EZu9 W)Q+V9쯟-,ާgؗWQ5W=0 ]ŃCK&w(;P|t1+%C t十,o&h%,Zg(%a JN?x(0HhHElSNC( BڒE*Pf)aGpVBů%,"M2P/El2џ?('mcSoݯOBrݣ?2F}`w"-eqpt cfCM k9,NYVSzb 4d`C7ބ{*OCXM+0d;%!6o8~8#m!X A?3yM?6xMA`N@ d4=LXȻV {4qD`Z_0wo&!)EElAE 5\Lb !<.݃G`ցj̷q%xYu s/-@[@K@{ EkiN [@`^;@0^*@U FD&3 XƁ!(󶱲n@ VR0{DyӬϨ;@dfR ~&[pdy0Y5*a@2>@$kk E7ױfxhB-@94$(Ε?D B觗3c<4 0-G<*[ @@n@ A +Rg.`TP]LOUe9P;Eyo@DWv)cV(6|J3X\\@ RO !@?9M4-I矏[1w="U@#̶ݟͿ^sKL$~ @«Ҧi_DB0HF@W!)Q()*Bj¤p ^P& Mk{@hAIrwXրF&aO y  hBmQq Q5{ UB$"gMߜ&am8 AW4pl2K/W)m_85W@ H725;o _y)| *hKD@: X /󇔓@0ys{0 q~k>0`@"c̢P4K p+kTx0mxBǕB$*Hw)Iv/@"ǣY A?"@Pa=]R,M;:5"lǟQ&&@bѕŝPfnTa98DtR Z "K ]$| p3A`ac 8 'j # @mR t.7 ?]yU@BS }Q"v9Q3|;%Fn8##@M)~Ppn/圝&T28߫ ?b(Ji7AHeP8,f'΁?$~E)5ZB?}R@E)U>6![|  aoxB>/ HaD<+T6Zv0KL8 @|HP.Hw$c .* `!ݟpz~R$FX*[H K 0t?`V Fz*K`Gtun0dFuQ>gg_| uCI05 1}H% ʀ SȤ| /?2EMJ x-KLpc3x4HK j DL hA=Ɖ:+X\ x+tjGp#ZtG͋Bm@ KPw|K@;LK@GhF5/.A ^X @D ЪdxB-jqH76tt!@vOV%v cP?C<YyD>xJիdi<KL ^\;.r?HqO)HCuL>_ni OMh#|請IR?lL_c2H}A$ Q!btk'jA,CͿwG7~cy/!&ՁgKmT:m t{_Q_]ZǵA:7.e.S3Qo?79_]_ 74f&_k+ IӁA&`aޗlW|m+"Co$Hy M6'?[hQs< `7 ށ qo-) ?򜞾 oF>}o!H'@/1j@jI>7(9?%@ 錿Cockf-2 lXqr }vӅܠtAy\FMB͇߶h` y3!e\W]`|B'WgdJG-] B@ LEL wRxw}}kTǀv5ig]nQVb?)Hܔp@TȕQ}|gUn i x e^|Ly `.7WyJ9BՅ0|WN|@/ x|('9 J9`S_x=Q\HmQ*3̣[Bx,"\$ݧd|W5dudeق,<)۬eƋMV #3V>I<_gM!۹H6KIu~ : M}V)i(>&9:QRz <잁LJW80_U%!e8]MSJ}:r@mo9Bqb@ HycO%" %] mh/rT1@A @[@ ~):ߢ}Mԩ&r?4㘱"+n 7p" C5q0\'B7~_]qshVr0ZW&KHHDl [њL;V>_J]R:z^pg X?%D@^puC@)K>"nƈ{^ BiA U7` " oWEd+ve#"rAG@m\j'%e pݐ6Δ,Y6R(< ScS/G}b>*7mec#a[9 g`Tr5G@|wb?oA J @=dg \(x#^2`0:?25ԟ~R@"A {Ex%o**ώ\ '@ohe0.(3 tG>^owzv\M_M`ԕ(j;Fv}ht#s.0A$c䯁#| @(&2%7e/#ckt3֪ 7Y4LN6: @gHm Z/qaߗ X<1шh$mS?7Cco)xB@ԝ?1G6KN1@}@ZvC`>ߝtK**] oH/^+~??i% &<^İT>#fCtUQF@P<г{=Vz:-F뢥ۢ8J'+ko /" =ޛ-4V j[p ϝm\.9P޹{-6ۈ~&Hz'Bn@zģ_@PCO`0j(76'+ _0Zzt )d(gltWFQqjI=u +TLIXIɾu`yg_/PD )-U0=Q[=s_`Lu\$ R;/0Uu`?"wxi` kO 'Jкn_P4 }k[&tE`" 65I-@8BItZhD@g@+0@=gg G@VN_h @I@ k{OG@A@' GX 94(h~n {`@I@As s`hg~ﶟOn0 ~/~4:|}믧ϰ;h~zvPVAc|9Grr`_2"y+_;'dn葙?Xaw(ˀA_"`o&&cd܄d5?w3O_.-U@#~__ѧ1pky@&@@M'tm?]AP_7`JUYF?|+R$?,==3B/s#; .}iߟ/3/Rl 63UFm*Ιe0ػ$'0>UɷA-?_fq%p>,/~uQ`Xs}Jr?cz9>pf@bC-'4u@ypa(x;[ȫJ:}C|kѴpwO'!rN ^G\##o{'b@]m[* B@ŤIEV!.wZῡh#@LkQ h`\Ibh0P}gUXJ΅`5o/>ˊ5 @Spyt?Kz8 0xnW`[`V|ϰ0Q ]JO*3{15r:$,D@;ZIVY@}rdvQRT]vC 0m@;C! a*`cX@L_+MS4| wγ2EA^vI@96k̞#X#@;vmH m`@ `;0K5ŝF`mC`_g.L&)@4Bk4 ['@{[B{@A 3 :b ?G%N ^ͲI )f*ϸmN67K '8/ |37(!NVd_ @N47@ `s Yςt\ebR?*x3cCB郍@X@PG5}qtb('nc>rYMI?mЌ #@6kĮ.hFW BH.ENM$ wvrhw :2¾+4ӿ2]_Nd_CZ6~5WHJkhu4ܴτ :(J  9)*R> p;+6 lNl,?NdV@t 3Z{^ 4  &`8d&mY?9BNև5qOc3/ ~b (X=CnZ Eܔj~B~N?9}3u)W&l3NS֗`D!?/!Ez $MˏX?;E2t  "͌}^7P'%`Y:6@=._ʧv<,CO9'5,_ I/@~E|b" זN.=scO`ڿwp$Fr7/-_j8u"Im|n^8svd2Ō+m:xJJQX`x|ӧ"p{)a p&ϝ\I/,YP*>Lj1m>Qp"7/CƤ𓽳[J#p"c%%G D+ASEURބ++酴2^N>T&FP/f&B4.ߡ)@֋ +?Х!`f 1Z N?4'CldrmG+j"?@} m`ؽ_Ѧ!`vA|5#]_ !}+p}_!9z@,Y0 J ]};+%^2LbxsߍR,P @LWCT_sE4*z6dW}Upzş^hS=ZՃ`jXavA;kTwR 3@wc9nv(m@!&*?q;+w o@ u˅*?Y4XF?\p8dr5JhxT 4,ZH,(h`B;/ln` y60Ʉ|J %l'@@A+{\ <6pːY neW/Td BY OI6J적S|xdXC̀s&A[$LBՏs0Ȫ' j!zh>H]Np3+? `,(/.AA`)!ëqT9,.y(eoCZyUZhzkѬp4BV۞DtU,+ )>pQHteXQ@䁰R!:Z80,#Գ@,^H+Gm}B@|M Mt0<,TL=w8f v=(Cb7ւ;4t@Im(P%i s'k4j>E"6 ({#h1M4lL`|KU 4`ˏ IDFm@ 鴲Jf\6/nT@WuK6 wL> mEBeG_vaBtK~|7w3wiBQ+h#"rըEGIjݺ)f^[v̐P_o 6sS~ŁSo&k1FS?,7S<#_s Sā9Ql!}9׼ *,lԿ0c?=P?s^'APjX<?%oYc`,fpP ?"@c@W ~]Ykc_t"s@ .$R'@6?x(?3jכ -? ="Goכ39ac/9@s.46R?F7;c k D)/\ϒݿ7"PMr.=E 3؜ߖ{8XHw;i讖n>G=8 }[@x#*~߃~|l+7(< Zbۓ}#ޯ*r 4d9Mh.ϨWA_p4x= p,vAO[ D&̀4A&yZ.q9m=')joj'K :AD,;O8g^@>:}?0m]`L)A$q >ܯ,}Պk0B!]ԓ{T>WMP ]Ҳf+0fˊ ~ 'v:k\m;сcۦTa AbsCb.͗%6y0[| 0[yMhIENDB`mozilla-vpn-client-2.2.0/macos/app/Images.xcassets/AppIcon.appiconset/macos_appIcon_512.png000066400000000000000000000260601404202232700315270ustar00rootroot00000000000000PNG  IHDRæ$)PLTE-+5-+5-+5-+5-+5-+5-+5,*4-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5-+5,*4-+5,*4,*3+)3-+5,*4*(1,*3,*4,*4##")%$+ $)(1!ҵKKL<<=!@@A""$NNOlll...iii112ZZZyyyhhijjk]]^ӆ,,,zz{MMNwwwOOPJJK8r3tRNS "2[F.&,)0:6(nWI=7+;o;$~)$QgR 0N/hHy?<0 ;;W-_4(.ǀ/]oJTb:U.s9ܾD:IPP_9ɪ ,PKrB.GE;()Vu EZu9 W)Q+V9쯟-,ާgؗWQ5W=0 ]ŃCK&w(;P|t1+%C t十,o&h%,Zg(%a JN?x(0HhHElSNC( BڒE*Pf)aGpVBů%,"M2P/El2џ?('mcSoݯOBrݣ?2F}`w"-eqpt cfCM k9,NYVSzb 4d`C7ބ{*OCXM+0d;%!6o8~8#m!X A?3yM?6xMA`N@ d4=LXȻV {4qD`Z_0wo&!)EElAE 5\Lb !<.݃G`ցj̷q%xYu s/-@[@K@{ EkiN [@`^;@0^*@U FD&3 XƁ!(󶱲n@ VR0{DyӬϨ;@dfR ~&[pdy0Y5*a@2>@$kk E7ױfxhB-@94$(Ε?D B觗3c<4 0-G<*[ @@n@ A +Rg.`TP]LOUe9P;Eyo@DWv)cV(6|J3X\\@ RO !@?9M4-I矏[1w="U@#̶ݟͿ^sKL$~ @«Ҧi_DB0HF@W!)Q()*Bj¤p ^P& Mk{@hAIrwXրF&aO y  hBmQq Q5{ UB$"gMߜ&am8 AW4pl2K/W)m_85W@ H725;o _y)| *hKD@: X /󇔓@0ys{0 q~k>0`@"c̢P4K p+kTx0mxBǕB$*Hw)Iv/@"ǣY A?"@Pa=]R,M;:5"lǟQ&&@bѕŝPfnTa98DtR Z "K ]$| p3A`ac 8 'j # @mR t.7 ?]yU@BS }Q"v9Q3|;%Fn8##@M)~Ppn/圝&T28߫ ?b(Ji7AHeP8,f'΁?$~E)5ZB?}R@E)U>6![|  aoxB>/ HaD<+T6Zv0KL8 @|HP.Hw$c .* `!ݟpz~R$FX*[H K 0t?`V Fz*K`Gtun0dFuQ>gg_| uCI05 1}H% ʀ SȤ| /?2EMJ x-KLpc3x4HK j DL hA=Ɖ:+X\ x+tjGp#ZtG͋Bm@ KPw|K@;LK@GhF5/.A ^X @D ЪdxB-jqH76tt!@vOV%v cP?C<YyD>xJիdi<KL ^\;.r?HqO)HCuL>_ni OMh#|請IR?lL_c2H}A$ Q!btk'jA,CͿwG7~cy/!&ՁgKmT:m t{_Q_]ZǵA:7.e.S3Qo?79_]_ 74f&_k+ IӁA&`aޗlW|m+"Co$Hy M6'?[hQs< `7 ށ qo-) ?򜞾 oF>}o!H'@/1j@jI>7(9?%@ 錿Cockf-2 lXqr }vӅܠtAy\FMB͇߶h` y3!e\W]`|B'WgdJG-] B@ LEL wRxw}}kTǀv5ig]nQVb?)Hܔp@TȕQ}|gUn i x e^|Ly `.7WyJ9BՅ0|WN|@/ x|('9 J9`S_x=Q\HmQ*3̣[Bx,"\$ݧd|W5dudeق,<)۬eƋMV #3V>I<_gM!۹H6KIu~ : M}V)i(>&9:QRz <잁LJW80_U%!e8]MSJ}:r@mo9Bqb@ HycO%" %] mh/rT1@A @[@ ~):ߢ}Mԩ&r?4㘱"+n 7p" C5q0\'B7~_]qshVr0ZW&KHHDl [њL;V>_J]R:z^pg X?%D@^puC@)K>"nƈ{^ BiA U7` " oWEd+ve#"rAG@m\j'%e pݐ6Δ,Y6R(< ScS/G}b>*7mec#a[9 g`Tr5G@|wb?oA J @=dg \(x#^2`0:?25ԟ~R@"A {Ex%o**ώ\ '@ohe0.(3 tG>^owzv\M_M`ԕ(j;Fv}ht#s.0A$c䯁#| @(&2%7e/#ckt3֪ 7Y4LN6: @gHm Z/qaߗ X<1шh$mS?7Cco)xB@ԝ?1G6KN1@}@ZvC`>ߝtK**] oH/^+~??i% &<^İT>#fCtUQF@P<г{=Vz:-F뢥ۢ8J'+ko /" =ޛ-4V j[p ϝm\.9P޹{-6ۈ~&Hz'Bn@zģ_@PCO`0j(76'+ _0Zzt )d(gltWFQqjI=u +TLIXIɾu`yg_/PD )-U0=Q[=s_`Lu\$ R;/0Uu`?"wxi` kO 'Jкn_P4 }k[&tE`" 65I-@8BItZhD@g@+0@=gg G@VN_h @I@ k{OG@A@' GX 94(h~n {`@I@As s`hg~ﶟOn0 ~/~4:|}믧ϰ;h~zvPVAc|9Grr`_2"y+_;'dn葙?Xaw(ˀA_"`o&&cd܄d5?w3O_.-U@#~__ѧ1pky@&@@M'tm?]AP_7`JUYF?|+R$?,==3B/s#; .}iߟ/3/Rl 63UFm*Ιe0ػ$'0>UɷA-?_fq%p>,/~uQ`Xs}Jr?cz9>pf@bC-'4u@ypa(x;[ȫJ:}C|kѴpwO'!rN ^G\##o{'b@]m[* B@ŤIEV!.wZῡh#@LkQ h`\Ibh0P}gUXJ΅`5o/>ˊ5 @Spyt?Kz8 0xnW`[`V|ϰ0Q ]JO*3{15r:$,D@;ZIVY@}rdvQRT]vC 0m@;C! a*`cX@L_+MS4| wγ2EA^vI@96k̞#X#@;vmH m`@ `;0K5ŝF`mC`_g.L&)@4Bk4 ['@{[B{@A 3 :b ?G%N ^ͲI )f*ϸmN67K '8/ |37(!NVd_ @N47@ `s Yςt\ebR?*x3cCB郍@X@PG5}qtb('nc>rYMI?mЌ #@6kĮ.hFW BH.ENM$ wvrhw :2¾+4ӿ2]_Nd_CZ6~5WHJkhu4ܴτ :(J  9)*R> p;+6 lNl,?NdV@t 3Z{^ 4  &`8d&mY?9BNև5qOc3/ ~b (X=CnZ Eܔj~B~N?9}3u)W&l3NS֗`D!?/!Ez $MˏX?;E2t  "͌}^7P'%`Y:6@=._ʧv<,CO9'5,_ I/@~E|b" זN.=scO`ڿwp$Fr7/-_j8u"Im|n^8svd2Ō+m:xJJQX`x|ӧ"p{)a p&ϝ\I/,YP*>Lj1m>Qp"7/CƤ𓽳[J#p"c%%G D+ASEURބ++酴2^N>T&FP/f&B4.ߡ)@֋ +?Х!`f 1Z N?4'CldrmG+j"?@} m`ؽ_Ѧ!`vA|5#]_ !}+p}_!9z@,Y0 J ]};+%^2LbxsߍR,P @LWCT_sE4*z6dW}Upzş^hS=ZՃ`jXavA;kTwR 3@wc9nv(m@!&*?q;+w o@ u˅*?Y4XF?\p8dr5JhxT 4,ZH,(h`B;/ln` y60Ʉ|J %l'@@A+{\ <6pːY neW/Td BY OI6J적S|xdXC̀s&A[$LBՏs0Ȫ' j!zh>H]Np3+? `,(/.AA`)!ëqT9,.y(eoCZyUZhzkѬp4BV۞DtU,+ )>pQHteXQ@䁰R!:Z80,#Գ@,^H+Gm}B@|M Mt0<,TL=w8f v=(Cb7ւ;4t@Im(P%i s'k4j>E"6 ({#h1M4lL`|KU 4`ˏ IDFm@ 鴲Jf\6/nT@WuK6 wL> mEBeG_vaBtK~|7w3wiBQ+h#"rըEGIjݺ)f^[v̐P_o 6sS~ŁSo&k1FS?,7S<#_s Sā9Ql!}9׼ *,lԿ0c?=P?s^'APjX<?%oYc`,fpP ?"@c@W ~]Ykc_t"s@ .$R'@6?x(?3jכ -? ="Goכ39ac/9@s.46R?F7;c k D)/\ϒݿ7"PMr.=E 3؜ߖ{8XHw;i讖n>G=8 }[@x#*~߃~|l+7(< Zbۓ}#ޯ*r 4d9Mh.ϨWA_p4x= p,vAO[ D&̀4A&yZ.q9m=')joj'K :AD,;O8g^@>:}?0m]`L)A$q >ܯ,}Պk0B!]ԓ{T>WMP ]Ҳf+0fˊ ~ 'v:k\m;сcۦTa AbsCb.͗%6y0[| 0[yMhIENDB`mozilla-vpn-client-2.2.0/macos/app/Images.xcassets/AppIcon.appiconset/macos_appIcon_64.png000066400000000000000000000017521404202232700314520ustar00rootroot00000000000000PNG  IHDR@@PLTE-+5-+5-+5"!!-+5-+5nno$$&QQR{{{⦦AAC223񉉊446DDEӶ}}~Է__`PPQbtRNS հ6ͦuhM&\bDIDATXÝk{0T[EkWlÀIL9_y'!0(rn#NFd4$ ]~Lc'w{AP_foOp^q*DMaz6RVĀF=bo1p_)#s:.V>}!VmC!c%ap&KI~p}JkvkI  ,P6P:AL wo5ߏ(J8U_UT_."xNэ?LE]Bunۺ1J`>gMڼk^H]#&&p4Rg#@o r,V2&/S9VHd6M_2S(lh5u v(+/K.r^ X}QZ"qAc#[gnW_VB SCI4nEhxѲ%8o_Æol)K6h DzH1ÏinU6-j=rC63ӏ5)dIls$k3r2ͿΠTdjrpIٌNOY am'loxIo#Nx<5/z3p}.o/AyHKWW~; @h׻wr/ٽ_c顑F۩mgnތIENDB`mozilla-vpn-client-2.2.0/macos/app/Images.xcassets/Contents.json000066400000000000000000000000771404202232700246520ustar00rootroot00000000000000{ "info" : { "author" : "xcode", "version" : 1 } } mozilla-vpn-client-2.2.0/macos/app/Info.plist000066400000000000000000000023111404202232700210620ustar00rootroot00000000000000 CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) ITSAppUsesNonExemptEncryption LSApplicationCategoryType public.app-category.utilities LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} LSMultipleInstancesProhibited NSPrincipalClass NSApplication NSSupportsAutomaticGraphicsSwitching CFBundleAllowMixedLocalizations mozilla-vpn-client-2.2.0/macos/app/WireGuard-Bridging-Header.h000066400000000000000000000014521404202232700240720ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "wireguard-go-version.h" #include #include #define WG_KEY_LEN (32) #define WG_KEY_LEN_BASE64 (45) #define WG_KEY_LEN_HEX (65) void key_to_base64(char base64[WG_KEY_LEN_BASE64], const uint8_t key[WG_KEY_LEN]); bool key_from_base64(uint8_t key[WG_KEY_LEN], const char* base64); void key_to_hex(char hex[WG_KEY_LEN_HEX], const uint8_t key[WG_KEY_LEN]); bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex); void write_msg_to_log(const char* tag, const char* msg); #import "TargetConditionals.h" #if TARGET_OS_OSX # include #endif mozilla-vpn-client-2.2.0/macos/app/daemon.entitlements000066400000000000000000000013051404202232700230140ustar00rootroot00000000000000 com.apple.application-identifier $(DEVELOPMENT_TEAM).$(APP_ID_MACOS) keychain-access-groups $(DEVELOPMENT_TEAM).* com.apple.developer.team-identifier $(DEVELOPMENT_TEAM) com.apple.security.application-groups $(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS) com.apple.security.network.client com.apple.security.network.server mozilla-vpn-client-2.2.0/macos/app/networkExtension.entitlements000066400000000000000000000015641404202232700251460ustar00rootroot00000000000000 com.apple.application-identifier $(DEVELOPMENT_TEAM).$(APP_ID_MACOS) com.apple.developer.networking.networkextension packet-tunnel-provider keychain-access-groups $(DEVELOPMENT_TEAM).* com.apple.developer.team-identifier $(DEVELOPMENT_TEAM) com.apple.security.app-sandbox com.apple.security.application-groups $(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS) com.apple.security.network.client com.apple.security.network.server mozilla-vpn-client-2.2.0/macos/daemon/000077500000000000000000000000001404202232700176005ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/macos/daemon/helper.sh000077500000000000000000000333251404202232700214240ustar00rootroot00000000000000#!/usr/bin/env bash # SPDX-License-Identifier: GPL-2.0 # # Copyright (C) 2015-2020 Jason A. Donenfeld . All Rights Reserved. # set -e -o pipefail shopt -s extglob export LC_ALL=C SELF="${BASH_SOURCE[0]}" [[ $SELF == */* ]] || SELF="./$SELF" SELF="$(cd "${SELF%/*}" && pwd -P)/${SELF##*/}" export PATH="/usr/bin:/bin:/usr/sbin:/sbin:${SELF%/*}:$PATH" WG_CONFIG="" INTERFACE="" ADDRESSES=( ) MTU="" DNS="" TABLE="" CONFIG_FILE="" PROGRAM="${0##*/}" ARGS=( "$@" ) cmd() { echo "[#] $*" >&2 "$@" } die() { echo "$PROGRAM: $*" >&2 exit 1 } [[ ${BASH_VERSINFO[0]} -ge 3 ]] || die "Version mismatch: bash ${BASH_VERSINFO[0]} detected, when bash 3+ required" parse_options() { local interface_section=0 line key value stripped path v CONFIG_FILE="$1" [[ -e $CONFIG_FILE ]] || die "\`$CONFIG_FILE' does not exist" [[ $CONFIG_FILE =~ (^|/)([a-zA-Z0-9_=+.-]{1,15})\.conf$ ]] || die "The config file must be a valid interface name, followed by .conf" CONFIG_FILE="$(cd "${CONFIG_FILE%/*}" && pwd -P)/${CONFIG_FILE##*/}" INTERFACE="${BASH_REMATCH[2]}" shopt -s nocasematch while read -r line || [[ -n $line ]]; do stripped="${line%%\#*}" key="${stripped%%=*}"; key="${key##*([[:space:]])}"; key="${key%%*([[:space:]])}" value="${stripped#*=}"; value="${value##*([[:space:]])}"; value="${value%%*([[:space:]])}" [[ $key == "["* ]] && interface_section=0 [[ $key == "[Interface]" ]] && interface_section=1 if [[ $interface_section -eq 1 ]]; then case "$key" in Address) ADDRESSES+=( ${value//,/ } ); continue ;; MTU) MTU="$value"; continue ;; DNS) DNS="${value//,/ }"; continue ;; Table) TABLE="$value"; continue ;; esac fi WG_CONFIG+="$line"$'\n' done < "$CONFIG_FILE" shopt -u nocasematch } admin_check() { [[ $UID == 0 ]] || die "This script must be executed as admin" } get_real_interface() { local interface diff wg show interfaces >/dev/null [[ -f "/var/run/wireguard/$INTERFACE.name" ]] || return 1 interface="$(< "/var/run/wireguard/$INTERFACE.name")" [[ -n $interface && -S "/var/run/wireguard/$interface.sock" ]] || return 1 diff=$(( $(stat -f %m "/var/run/wireguard/$interface.sock" 2>/dev/null || echo 200) - $(stat -f %m "/var/run/wireguard/$INTERFACE.name" 2>/dev/null || echo 100) )) [[ $diff -ge 2 || $diff -le -2 ]] && return 1 REAL_INTERFACE="$interface" echo "[+] Interface for $INTERFACE is $REAL_INTERFACE" >&2 return 0 } add_if() { export WG_TUN_NAME_FILE="/var/run/wireguard/$INTERFACE.name" mkdir -p "/var/run/wireguard/" cmd "${WG_QUICK_USERSPACE_IMPLEMENTATION:-wireguard-go}" utun get_real_interface } del_routes() { [[ -n $REAL_INTERFACE ]] || return 0 local todelete=( ) destination gateway netif while read -r destination _ _ _ _ netif _; do [[ $netif == "$REAL_INTERFACE" ]] && todelete+=( "$destination" ) done < <(netstat -nr -f inet) for destination in "${todelete[@]}"; do cmd route -q -n delete -inet "$destination" >/dev/null || true done todelete=( ) while read -r destination gateway _ netif; do [[ $netif == "$REAL_INTERFACE" || ( $netif == lo* && $gateway == "$REAL_INTERFACE" ) ]] && todelete+=( "$destination" ) done < <(netstat -nr -f inet6) for destination in "${todelete[@]}"; do cmd route -q -n delete -inet6 "$destination" >/dev/null || true done for destination in "${ENDPOINTS[@]}"; do if [[ $destination == *:* ]]; then cmd route -q -n delete -inet6 "$destination" >/dev/null || true else cmd route -q -n delete -inet "$destination" >/dev/null || true fi done } del_if() { [[ -z $REAL_INTERFACE ]] || cmd rm -f "/var/run/wireguard/$REAL_INTERFACE.sock" cmd rm -f "/var/run/wireguard/$INTERFACE.name" } up_if() { cmd ifconfig "$REAL_INTERFACE" up } add_addr() { if [[ $1 == *:* ]]; then cmd ifconfig "$REAL_INTERFACE" inet6 "$1" alias else cmd ifconfig "$REAL_INTERFACE" inet "$1" "${1%%/*}" alias fi } set_mtu() { # TODO: use better set_mtu algorithm from freebsd.bash local mtu=0 current_mtu=-1 destination netif defaultif if [[ -n $MTU ]]; then cmd ifconfig "$REAL_INTERFACE" mtu "$MTU" return fi while read -r destination _ _ _ _ netif _; do if [[ $destination == default ]]; then defaultif="$netif" break fi done < <(netstat -nr -f inet) [[ -n $defaultif && $(ifconfig "$defaultif") =~ mtu\ ([0-9]+) ]] && mtu="${BASH_REMATCH[1]}" [[ $mtu -gt 0 ]] || mtu=1500 mtu=$(( mtu - 80 )) [[ $(ifconfig "$REAL_INTERFACE") =~ mtu\ ([0-9]+) ]] && current_mtu="${BASH_REMATCH[1]}" [[ $mtu -eq $current_mtu ]] || cmd ifconfig "$REAL_INTERFACE" mtu "$mtu" } collect_gateways() { local destination gateway GATEWAY4="" while read -r destination gateway _; do [[ $destination == default ]] || continue GATEWAY4="$gateway" break done < <(netstat -nr -f inet) GATEWAY6="" while read -r destination gateway _; do [[ $destination == default ]] || continue GATEWAY6="$gateway" break done < <(netstat -nr -f inet6) } collect_endpoints() { ENDPOINTS=( ) while read -r _ endpoint; do [[ $endpoint =~ ^\[?([a-z0-9:.]+)\]?:[0-9]+$ ]] || continue ENDPOINTS+=( "${BASH_REMATCH[1]}" ) done < <(wg show "$REAL_INTERFACE" endpoints) } array_contains() { local oifs oifs=$IFS IFS=';' for word in $1; do [[ -n "$word" ]] || continue if [[ "$(echo "$word" | cut -d= -f1)" == "$2" ]]; then IFS=$oifs return 0 fi done IFS=$oifs return 1 } array_remove() { local oifs oifs=$IFS IFS=';' tmp="" for word in $1; do [[ -n "$word" ]] || continue if [[ "$(echo "$word" | cut -d= -f1)" == "$2" ]]; then continue fi tmp="$tmp|$word" done IFS=$oifs echo $tmp | sed "s/|/;/g" } SERVICE_DNS="" SERVICE_DNS_SEARCH="" collect_new_service_dns() { local service get_response local found_services local oifs { read -r _ && while read -r service; do [[ $service == "*"* ]] && service="${service:1}" found_services="$found_services;$service" array_contains "$SERVICE_DNS" "$service" && continue get_response="$(cmd networksetup -getdnsservers "$service" |tr '\n' '-')" [[ $get_response == *" "* ]] && get_response="Empty" [[ -n $get_response ]] && SERVICE_DNS="$SERVICE_DNS;$service=$get_response" get_response="$(cmd networksetup -getsearchdomains "$service")" [[ $get_response == *" "* ]] && get_response="Empty" [[ -n $get_response ]] && SERVICE_DNS_SEARCH="$SERVICE_DNS_SEARCH;$service=$get_response" done; } < <(networksetup -listallnetworkservices) oifs=$IFS IFS=';' for service_value in $SERVICE_DNS; do [[ -n "$service_value" ]] || continue service=$(echo $service_value | cut -d= -f1) if ! array_contains "$found_services" "$service"; then SERVICE_DNS=$(array_remove "$SERVICE_DNS" "$service") SERVICE_DNS_SEARCH=$(array_remove "$SERVICE_DNS_SEARCH" "$service") fi done IFS=$oifs } set_endpoint_direct_route() { local old_endpoints endpoint old_gateway4 old_gateway6 remove_all_old=0 added=( ) old_endpoints=( "${ENDPOINTS[@]}" ) old_gateway4="$GATEWAY4" old_gateway6="$GATEWAY6" collect_gateways collect_endpoints [[ $old_gateway4 != "$GATEWAY4" || $old_gateway6 != "$GATEWAY6" ]] && remove_all_old=1 if [[ $remove_all_old -eq 1 ]]; then for endpoint in "${ENDPOINTS[@]}"; do [[ " ${old_endpoints[*]} " == *" $endpoint "* ]] || old_endpoints+=( "$endpoint" ) done fi for endpoint in "${old_endpoints[@]}"; do [[ $remove_all_old -eq 0 && " ${ENDPOINTS[*]} " == *" $endpoint "* ]] && continue if [[ $endpoint == *:* && $AUTO_ROUTE6 -eq 1 ]]; then cmd route -q -n delete -inet6 "$endpoint" >/dev/null 2>&1 || true elif [[ $AUTO_ROUTE4 -eq 1 ]]; then cmd route -q -n delete -inet "$endpoint" >/dev/null 2>&1 || true fi done for endpoint in "${ENDPOINTS[@]}"; do if [[ $remove_all_old -eq 0 && " ${old_endpoints[*]} " == *" $endpoint "* ]]; then added+=( "$endpoint" ) continue fi if [[ $endpoint == *:* && $AUTO_ROUTE6 -eq 1 ]]; then if [[ -n $GATEWAY6 ]]; then cmd route -q -n add -inet6 "$endpoint" -gateway "$GATEWAY6" >/dev/null || true else # Prevent routing loop cmd route -q -n add -inet6 "$endpoint" ::1 -blackhole >/dev/null || true fi added+=( "$endpoint" ) elif [[ $AUTO_ROUTE4 -eq 1 ]]; then if [[ -n $GATEWAY4 ]]; then cmd route -q -n add -inet "$endpoint" -gateway "$GATEWAY4" >/dev/null || true else # Prevent routing loop cmd route -q -n add -inet "$endpoint" 127.0.0.1 -blackhole >/dev/null || true fi added+=( "$endpoint" ) fi done ENDPOINTS=( "${added[@]}" ) } set_dns() { collect_new_service_dns local service response local oifs ooifs oifs=$IFS IFS=';' for service_value in $SERVICE_DNS; do [[ -n "$service_value" ]] || continue service=$(echo $service_value | cut -d= -f1) ooifs=$IFS IFS=' ' while read -r response; do [[ $response == *Error* ]] && echo "$response" >&2 done < <( cmd networksetup -setdnsservers "$service" $DNS cmd networksetup -setsearchdomains "$service" Empty ) IFS=$ooifs done IFS=$oifs } array_value() { local oifs oifs=$IFS IFS=';' for word in $1; do [[ -n "$word" ]] || continue if [[ "$(echo "$word" | cut -d= -f1)" == "$2" ]]; then IFS=$oifs echo $(echo "$word" | cut -d= -f2) return fi done IFS=$oifs } del_dns() { local service response oifs oifs=$IFS IFS=';' for service_value in $SERVICE_DNS; do [[ -n "$service_value" ]] || continue service=$(echo $service_value | cut -d= -f1) value=$(echo $service_value | cut -d= -f2 | sed "s/-/ /g") IFS=' ' while read -r response; do [[ $response == *Error* ]] && echo "$response" >&2 done < <( cmd networksetup -setdnsservers "$service" $value || true cmd networksetup -setsearchdomains "$service" "$(array_value "$SERVICE_DNS_SEARCH" "$service")" || true ) IFS=';' done IFS=$oifs } monitor_daemon() { echo "[+] Backgrounding route monitor" >&2 (trap 'del_routes; del_dns; exit 0' INT TERM EXIT exec >/dev/null 2>&1 local event pid=$BASHPID [[ -n "$DNS" ]] && trap set_dns ALRM # TODO: this should also check to see if the endpoint actually changes # in response to incoming packets, and then call set_endpoint_direct_route # then too. That function should be able to gracefully cleanup if the # endpoints change. while read -r event; do [[ $event == RTM_* ]] || continue ifconfig "$REAL_INTERFACE" >/dev/null 2>&1 || break [[ $AUTO_ROUTE4 -eq 1 || $AUTO_ROUTE6 -eq 1 ]] && set_endpoint_direct_route [[ -z $MTU ]] && set_mtu if [[ -n "$DNS" ]]; then set_dns sleep 2 && kill -ALRM $pid 2>/dev/null & fi done < <(route -n monitor)) & disown } add_route() { [[ $TABLE != off ]] || return 0 local family=inet [[ $1 == *:* ]] && family=inet6 if [[ $1 == */0 && ( -z $TABLE || $TABLE == auto ) ]]; then if [[ $1 == *:* ]]; then AUTO_ROUTE6=1 cmd route -q -n add -inet6 ::/1 -interface "$REAL_INTERFACE" >/dev/null cmd route -q -n add -inet6 8000::/1 -interface "$REAL_INTERFACE" >/dev/null else AUTO_ROUTE4=1 cmd route -q -n add -inet 0.0.0.0/1 -interface "$REAL_INTERFACE" >/dev/null cmd route -q -n add -inet 128.0.0.0/1 -interface "$REAL_INTERFACE" >/dev/null fi else [[ $TABLE == main || $TABLE == auto || -z $TABLE ]] || die "Darwin only supports TABLE=auto|main|off" cmd route -q -n add -$family "$1" -interface "$REAL_INTERFACE" >/dev/null fi } set_config() { cmd wg setconf "$REAL_INTERFACE" <(echo "$WG_CONFIG") } cmd_usage() { cat >&2 <<-_EOF Usage: $PROGRAM [ up | down ] [ CONFIG_FILE ] Usage: $PROGRAM [ cleanup ] [ DNS, DNS... ] _EOF } cmd_up() { local i get_real_interface && die "\`$INTERFACE' already exists as \`$REAL_INTERFACE'" trap 'del_if; del_routes; exit' INT TERM EXIT add_if set_config for i in "${ADDRESSES[@]}"; do add_addr "$i" done set_mtu up_if for i in $(while read -r _ i; do for i in $i; do [[ $i =~ ^[0-9a-z:.]+/[0-9]+$ ]] && echo "$i"; done; done < <(wg show "$REAL_INTERFACE" allowed-ips) | sort -nr -k 2 -t /); do add_route "$i" done [[ $AUTO_ROUTE4 -eq 1 || $AUTO_ROUTE6 -eq 1 ]] && set_endpoint_direct_route [[ -n "$DNS" ]] && set_dns monitor_daemon trap - INT TERM EXIT } cmd_down() { if ! get_real_interface || [[ " $(wg show interfaces) " != *" $REAL_INTERFACE "* ]]; then die "\`$INTERFACE' is not a WireGuard interface" fi del_if } cmd_cleanup() { INTERFACE="$1" { read -r _ && while read -r service; do [[ $service == "*"* ]] && service="${service:1}" get_response="$(cmd networksetup -getdnsservers "$service" |tr '\n' '-')" [[ $get_response == *" "* ]] && continue get_response="$(echo $get_response | tr '-' '\n')" echo "$get_response" | grep "$2" &>/dev/null || continue get_response="$(echo "$get_response" | grep -v "$2")" if [[ "$3" ]]; then get_response="$(echo "$get_response" | grep -v "$3" || echo "")" fi if [[ $get_response ]]; then cmd networksetup -setdnsservers "$service" $get_response else cmd networksetup -setdnsservers "$service" "Empty" fi done; } < <(networksetup -listallnetworkservices) del_if } # ~~ function override insertion point ~~ if [[ $# -eq 1 && ( $1 == --help || $1 == -h || $1 == help ) ]]; then cmd_usage elif [[ $# -eq 2 && $1 == up ]]; then admin_check parse_options "$2" cmd_up elif [[ $# -eq 2 && $1 == down ]]; then admin_check parse_options "$2" cmd_down elif [[ $1 == cleanup ]]; then admin_check cmd_cleanup "$2" "$3" "$4" else cmd_usage exit 1 fi exit 0 mozilla-vpn-client-2.2.0/macos/gobridge/000077500000000000000000000000001404202232700201175ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/macos/gobridge/.gitignore000066400000000000000000000000231404202232700221020ustar00rootroot00000000000000.cache/ .tmp/ out/ mozilla-vpn-client-2.2.0/macos/gobridge/Makefile000066400000000000000000000043051404202232700215610ustar00rootroot00000000000000# SPDX-License-Identifier: GPL-2.0 # # Copyright (C) 2018-2019 Jason A. Donenfeld . All Rights Reserved. # These are generally passed to us by xcode, but we set working defaults for standalone compilation too. ARCHS ?= arm64 armv7 SDK_NAME ?= iphoneos SDKROOT ?= $(shell xcrun --sdk $(SDK_NAME) --show-sdk-path) CONFIGURATION_BUILD_DIR ?= $(CURDIR)/out CONFIGURATION_TEMP_DIR ?= $(CURDIR)/.tmp export CC ?= clang LIPO ?= lipo DESTDIR ?= $(CONFIGURATION_BUILD_DIR) BUILDDIR ?= $(CONFIGURATION_TEMP_DIR)/wireguard-go-bridge CFLAGS_PREFIX := $(if $(DEPLOYMENT_TARGET_CLANG_FLAG_NAME),-$(DEPLOYMENT_TARGET_CLANG_FLAG_NAME)=$($(DEPLOYMENT_TARGET_CLANG_ENV_NAME)),) -Wno-unused-command-line-argument -isysroot $(SDKROOT) -arch GOARCH_arm64 := arm64 GOARCH_armv7 := arm GOARCH_x86_64 := amd64 build: $(DESTDIR)/libwg-go.a version-header: $(DESTDIR)/wireguard-go-version.h REAL_GOROOT := $(shell go env GOROOT 2>/dev/null) export GOROOT := $(BUILDDIR)/goroot $(GOROOT)/.prepared: [ -n "$(REAL_GOROOT)" ] mkdir -p "$(GOROOT)" rsync -a --delete --exclude=pkg/obj/go-build "$(REAL_GOROOT)/" "$(GOROOT)/" cat goruntime-*.diff | patch -p1 -f -N -r- -d "$(GOROOT)" touch "$@" define libwg-go-a $(BUILDDIR)/libwg-go-$(1).a: export CGO_ENABLED := 1 $(BUILDDIR)/libwg-go-$(1).a: export CGO_CFLAGS := $(CFLAGS_PREFIX) $(ARCH) $(BUILDDIR)/libwg-go-$(1).a: export CGO_LDFLAGS := $(CFLAGS_PREFIX) $(ARCH) $(BUILDDIR)/libwg-go-$(1).a: export GOOS := darwin $(BUILDDIR)/libwg-go-$(1).a: export GOARCH := $(GOARCH_$(1)) $(BUILDDIR)/libwg-go-$(1).a: $(GOROOT)/.prepared go.mod go build -tags ios -ldflags=-w -trimpath -v -o "$(BUILDDIR)/libwg-go-$(1).a" -buildmode c-archive rm -f "$(BUILDDIR)/libwg-go-$(1).h" endef $(foreach ARCH,$(ARCHS),$(eval $(call libwg-go-a,$(ARCH)))) $(DESTDIR)/wireguard-go-version.h: $(GOROOT)/.prepared go.mod go list -m golang.zx2c4.com/wireguard | sed -n 's/.*v\([0-9.]*\).*/#define WIREGUARD_GO_VERSION "\1"/p' > "$@" $(DESTDIR)/libwg-go.a: $(foreach ARCH,$(ARCHS),$(BUILDDIR)/libwg-go-$(ARCH).a) @mkdir -vp "$(DESTDIR)" $(LIPO) -create -output "$@" $^ clean: rm -rf "$(BUILDDIR)" "$(DESTDIR)/libwg-go.a" "$(DESTDIR)/wireguard-go-version.h" install: build .PHONY: clean build version-header install mozilla-vpn-client-2.2.0/macos/gobridge/api.go000066400000000000000000000073111404202232700212210ustar00rootroot00000000000000/* SPDX-License-Identifier: MIT * * Copyright (C) 2018-2019 Jason A. Donenfeld . All Rights Reserved. */ package main // #include // #include // static void callLogger(void *func, int level, const char *msg) // { // ((void(*)(int, const char *))func)(level, msg); // } import "C" import ( "bufio" "bytes" "errors" "golang.org/x/sys/unix" "golang.zx2c4.com/wireguard/device" "golang.zx2c4.com/wireguard/tun" "log" "math" "os" "os/signal" "runtime" "strings" "unsafe" ) var loggerFunc unsafe.Pointer var versionString *C.char type CLogger struct { level C.int } func (l *CLogger) Write(p []byte) (int, error) { if uintptr(loggerFunc) == 0 { return 0, errors.New("No logger initialized") } message := C.CString(string(p)) C.callLogger(loggerFunc, l.level, message) C.free(unsafe.Pointer(message)) return len(p), nil } type tunnelHandle struct { *device.Device *device.Logger } var tunnelHandles = make(map[int32]tunnelHandle) func init() { versionString = C.CString(device.WireGuardGoVersion) device.RoamingDisabled = true signals := make(chan os.Signal) signal.Notify(signals, unix.SIGUSR2) go func() { buf := make([]byte, os.Getpagesize()) for { select { case <-signals: n := runtime.Stack(buf, true) buf[n] = 0 if uintptr(loggerFunc) != 0 { C.callLogger(loggerFunc, 0, (*C.char)(unsafe.Pointer(&buf[0]))) } } } }() } //export wgEnableRoaming func wgEnableRoaming(enabled bool) { device.RoamingDisabled = !enabled } //export wgSetLogger func wgSetLogger(loggerFn uintptr) { loggerFunc = unsafe.Pointer(loggerFn) } //export wgTurnOn func wgTurnOn(settings string, tunFd int32) int32 { logger := &device.Logger{ Debug: log.New(&CLogger{level: 0}, "", 0), Info: log.New(&CLogger{level: 1}, "", 0), Error: log.New(&CLogger{level: 2}, "", 0), } err := unix.SetNonblock(int(tunFd), true) if err != nil { logger.Error.Println(err) return -1 } tun, err := tun.CreateTUNFromFile(os.NewFile(uintptr(tunFd), "/dev/tun"), 0) if err != nil { logger.Error.Println(err) return -1 } logger.Info.Println("Attaching to interface") device := device.NewDevice(tun, logger) setError := device.IpcSetOperation(bufio.NewReader(strings.NewReader(settings))) if setError != nil { logger.Error.Println(setError) device.Close() return -1 } device.Up() logger.Info.Println("Device started") var i int32 for i = 0; i < math.MaxInt32; i++ { if _, exists := tunnelHandles[i]; !exists { break } } if i == math.MaxInt32 { device.Close() return -1 } tunnelHandles[i] = tunnelHandle{device, logger} return i } //export wgTurnOff func wgTurnOff(tunnelHandle int32) { device, ok := tunnelHandles[tunnelHandle] if !ok { return } delete(tunnelHandles, tunnelHandle) device.Close() } //export wgSetConfig func wgSetConfig(tunnelHandle int32, settings string) int64 { device, ok := tunnelHandles[tunnelHandle] if !ok { return 0 } err := device.IpcSetOperation(bufio.NewReader(strings.NewReader(settings))) if err != nil { device.Error.Println(err) return err.ErrorCode() } return 0 } //export wgGetConfig func wgGetConfig(tunnelHandle int32) *C.char { device, ok := tunnelHandles[tunnelHandle] if !ok { return nil } settings := new(bytes.Buffer) writer := bufio.NewWriter(settings) err := device.IpcGetOperation(writer) if err != nil { return nil } writer.Flush() return C.CString(settings.String()) } //export wgBumpSockets func wgBumpSockets(tunnelHandle int32) { device, ok := tunnelHandles[tunnelHandle] if !ok { return } device.BindUpdate() device.SendKeepalivesToPeersWithCurrentKeypair() } //export wgVersion func wgVersion() *C.char { return versionString } func main() {} mozilla-vpn-client-2.2.0/macos/gobridge/go.mod000066400000000000000000000004401404202232700212230ustar00rootroot00000000000000module golang.zx2c4.com/wireguard/ios go 1.13 require ( golang.org/x/crypto v0.0.0-20200117160349-530e935923ad // indirect golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa // indirect golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 golang.zx2c4.com/wireguard v0.0.20200121 ) mozilla-vpn-client-2.2.0/macos/gobridge/go.sum000066400000000000000000000042441404202232700212560ustar00rootroot00000000000000golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc h1:c0o/qxkaO2LF5t6fQrT4b5hzyggAkLLlCUjqfRxd8Q4= golang.org/x/crypto v0.0.0-20191002192127-34f69633bfdc/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200117160349-530e935923ad h1:Jh8cai0fqIK+f6nG0UgPW5wFk8wmiMhM3AyciDBdtQg= golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20191003171128-d98b1b443823 h1:Ypyv6BNJh07T1pUSrehkLemqPKXhus2MkfktJ91kRh4= golang.org/x/net v0.0.0-20191003171128-d98b1b443823/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191003212358-c178f38b412c h1:6Zx7DRlKXf79yfxuQ/7GqV3w2y7aDsk6bGg0MzF5RVU= golang.org/x/sys v0.0.0-20191003212358-c178f38b412c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9 h1:1/DFK4b7JH8DmkqhUk48onnSfrPzImPoVxuomtbT2nk= golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.zx2c4.com/wireguard v0.0.20200121 h1:vcswa5Q6f+sylDfjqyrVNNrjsFUUbPsgAQTBCAg/Qf8= golang.zx2c4.com/wireguard v0.0.20200121/go.mod h1:P2HsVp8SKwZEufsnezXZA4GRX/T49/HlU7DGuelXsU4= mozilla-vpn-client-2.2.0/macos/gobridge/goruntime-boottime-over-monotonic.diff000066400000000000000000000046771404202232700275740ustar00rootroot00000000000000From 04f5695b83cd221e99e9fa6171b57e45177d5ad3 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Wed, 27 Feb 2019 05:33:01 +0100 Subject: [PATCH] runtime: use libc_mach_continuous_time in nanotime on Darwin This makes timers account for having expired while a computer was asleep, which is quite common on mobile devices. Note that continuous_time absolute_time, except that it takes into account time spent in suspend. Fixes #24595 --- src/runtime/sys_darwin.go | 2 +- src/runtime/sys_darwin_amd64.s | 2 +- src/runtime/sys_darwin_arm64.s | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/runtime/sys_darwin.go b/src/runtime/sys_darwin.go index 376f76dbc5..a0677a83f6 100644 --- a/src/runtime/sys_darwin.go +++ b/src/runtime/sys_darwin.go @@ -431,7 +431,7 @@ func setNonblock(fd int32) { //go:cgo_import_dynamic libc_usleep usleep "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic libc_mach_timebase_info mach_timebase_info "/usr/lib/libSystem.B.dylib" -//go:cgo_import_dynamic libc_mach_absolute_time mach_absolute_time "/usr/lib/libSystem.B.dylib" +//go:cgo_import_dynamic libc_mach_continuous_time mach_continuous_time "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic libc_gettimeofday gettimeofday "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic libc_sigaction sigaction "/usr/lib/libSystem.B.dylib" //go:cgo_import_dynamic libc_pthread_sigmask pthread_sigmask "/usr/lib/libSystem.B.dylib" diff --git a/src/runtime/sys_darwin_amd64.s b/src/runtime/sys_darwin_amd64.s index 87c8db8c82..f962f24339 100644 --- a/src/runtime/sys_darwin_amd64.s +++ b/src/runtime/sys_darwin_amd64.s @@ -97,7 +97,7 @@ TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$0 PUSHQ BP MOVQ SP, BP MOVQ DI, BX - CALL libc_mach_absolute_time(SB) + CALL libc_mach_continuous_time(SB) MOVQ AX, 0(BX) MOVL timebase<>+machTimebaseInfo_numer(SB), SI MOVL timebase<>+machTimebaseInfo_denom(SB), DI // atomic read diff --git a/src/runtime/sys_darwin_arm64.s b/src/runtime/sys_darwin_arm64.s index ac3ca74f63..5e91540f94 100644 --- a/src/runtime/sys_darwin_arm64.s +++ b/src/runtime/sys_darwin_arm64.s @@ -121,7 +121,7 @@ GLOBL timebase<>(SB),NOPTR,$(machTimebaseInfo__size) TEXT runtime·nanotime_trampoline(SB),NOSPLIT,$40 MOVD R0, R19 - BL libc_mach_absolute_time(SB) + BL libc_mach_continuous_time(SB) MOVD R0, 0(R19) MOVW timebase<>+machTimebaseInfo_numer(SB), R20 MOVD $timebase<>+machTimebaseInfo_denom(SB), R21 -- 2.23.0 mozilla-vpn-client-2.2.0/macos/gobridge/wireguard.h000066400000000000000000000013101404202232700222540ustar00rootroot00000000000000/* SPDX-License-Identifier: GPL-2.0 * * Copyright (C) 2018-2019 WireGuard LLC. All Rights Reserved. */ #ifndef WIREGUARD_GO_BRIDGE_H #define WIREGUARD_GO_BRIDGE_H #include #include #include typedef struct { const char* p; size_t n; } gostring_t; typedef void (*logger_fn_t)(int level, const char* msg); extern void wgEnableRoaming(bool enabled); extern void wgSetLogger(logger_fn_t logger_fn); extern int wgTurnOn(gostring_t settings, int32_t tun_fd); extern void wgTurnOff(int handle); extern int64_t wgSetConfig(int handle, gostring_t settings); extern char* wgGetConfig(int handle); extern void wgBumpSockets(int handle); extern const char* wgVersion(); #endif mozilla-vpn-client-2.2.0/macos/loginitem/000077500000000000000000000000001404202232700203245ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/macos/loginitem/Info.plist000066400000000000000000000017421404202232700223000ustar00rootroot00000000000000 CFBundleExecutable ${EXECUTABLE_NAME} CFBundleIconFile CFBundleIdentifier ${PRODUCT_BUNDLE_IDENTIFIER} CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType APPL CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) ITSAppUsesNonExemptEncryption LSMinimumSystemVersion ${MACOSX_DEPLOYMENT_TARGET} LSMultipleInstancesProhibited NSPrincipalClass NSApplication LSBackgroundOnly mozilla-vpn-client-2.2.0/macos/loginitem/MozillaVPNLoginItem.entitlements000066400000000000000000000003601404202232700265630ustar00rootroot00000000000000 com.apple.security.app-sandbox mozilla-vpn-client-2.2.0/macos/loginitem/main.m000066400000000000000000000010701404202232700214240ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #import int main() { NSString *appId = [NSString stringWithUTF8String: APP_ID]; [[NSWorkspace sharedWorkspace] launchAppWithBundleIdentifier:appId options:NSWorkspaceLaunchDefault additionalEventParamDescriptor:NULL launchIdentifier:NULL]; return 0; } mozilla-vpn-client-2.2.0/macos/networkextension/000077500000000000000000000000001404202232700217635ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/macos/networkextension/Info.plist000066400000000000000000000026151404202232700237370ustar00rootroot00000000000000 CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName $(PRODUCT_NAME) CFBundlePackageType $(PRODUCT_BUNDLE_PACKAGE_TYPE) CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion $(CURRENT_PROJECT_VERSION) ITSAppUsesNonExemptEncryption LSMinimumSystemVersion $(MACOSX_DEPLOYMENT_TARGET) CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleDisplayName MozillaVPNNetworkExtension NSExtension NSExtensionPointIdentifier com.apple.networkextension.packet-tunnel NSExtensionPrincipalClass $(PRODUCT_MODULE_NAME).PacketTunnelProvider com.wireguard.ios.app_group_id group.$(APP_ID_IOS) com.wireguard.macos.app_group_id $(DEVELOPMENT_TEAM).group.$(APP_ID_MACOS) mozilla-vpn-client-2.2.0/macos/networkextension/MozillaVPNNetworkExtension.entitlements000066400000000000000000000016721404202232700316700ustar00rootroot00000000000000 com.apple.application-identifier $(DEVELOPMENT_TEAM).$(NETEXT_ID_MACOS) com.apple.developer.networking.networkextension packet-tunnel-provider keychain-access-groups $(DEVELOPMENT_TEAM).* com.apple.developer.team-identifier $(DEVELOPMENT_TEAM) com.apple.developer.system-extension.install com.apple.security.app-sandbox com.apple.security.application-groups $(DEVELOPMENT_TEAM).$(GROUP_ID_MACOS) com.apple.security.network.client com.apple.security.network.server mozilla-vpn-client-2.2.0/macos/networkextension/WireGuardNetworkExtension-Bridging-Header.h000066400000000000000000000014001404202232700322000ustar00rootroot00000000000000/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "macos/gobridge/wireguard.h" #include "wireguard-go-version.h" #include #include #define WG_KEY_LEN (32) #define WG_KEY_LEN_BASE64 (45) #define WG_KEY_LEN_HEX (65) void key_to_base64(char base64[WG_KEY_LEN_BASE64], const uint8_t key[WG_KEY_LEN]); bool key_from_base64(uint8_t key[WG_KEY_LEN], const char* base64); void key_to_hex(char hex[WG_KEY_LEN_HEX], const uint8_t key[WG_KEY_LEN]); bool key_from_hex(uint8_t key[WG_KEY_LEN], const char* hex); void write_msg_to_log(const char* tag, const char* msg); mozilla-vpn-client-2.2.0/macos/pkg/000077500000000000000000000000001404202232700171165ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/macos/pkg/Distribution000077500000000000000000000030351404202232700215240ustar00rootroot00000000000000 Mozilla VPN for MacOS MozillaVPN.pkg mozilla-vpn-client-2.2.0/macos/pkg/Resources/000077500000000000000000000000001404202232700210705ustar00rootroot00000000000000mozilla-vpn-client-2.2.0/macos/pkg/Resources/background.png000066400000000000000000002252741404202232700237310ustar00rootroot00000000000000PNG  IHDRD pHYs%%IR$iTXtXML:com.adobe.xmp ~6%HIDATxyX  jei%jXZu*:괞:uJu%%5T4QqGYy~p"Ay뺼.yHa)X}]&B``"v&B``"v&B``"v&B``"v&B``"v&B``"v&B``"v&B``"v&B`TádJ""")Ӻ|!55Ucǎмy!!!s#Pju]jjbccվ}{hر;vbbb԰aC;UGyD۷W하xV%''WY=7oVbb5|IRBB)5^ MnݺiڴiN?:uu7xp8JMMUjj&NX5^WV!**JԐ!ClTLohgue5f̘*+11Q.x\\\{v]IIIնʧuإV(?~|otJ]jjMv$''{Z,o^>^{QRRRZ,@VvMW.ul\\)g9*' ***XBS'vzݻ^&LPiך sPWԪsԊ[yADDwF\\Һho(Vs7o.ĉݸq*-KMMULLu릱cjذaj߾+*Zƍ+q;(,,LRAñ뤢3Hǟcſ;ve5aM2Z'*+-:$Ҟk׮]T IO꒓KybŊ)?~ &8+VvL)MLL,n8qbSU#|7n"""4a„2뤢mjر8qbbbac=6lbbbaÆsşۄ%''+55U]jjjmqƄ #(99Yݺuh9Ԫ.55U+W,vVن RjX GDDhϞ=:uϟ/hСCK#""k׮e]cǏWddw^kNJNN޽{+%,a.2o -m@٘2e7WjL&u}Yw.anפI N.PLإ*..N/X%&&EDD(**3nСe~8u44(/So6mƏ_Zb"""%wviܸq(i7xCǏkUǺSN՘1cʽG RSSխ[suvNY399YÆ +57o^k,V|n_i?y%$${ F:emYZu7xC<>tPM403SaWjhCJX5=W:M8QSL)xddGLMM2lŝR_E&''{÷ŝRSS#;d+~ tt(uHĄ JQƍS\\&NX⺄EEEUhs]wҤIpǏ?%@+oh„ 7(󹊾3IMM(xEEEyPd-l.s+eLLLvXmC 6,v]ke^t;VSN-:+VڝieĉWƪNwSep8*6.ɧt-ácVrEt^zj>qփ!߆JL+ݘGyBLؕniEۮ]R= r O(<3=WCjϞ=eԔ)SNޕv:2ЉH!Vbb&NxJĉK |*z#rR2dYlv*::Z111nWNHH8s Vj;v;┘=;.!!,66VZ;11=q)>>n NZjh&uUdjو5~Ss8JMMpV|]Y=mB§DopTZhfϺSRSS^uvJ}lOniE:]^Y!<;ߘ2ФII&U+55-nij4|"55PtgUWm<;" 줢-&M:mv]SL)s+lqԩS5u%;Ӻ]D%'' (k2T :aι[3m`'u%%%i̘1>_<!))*55Uj߾bbb4vX;V111jذB {:gtXYuOWgeINNV|||뗧o„ evgPnEDDhԩWrr7\N-ʹi4~n:ujkXB^Lƌ2O7{nDY{p(99Y z7ʼv͛7Oݺu+sPd^]1~oqڭ랍:֊ Mo>Y²Uea$zKlEcXWUN7ql6q:&L UǎyvNu]YM2;+;t6Q &T8=|rjU`p8J>:aXBƍӸqbŊRr&$$̻t6'G(cDiv&Mta]VXQy%*KNN>RåS܊-T1cƔTM:U`5n8%%%U|DDD2F5f(̳[C=% ڻwoU3 6mZ7n9qڵ9M=k޽e_;PԪ3fL!UYgIEAce)S}*Ä  Pժ-X e^_sڵ 22ӝ狳ڙγ+jU`WڶͲL8ԭUS\ʻ5""ϯ.g:.U[bv rʤAWBBBa]lllwxEGGntʘjfǏWrrr~Za'Zn?~Ə_jXg}e5u5**1J9&u]DD&MTM4g!nAn׼y8I.I&Uc5eVy]WM%22@T+;,2C.ݮX%%%4+vp*""Bdu*jЉ?ԩSdz/m/iDm6Qx\,a.@Z%!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L0;D!L{GZt=rkF R@@V0 _ߚC`/%24;x5o\]wЎ;? ׅ^S :0;Da-;;[sƍS߰a$r?ʕ+˼6,,L Ѐd j>ɓ'5zh-^r\ZzV^}z-=cz饗WuT%Ģ\VZU-a]yx<Mڧ̚5KTEDDsE` SϞ=:RSSק5T&&B`[bQ\.SwUPyءZ}駺v+?XwqG%W`lEJOOpX'Ij̇;T[*%f{LnuŖXDCڰa^~e!!!zWuVAe@`jtR͝;¯ӧyJ\Pvm*++ܯ Q=* C4h[egg!!!:* CSǎ}]i1%lڴIO87o&N(0]v޽ kVM6a̎#~~nta}9ՓYuIffz;w\%IZ҆ Լy.8'ahϞ=2 Cڹn_5g5mT#Gرcu%( Ǖ0;K˖-%mg|GհaCsI_)CU%eU~T6ۭ|t*??_.Ku{iҤBBB ߿ bYYY:ti9r>L`QF8qo߮~Aiii*((ѣG;?֝wީqΓ? (B`rյkWm޼YӾ}|],_0Hv5NR((Ԣv?x<:yrrrtرC۷ou!=zTǏѣGK ~fyjժ"""ԩS'G]vUFT^*עE {ڱcNMJ&L Ijݺ"##}X%p4|po\XXM6iܹ9sҔwyG˗/ /nn;$vP$''+&&FCZb_}vU6( --Mwb\PfUIC tqD?]wjPτj}E}wt*++Kv҆ ۷k׮]JOOښ4i+BzZ5iҤJ֩,s7o$5a;vM-ZhӦMr8?MbccK.Cg䮚]rJn 4{l-[L۶mSZZY7,,Lj۶""" iFVU:r\.vܩ={hǎ%ӵ`-X@wx y5c&NVlCyE%^~8nSO=QF׼y󔝝_*,,ԋ/w,sD-]T+WTJJiر.bEFF .Puy)<*0w `(;,8]ӿyZ'K<~kРA#l6^z̟6&qa:"ռiQQQzG ۭx=zeҏUT/OxgϞ)I2 C}QTPҥû U<ё_ Z 44TO~q\v/G`Çkڴi.ՠq SFF|]P+X,]tQw[Xw6f+~̏^6M!!!UvWG3wx ]*RhhBCCV;q|)#PP4$?nРԩϫvmKTs+@D`HUvt3vn@nReu@_ׯ/Iq5p{ܓRfLܼ-Rx,dv.0"ꢋzVx!R̃:;x(22Rh~*EPt~~~\.l6~T'ÐN)'бNHiМ)PP ]u__R2sP+''GvץzQ5+rNJ˅J \!5JbQ@!Taջs"o߾]=َ֪Gμr˯˽lUR ~Cg]2 N4~Љ,)/WIe5qy qC͛-I>R}-vR/4:Cܹ֮]G^uI^ԥZ,몀%:n('G:y(+jQԁk~}dHMZt4ݐ]=k6 ^@글;ծ]vWo.}`X0}*skjyXĶXZn՚9s$yzU^=Wen~~R&MT4-8b"j /QAAVUV.222r|>Odg-j+;OʷrrrvZ=䓊Ѳeʼ6(HbmT>ǣ^~$Yfzw ө͛7뫯җ_~J>S 4Y,Re8St]&MF`BSNk&07|S:tuYriΝZ`S[d9f͚z{EJy)<){G;{rB{[նV)))o>IRLLLve?]viܹZ|lRb⫿ڶmnA#GTϞ=Kvu_U@@6nܨ/Ni U"]zE8:P[|YxUka\y%''kȐ!%º9sQF>8qB?.\Eir:KjCBBԬY3WFW_RѣG륗^Ԝ9s β;Z`v[CR'Ov@ f-Z{WTº۷OҥKߗի-[w޺&MսSRRԭ[7y<EFFjժUe|3 ِᩔUJ^bU7j%;*,,믿^X,a]AA7?gIE]6m.LC ѵ^6mTh]*%%E6MVR޽K*W"uhQf@mXr:zKOQu]ӧذP :uVXS޽|A]s5jӦs}7+%%E.K+3٤ ,3XCRFfM>v@ sQ}ZpÇkԩen4+ۭUViZ`8p5[ֈ#4f]zޮ]tKz˗+,,k32 i㮴+yU 0v@ aV\{Gv$kĉz|\x<ڲefΜӧ+--k7o[nEGe]&ꚁcbb(IZj[uNuG&{^G-j<5?OM<ٻU4""B|I٤hܹ7olr!!!2dbcc]!/њ5kTXXs+}RFt[,Rq)<\&Daw}zH)))$Ţ?T}\۷O3fЬYey{t 7h4hcǎGڿ:vIO;_]_}wTw}U}Xaݻ"""{nmذA?."_u 8P))):qϟ/k ,3dEY,(5n\U.--M+VӵqFK5``zv@%xc;,PnYYYZ~&OʻUA-^XÆ TTTSnv:۷~G/TLLLJG k [W Б#G!իZbp@@.b߿_ǎnׁ|\.&$$D6M V jYPeIOOעE4e}%u]wiСu:+ֳgO]tEڼyvء-[[n.>|6mڤ<͟?_jݪb]O8M6)!!A|~zlԩ !Cĉop(!!ASzz233믿jϞ=ڿ< 93vVU!!!Z RzԠA5jHZRDD;<5mTjڴiCPNah߾};w>mݺU.KRQq%hՠAWk.?C~~~8qy_T$]:zt˗Yf|ߓ'Oj֭ϵdٳGyyyթS']q=zw^"۰aL֭[+$$DȨAjڴ7om۪gϞ޽ڴi6mڨ^zU>%vYrڿ/_>Hׯu!!!jѢ~vmufDym߾]{Vvv+Vr[R(-[LWaa>UViΜ9Zr222q֭nF 0@~rСWVU P``l6l6ׯ&MYё#Gt:UPPB9Nv"m@@ڵkݻꫯVTT"""T~JC`Ԯ]4m4}gڻw9ݮ]֍7V3ш#W_QFZtiSX}m֬Y[%Iw}ݳ~VzzVX/B֭SZZyŢƍ}>| K.'|R$)44T;w\?_ڵSդI5mT Js18qB:|ۧ]v)55U?222S^ /PStt&M߿\5@]G`!''G7oԩSĉ~ [*j q5ǻᆱ/X,zR;vLzҞ={ԱcG_^5*z0$͚5KK.ՁJ\ .@7tΝ;+ \umڴI]v _%KTU?+99Y6oެ#G(''ĵEmڴQ~t7O>jڴ*2;222l2}Z|%m7lӦƪO>ivw_LuMk֬QPP*սޫ_~~~;w r5'Nƍ5w\}7ڹsgׯVZiذa4h.s~_|mۦ)<}v*Y\eggkڵZp,YC,4inMs:uYw?vvڥ>@~K<׫W/5j}Ta~u]ܹ}[u_wyN:hB^4|p5J["##yfl6޽[m۶u+5k,m޼sv]~z!]p>̃}HS ЦMtwK.zWafӐ!Cn:}%"͛7Wtt$i޽Zno :oYx<ްbSNz?k͚5Vma$=ZRQo-K˖-O*99Yk׮-=p%\S; G}諯*q ~``.?^w[̙#GJ.}>tC111JNNօ^aÆiĈ޽OJKKS6m$I;w֪UN;֌߯^{MӦM>o]qqqu j|͞=['O?P⹈w}դIUXw9rDݻw{l25h@={4Հh\R_kqEp8r]p1c-[>}jh֭z-LtlExf ZrN.::ZwM;oտy<]qZfKQڴimVk֬_vYVEEE_Wddo JDjkԨQ_tf7ofΜ#F=zPN$I?RSS}[P c?I~}w>jo]xc-^X{$effF;wB8wl9N}Gz嗵w^wz)r-_+Ĺ:|ڵk'ө.]h֭.FӕW^$5nX?:v벪\VV^}Uk?<<\?0`@ELm׮]CyúF'ڵk?h޼ I۵eWTiX,:~xG 4ĉ뢋.$;vL>~aeeeB;R^^^yhٲer\l2d,Y_~Y.[o$NO~԰aCI… ㊪b7߬ӧkĈl*,,ɓ5j(ڵ%@%GyDV$uI>F@WpQkN m6YW_QM4њ5ktju1}zW!I:5yd]{> 4\.|MO+WQppz!}7 jMj;v(99ٷ0馛dZKjzJ ,%\"I_4j(M<9ѣGo$gϞ֭[@Tm駟i)K,щ'|\o\yZd "ժL=#z駵o>_gĖX֢EO$իWO=x BT\lR'NP۶m/( e.KӪUNݺuuY>W^yEt:eZ5tP ܹ2a1cxúƍk֬Yz `_o>Y,6M#F$hΜ9U֑#GTXXXkWhhy _|x@2'jm{]vW_}C2lۼϟ?_nۇ'V:jꡇɓռysIʕ+5b8([bQ^THHzj֬˃E:qℚ6mQm۶uY5u]oF~~~Z` T)v8ڹsϟEi۶m@}Zv73$9Rw$uE3fХ^$ژP>{5iDO??_^}U 8P_~~iYFWVt뭷ꮻTtƍ+Tk޼y$ٳGF"`*v'N]vz饗tws^Nn:۷,mڴQ>}$I80P|aÆk׮zǵzj9NiӦ4h{=YFӧO_0 ٳZh߾>s]s5Ǐ0T_|Q3f̐TPP? 𞻖 gĈ˼vk…:t.Rs=Zt\|UVi…UvdXԠAHRט]xxfΜ=zH* n%#-Aor) @?N_ӟQaO7$_DTǣ͛7SDDn&-^;Y7oqiݺuڲe .(5dlU~BxxΝKOOwX>W3˪n)+ɓR~6f^=)4T oQmh>۾}׿zÖzHǏgԷo_jJʕ+gueZҐ!C+55UWVVVf͚>L) СCu+::,oXkN3g԰aôf%''[n… մiS_"!0tiy#?F,j̢R-V)(,,L7pOZc߾}ΖbQǎ+jO>D7|~z3FSLQ}]:6*dұtC<ڱPӅu%]ұlh6CYU[kU4i֮]+Iի{ϺѣհaCI҂ jkҤI?~N:p >}ݫӧ/wXgV\)h[ldddԾ}{͚5K:t$-]Tcƌa+6a2W^{GGb-ޢРe]}r8 _޽{, .K=*??_~~~CP҉'k̙rJ<(y睺Aػw[CZbwoM?覛nѣGeX4vX{ui<' ӐiѣrN+ޕr'i?={((1bDn[fRe{ī@NN4}t}w-j_nMF흆a裏>%IW_}u뤢nw}Wwyrrr駟 /?!tU'{<=Yukח\dUzUƹ4vX)""B֭(UV{Վ;$unnKK,tjZh mcǎ. @A`WN~QNNկ@Ufۥ=zhΝZ2eN_vkҤIz畝]thcy)!!AZre N a-_\Ν;4Z5\nM_~5jTe;.I9r>5hР֬nz'oJt颯Z-ZqeVBi>C9UY{YҞTC;XL5A>Ν;%I{ֈ#|\j4=c;wAV%K@gV^d9n[[nW_}9sh׮],qM&M4h tMꪫԤI*gќ9s$IڵӸqjUX'Izc-[L۶m_WM>].@-G]%ٷ4ClІ +tԠA9;b,EZոitSNJMMfӗ_~,֢EOz驧7կ__t!KE]D5mԗW ǣ[NsQJJ222]%),,LQQQ9r5mڴ"dffꩧ/ǣPk̘1v(Cjj _~EVU?SO,]%6u!}Z]nӣWubNcJ-^X> f^{M:q$m۶zw5`SXM6MVU/u]狲ݻ7(!!A:vܿR_7x7oztoѳ>$IRhh&OoYfS k|Q hȑ. @-ƖJpP:Iʐa8Z4tԮžے$???=ut:k.9N5oޜvڥ'|R .T{СC6lfϞ<͙3Gu]8NsuQ6Z:((H}5h mVAAAZ-[tR͚5K[n~7ozlykOgUVVƏ={]v. @-E`wNCpV--pG۵yfIRu5Y|~mmݺU.Kv]Ç_Wv_;vPll֯_/0d?Y'Ng) s) }[CA~Ѷ܆vu}a<@;"fϞc*77駟*,,GU|͘1CO?9"hho#F VZ_~ڽ{vܩ+VԈ.77WSBBT$ժm+-ܢ/\͚5dee~мyj*رY*СCs=뮻ZmE۶m5qDm߾]hѾ. @-D`wvK9> \.fΜ)謭C3IKK_oX׭[7mVWǵh"+[:8}ϗ$CSN_|smݺUYYY-6m>}h۷ڶm[Zڼy͛k*,_l6?| 0@Ce]jόz'xB?\.{1EGG@#;!eg"R6Y{ӦMڷo$iРAuv-X@oQ~4hD 8PJHHиqԬY3W[u І #hڵ2 CVUꫯ0**J[O?-[hǎ(55U~\bM4Qu+**J-ZsN<-]T/֭[T׮];]qս{Z9?I)))2e8zJ~![cT*stN>WPP V.:Kz?ѣECDׯ{׻E8::Z\r~G޽[yyy,J9N}gzg~IR?_BAUXX;v(==]/y`o>\RgVJJ7.֨Q#]tE9rnuС;vܩŋkѢEJIIQe˖СCճgOm۶iKK.աC4}tr-}]ZB23x$ː'v~$) @ͨx5x`IEݻWƪtqKz#ժԽ{96x`}Gr8Zpzj?S-==]Vg}+Wȑ#2~ 44T_|F~sβ٪ǭݻl2}ڰaK\ӨQ#WrբEj=7&k֬;Taa{1۷oPy΁%tI ].6n(I դI/„z-wQ۶mɓ'{z쩠 WZvء^xAs̑TPP _|Q:u:Kr8ڲevڥnݺUBg4|-YDIII%3l6_~[շoj*x7*!!AK.޽{Kv]ڵӠA4x`]veu Ū0b;Zno߮iӦuYj sPflrrjР?̣U`CmٲE999B*ѣ s*##CsO6l#riٲezGcIE=69888X#GK/'O/N^|E}Z 5\!C(&&FRKLܹSg7|m۶v{ Q6m4`p +\5F믿(9N=:tZj}l6lzͿi-(ykݡ'Q#;j7u=O?y?ѣGoVKlS5g]q>s<)Z^{Mzv!Ţ.Ho^xJ ߭ZҴiӴqFM>]F.''G7nԳ>([RRRv:дiӴfǫuW^%[9~v;6,ۇ^uMR OnkdP`u`YR5矽wҥ 0VZiڸq~9]tE߿7l+ .\Vgٳr矯η/\W_}?+%%Z:<{MѣjРF]mg)--M}+[D'7o}jȑg5bф g?k 4ץA@@QHv6 oݯefh3'z5w艿ߤ#G#W }|]aHwevh'h++99YR ozGu2LZ4zFQ_PYy<=*輸[nE g}V-]fԲeK=zT:rHn,,,Tjjϟy)))da???5nX8p -[VY=8UӦMGQaa^z%EGG؝ ,=s]hhC=YtAh„k)??_RQ+믿^~gqLGYY5mlvڵǭaoۏ-YD'NT~}vIRAA?^]NN/^\Ė_ŢƍG[urn{zu)//Ok朷N{*IxE9ҡCtHX'ԺE>>:xK po(8Ȣz '?7djҤ>]$g!?뙷cgwu8uA?TA'3IEg6(''GfҬYrSΩ PTT7kڴi ߹kt%hZlRRRԫW/_!$΢' eeUzMå}X-U1Ð~ܣԢYw5^-_KJV?u>L;v^_~jOG٤j9v}0'u:d ;խl6Ao^@QgVF{pmݺU֋/ z'0W3fh֭]w粳?jƌ9srssOy}N4tP=j۶mW~%%%PzQߏj7ΰD~~X\jiDj" zpX,EgIAͯ\zt+=hԂ tM7髯$5h@O?,YRkG's e4G;ui56mb=̪tCGK`=.VUE"5o.uhoU`$[nޏ7mJVg׷%s+G~6)UYنexyڲe P.]'SxxioH$m\.X[9i[t$#[*(4t[{ϥ]yy ol"Mwc~yVE]^O?TXXG}T|Y~Ng޽z'sNIRm6W7t^~em߾;UV;d[N pJjf͚[nbѡC' ,ET GڟfAC2_G``љuM}/dggI&r:ի{_Rڽ׭Eiձr>_}^?I.K6]3DZsΕTtG}oŸ7w}=fv`Zlh 8PW&MN;dn5kbUImuV]s5:zZn~A-[uYj~S"VԮE#j"POj֢Zլ:I vmܸQ'NqE(MPE-dQV<7xNxlY/˥f͚?{C5hA6(PAo"l?{oYmzI@nQ@TGx<(* E<,h* FH{߫Zk=$̞|GLfu2c~ 5c>BkF+B$KRIE.3*Fsף5j)ZmK/}K$/~1o~YYY_WTٟYկnZyӟp-ɇ>!^122)VV ZAw<]}< OO|"!B!ĥD*.kلUښ\08#QH`_?>B-6P(ަl1jv쳹ofw[{|_hy{>_<(_13oGZ K*O|[Z1,{$7v6../EMK1_տԛ}+K9ِR+#]vms ++dRlV ;&v`PHmZ+v(W & -锦ٲ/$eC;p_=;|VVkmo_(VPJ+ySUW]9r>˛]#W\q/| yы^NVT\+6-YX .YqZ[[o&կ~c !B!.؉Y8pǏL&9~8}ZX+߻aLBm"b܂A)8'r[Y5e`zz_O k]R|W>yIc4nֵcfYZV:Ln,DsZRHb-JC49c#0Ղ R E6Dpl*$ThTqp fB-K> BKnpG|B سgo_nᑌ-eǘfpjpHIyg?c~7|*!B!ĥCK֚B7LfxxG>9B8>R,Yr9M~5ZwmQ[ j2<џ׽:B\q^sX,K^ OxpDSBV J)1c%6:lY%iU( V,af>݆UC*ځ -QٵCkPoZ*A`Y\24nm2cAkڝOJ)) ]8F<ǰsfVE^?齌LL'~iooz o/d+lz[-Ob]0>o tx5K2{ 6TˏRh4ʧ?i:jE>B!"'v✘4 vl+kP2 },!B!EN*9f9t)J$ '_lkmO:M)Hɏ]ʻBUK_Nc{\e)W-e`I&T,ɸ&sUjFgr̟7ok-Hg>e;>#baP*מϹ #1qZ*(U Œ% !q3JeXYްhvH%5&TR|9az.tXVLh KbI&D5W֗-Kf|H\$:\q]*dCXZx#eמ+I$G$I%/ZzUrH%UMw}gCf Ɔ=u~[A3ðvvtD"6i6rM7mB!9wu]G```~<яcUÎ1ir]oׄLo[ -yh YB@nstH,h6,ؿC+뾀lO~w~X۱;yⓟM$kk]kl&%W>0>a;s_΅WaL{,7[v۽aذʚn\V4LφdҚlZh=.q)gCbQɄٴLΆV-mi4}ږ\VΒ۩sk-ǧCrYЀ\: w{xZVW tJL  K+tzP{}~D16O^uguÿ׎N*8pi:ď355 7ErvK!Bq>,q\uUe/`eew[ *7E$rnחSLysNiYY i5L-&=6X)H$bSRlب&v|h*!~׾1X,3|o“oz.Zk餛wl*P2D"PYBfBV\j Vλ1UӚi7R]p UC&{0| ( Qx\tjGh_B,8'Ь즰\d7˶F.km"S΅*Uw}, ^X.$6,QZMKnh6] \2^̻>EftDhٳkpÇoB!=i#>l69tW_}5÷Xgjr}\BelF*1X+ͨی(ѭrU%n\*e5aok~'?LD)WW׿w.T-+FvE҆x\(5(W,( y,^n4" фaM&i,e4X3R=7Ud;Y\vA.{-٬^wiFK]RkUtډ FVR i4mo)G_ZF4XG"t]ef.R5TR3з:l7;*,P,Vށ'n8ĶdqyZ>9ZyC.c!B!+qN޽m oj?QzQ4zS*lgYnY-n j7ʤ5+ڽG+J!# Ah֭[ӤC|#jL&+^  ~iE.1hh4-务PxR)7oej؈f~ɐI), \?)V+RIEX"Cd0ֺ՚2cb^w\O Jv۲c̣O-W}0m4lUznvɝus z֐Im*iȐ{3iM"nHHAclof]*!ad۴0Duc WenH(;-3s!ahzue P,Pnl<Lav!Ru3t Jĥ!N|Y:/zыdB!$֚=q|K_b~~)yӞvl= XBDU[00m˺@hu l( EC¹Jvzg>)~/j5yk~׽]w05jrK4 E*bQEb1qad2( ̥n^jЬ] 6L^V*Jx̅|Vs3uطӊ7.]pWY).7uן ^wV4gWO4 vZ -9t44ײ t5 ,ŲkM4G]N /+?kS䲚D\sd2VE6妒7ꖣCP\R|СC|ߠV?\q},!B!EJ;q^$ G//TUSxO\mvX&g nshlH4 q+Τy!D0熱%:-MM/oWL/Ww}{M~σB.`ꐌVc " t[^Mwa)uiŲh(URzõ;A*ܦURn\fY]sAf6*/1Ɲ3F|pn\m1"Չ+JNg[jA;{a6< nlBWuXZ뻀a .bpha>,,\u-ah Kn Oҽ7M7uK&\ˮ/X+VBu Xt(ZwwcOvK!BqN7LLL| ۘO:%Evc.tƵhrѨ"4nB₥5ӛcםϹ;-Դ[ȻocrmTJq=wc:?ĿV~6::^y !biytZJ!gNJղJ<ϵ.`3-=W9*ƢZ k2iűɐTR%*Bj͒+u/4[]9=kh4cV Ų~gLHe_ ݢ X^5D|Ę`f6tv=bNvmLOOsM7]Tn)-8`ka%rkf.Zʖ!kyt@ E^Mk c#C7.--__o~vo|{vV=O11ߧY[sR.l(=,?Iy\k(nY?G:ZL<3XX6,.Z`5\E]&ٽcdеf3{}y֪^n`C0H %ĘO^џ,.j0>ZU ζY%C6ѽDZبG.\lVsӝ&PD#Ok4feյvCb3{d3nm~⎍Qy=-BZ妛n+c !B!.B؉ O}*x[of=nn馋fSbl8rZMZŷr"PSqp@3;25ڔY_T jZw֊DUeR. v(U,afE6,sݏ锛sm.-+kqb8g-L̈́9˸ @qQJl6G>1c'B!ؒ4X 7~7+x+^2_׸˿Kgmf4;aqZO%ذtj0د!^{k.] R.4, ˆ=<q}죽j:d2Oگ&~4Ge{՚kB b'="qk25^t9ڮ}uN>JsΌrRz<ae͐I&vUKeKs-]WXTm.ѬpD\ћ;6=Nk]_2Ugn%um}.;rҳX\c!3!TlZ15ި0{(-BZpy`;b|Ls}>PXQ)4Ƹ{}+UB@[lZV Zjkþ}f~~/˄a(sB!'N\P{طo/yK;~xs>FGGs.vn j7K+ ynbCbɅZւV.4ZZ ),V/}%`P|~ZqF-Ȥ?; a~ɰj4(}oH:{vmg׮]ڵy8ẕB!BD0w5hiZ|8hZgP,k h67Z+k;lSvu-ymݧZv-6Rkar:wi]+|v3VB/x׾o֍ |_oxԸ:JPmh`sQSMwvZqЀÃz2;oh= S!xڽDea?'qWvxSe~߳sR2=I.\rruJ*4nn_^3د{[u]*ص6\H _2 h|}¹y;'<&F={J-gL{w{3:\_Ƣ{XYc_4Lx=wD>kZe7VJ\$\.s=lB!#ۢb=Zk_Z>7믿0GbqmlxеK` eJeʪk\+Z"@:I3\qg,4aaE$H&nn'? fv={,7?OJVUpL(iE* шbumfܜJ{nslf,68z,vqOַZ %GdUus.[0j;5G' ժ?\`5~D6-ҟ%pk4,v{,.ffC\06jYW ;2د5֩rVͣK'5A Ru/64K·Yx& D# Z,Z;})ʪj,ۊ\Uedm5Z-8muA"k~&J }ƅ)b{)x@|"!B!H;m{3L}f'?Iɟ%? ?:jw{N+>ahO}Өz? 3_ʳLR^UUՅdeCB鹐}=]p~b-kVwPoSS3.f&$v`YZ.󀾼ۚ{lE`"6Vmp]Y3 jqtH<uZas nꚫe5Gn]vx{8Z"F}OuGxTQk{wxZW)?"ӕ5tfbL\l.DZʣhn>B!"#E>*Dy{ysHӽOφYpAVBbi]͞z(;c jɱcpwĭ}y*Ҧ9uÓg ymv3Қ-WAႬV<υ]ݐlxHfu0D#osK6gC,qTwNYb11nkjmAkvROk3sdgDT/hc7VwmZXYMz K{|MD"+{4G&€^)QH'5kEȐTn*juýB-ط#>RYrY(-ZEnzscQS Uofz`edh}f ժanW Y[3їQ%'pw311wI6c !B!."؉Nb-_4[n!PzL&㱏},O}SO{\gCV #酪^ǦB {|26[; kvz ^v&,-/R.M{,?X0\ؔvna@kbb06Zʰn5V\r(p.ng FG{59j }FuY0دܙʝu]޿x̵6['_͛s۶)Qkkݻ͹km|lW-d<E+M^w"ؿ}o,kECi{wW)ZwE=/o;t4T\yTvK,V6nb,fq9$V I{߲sv˲w fC-pgZ{ѱ'6ҕJl>z|Di ;DG'ju.̳l{}+nW40*OjFӽ kF5 !+k]+G=B<_I ;HXfuhFܗXv3< K-ֺndcn!dai}0:LBXTr4XXZ^oG՚eh@s@{-CXdEB__hYzvK!Bq⢓IkاR<я;s_뮻XZZn?f H3F"G,G:H$A* J%( kZ-Ϩ QvÍ <-<1JhE*r>|v*!aḅ/z?!ͱQ啐C<_I)ž}GB!BH`'.:ߵ2n>\s \s kƽw׾5q4 a@TTZ#whQJNa߾<`ٹ v&Ocq*ZlYZv` B qv3̬]߲bz.UDX E춒* \j2X+jиn.0-Wmy2)傿-l5B=t\@4,#îtn>d*\Oz>ӝѨ JzxMK+Ղ!jV۲^֝&ق.4F\^q[dra>ߙR7a˽7vۨ;ЯI]llH&5ш;GaI$}9w"ҌZ9 qu;c ǎ!B!.:؉KN4a{CWԧ(0;3qn.9 rrLբjQ6]KkM*;C.!M͍0:+ص ã;1bҲٲniaI&WY5;o݂n pN%V&]O 6VƵkvY s.9y<՚kl.,UŢg eiMtfP ̦0Scbso|1_..*+"7خwƍNꛙ {km?V\bEui=I>I+nDu7,hvƄ7ven5Chz[wy;'<  i6-LHn.Hh@waЗwۏ ணqRqqرcXf)B!$؉KV`سlky??rLTvZ M)qŁ}}i;tvf}7WX6)W3ʔl_.dBQ' wUgZ50dBynyk\qtck}ڲgߛ&euA]lYRD#f k4N舡X6JRF=b\NlMŒݴbhPS,mɄU޹K*N 2 ‘cX MeUxLI-Ub.mg9D<:ZScQr +Occ+R08YZ6pSXdžjB8+Q kWP'\|[=Fժx==UF"jm3hywRys tblcrUd*^1nfnrٲZ4`!J / ь *Q]7./r=Obff,..@OB!‘\sssZ-2 } VieI5ZХѰJaeŵ 6TR3496LjFG4"ˮrΏxh嶃-՚[sn`1)Oqwf˒I+ (-k*Ƣ WTJr^m6K+YnoUʵnl !̤۪ Q5vIĉ![,h\٩lu)EtYOwRnqG7qmvL>5[Qw>cvU1YuK喎$085K26齇qaM>fV˅ڌATll6K&ܟ竫 oB! .1ao}||ߥ\.sN?d>yjʤXtfl%:ժtkL%5 |рa^T.=j{ynun\mI5{vz+WL̈́P152< Ѩ LVM50+VKcS9k}=]EZ,Z.7VumׂzPXėIUz'\Ze w&'u 6ju'u~^b];j:Ζ٘^_aqmƹwTRf k %fٹ% Zqh`yPp7vuOlD㊑!Mj,̻xc_G_N!)ŹNIRmŢvB!B .!Z>Wk_ַxwنv ZpQ\ZA2J֊-a`YY3+.LMaE궁6rn1NucaRkvMxbFmе *,Ѩ[ }_VpwJA2T\U3 i7텕]X)5\霸~R>ܪl>vjC}}&eZ}q!^.I9QEn1UWZj7k.\O5Wūu[qޝx G;ate{S!+!XZqhz+ݹ9S[Z^et޴Z!B!$:tbjjh4޽{I&=zB??r׿~z^$ ku/`:P4"T )juEee5ej:`hcxеR*8cr&.`)  * f𴫠kWߧؽB-wߑ!Fs=@rg:JծK.l2S,wÝNIoQ,CeNeމ^υM0ps.u7./T (֊.dݸC)IL2HDQ2ꍏz 9,Œ^$ZU[rxH,h(\`YlX1!. Badȣ\1Ģ/->wglGfm>B!b"]'wy'/zыַŇ?a"QĢQB:ktmS-kܾ>ȈGaH&AοذM 8:r|:$TJJNu tw!/R;wnu^ !B!.&Raw 9rbK7t_="+++,R٠dJmU0RM_NˡR7kts!鴫P+ !ay%O*F= Ӯiׄ&th3i4Rbn[k,.tNgj9zgb~ui-,-o>h7 ,][2X+MCAosny»]>˫e*(u-,,ӊk hbP,׽9xJCh,bbLjY|_ep@ӗ;97 s dRƚfIP!B!.*]BVZ9<%R,S3n3k&e1LDdi򊫦zn>h,Ͷ`ǔ6.#.hkah7Z;[E[-}GB >kt6j wBɜTA֝}lm*UtWuLoEEA &qNAN<{]8Z~gr±}zVuy[|p\n ϳsmܾ 㬅f`:y6eC쵢D1v;zUyA&qmOJ!M%Frk WUk{x \Y5mOl+B!v뮻h4Jڵ|Vه> SLy,TknZU%EZ23&*X2ꖥ吁>MZRIEs 6=谦rSӳ!߃)BuQaZYR)E:z+\1s5n, @ |؍m7s " [p.M!yN t WG[\q*ayB j %vq՜QP}QEeT ٴ=Ę< wsߑU|^/̋FUgA _˻wΤCf;?a$B!Bv=q*"nebٌ̅baɅ"%pK˫. =f4q{-;ƪ?|&҈" ]h:KwAkCקEuCfǝ4 {6::"B]h'k_M;wx9rmS3a/WL(V 3䳮%j5ⲛqb rMOkX+}YMj9z14/-RTJ%B!Bv]v/~??( \}O$ccc}Sj6-˫|NwZ< pKXG4Vw$tJQ*^.f4G=juCW/fB-ĸRG#[ ٻ#a&ULzX\RͲVDK-Îq{ʮ"otcv~l*sj* EuΘ-Bgח%t+r!dui}ZhřN)+W,Y Y3Ui z(P( fN"k᪃ oCIk2$b.YX )W,n^RlI,Sָ_ j+K+ax܅&>#kO6Z4uwްLφ|NhOʖ{|7BP.Y˦VXpyӧws'kO?o|Qf*$;1@;mat`}>R;Ssnl$x%CZF4ٌB\(""sxȕ>&!}y gU.Sy̅םmvNxnmVџ?=v& !B!ăvHD۳E:/. wŠ9CG떩ِD\$mw-W-vz.UMѨB YbZDL[dQo@%T\Jk7X2D"TovޝBqzJǧ EbOR[][HrRƻnلwrtl)MDTv[Q0kaݚMKi0ֵitҽ'W,WMvyY\1rӪxU4 @, W96ٻ {wT!B!ą$ yp](6R0`d30<衔k0>T-Ѵy[фPp|*$qS٬f X拴.+{b v6U]Mxw63ωntZOQ@O9*@f,gb!W$⚻ C,.Dvi-IT=;Uڐ4[σDE+wD2 !B!I`'.F29N)z!ꚛs,R]236jmXY3'iƸyWTj U v[{\ aj(4)98mcEؙ0t׉h7uXgGǩ.qqÉw;hޫO궳]]2@ޏAM*V]VReW߃~73݆XU`R\=fnP,ػۣR6JZr( M86if\(޵Ń{Mmܚ#!B!.R؉sX2 }Ĩos2xJNTˆ\f[IXT1:)W J)%ZeK+E5R4Z+w\jdR[* V"k^֊0tՀ(jE>.RCfL6}rom,RUt 6'> [}[?NV,MUtcEw +">,,E..UpnaiXZvOi7߱X% %PI/0Q/iA3!(z絭r]:B*^!B!؉s&VӆV%sյ jqE2B`ltJJD"nLHжd2jhZhZvNxavCUk Viш\qM2+KP\ ~Zu)rŞUqʺnP1;UىW ;9;9£NjXϭ*N lgwB uͩg]Lgb[]t=4gߢSͨ;qƩڋO׊k;/ʛfmoPϸ՚.YuʲZY K*km=6ɤ53yQOnEhgcعًtr[}>&ݖv`ɤ J2iE:%3@:룵d23[!B`"ѴP\18iH$raDw@*ruq9$Vx>Z"C 36݆Oqlj}\ edHfz-'Vuzx[Nni‰Uf60.XYvsMsPY 6vݬ{b* !f7oi}<Ʉ&RR1*ײ;)tRQY X+XLc96lXWl7dӊAbMbN}BخF-&esb6zʺZ$h((B!vsDrŅ^ 2i$,VĢn&֮oiy01 )-4.}ȦazDQPT*O+4P@iEB>ZA byUMhuC0Vnӭtna=}6m[<Qn\.19JjYC~ВMyJ@yyݰ,*%w]ccTBS(* p|*X6x" 4&ɍYV ߙ ZN)V &h6NMMD%V!Bv⼲ hL  b$R ϳd3b <26ـ{}"hh cpNvR7~yBlo}[|}(ykxK^B6cWJr @, ;!B!&؉P4L͆hqUX-K n6W gOz$B!HRf) * Ch4T2Q}>CB)뾀 HHa1Fa Lyd25`Lȇ>e|mo{w 넸EQ~׼5,..B!b#؈fDŽV.;S!3s! 8r,CkEڇXL* b x$nhPSZeCr_Ņ;T<&ϟAq ԧ>Ç355':>|5v !B!.62Zl)Wxȱ\y_UC͖%Uxʵݹ^lZh]RJ(rrW֯ߑBk!܍\s͹|Bmzvv |!B!F;qJZ)">n=kdFF2;* /U\uU~ŒG>䳊%}TR2`Ȥ}>T{kA+`FQ]; ÐY0<?xB!b#%h}FqޞK,\RZ]m_l)-,Q$ 7/wtbQʚa`0FSo@X4+!{w{ k ՚R5,%K|їJ%g#B<~7~}2++|8HH`'B!8vy^pر"],\}BX!B 8TR1>Q[uK>6*~DA:I$БXT1-o."85>K`oۀk~'B!B\$ ]رcٟYoG&![-V wzh(ˆAuMLHbYXkM&,[Y3J*M{Ε5C*nF^*OjBKC-8x6F!Bq1NTnChIar:/ٳtCe),͖E)7nh@J*f a~X +&1Zuz"!ĥV|]vsm>B!b%x d3H:AwTHjV \NsBS,YC-7O!.G-ku7!B! $X_^JKBȎqn}ط褫KڃـxL3>+W ]kK߅ٌvؖ{~m BZ׿u }mB!1 Y !DӠ5uŲX*RIGm9R(4=<\[meY-2) OwsRE`'Tj5nƸ+DB!B_f9|,P2Zpߑ%Cii7دٳTokn9t$/-ٌ&rUszzJA Aɸ~HDQ(vm|Gpq^*v}No8}s'?yN-B!NQ,Ȥ`QWBV +}Bj <ÞՆVP,R5h&C=;=RIM_amͰ۳#PKzc:+#[o%R<)O!9[s2z׻Y^^O}*d>B!‘NCa),wh˫~>5 Д밼*DܵNϺ8 qvabe(-锦54[S!}ZͲ!aR!_֒Ny^v(uZvB!B\$' Ϳ6MKah<Men= wUxKn֜q"]k<سbYX2K4㣚JղlXXiMI.8pa0&wZJwG>+Gk!B!ĥD;10jpVwUmcA`FJA2H& ޴h w4֤R t@b5Ah96]bOSZrYeҊvT ŢV>`;!?n`||\.ysX^^t{<ؿZ% 4f]#-ÚhĒN*|-(W ak@1Tv}ːN+Ih!"^!E>1Zh>AcdCB!<JhZ|_.PS[*KR"blCunxy d <{{.CZnkuTkpO3;g}c'MNN/|suםkp |ѣe3??ρx@<\{9B!vFrx@r؛c׭D1KhVA):c-^(xLUK,bDe~1R ie˽Gϡ՚݆ə\+CD#Y{BqkY}#ɞ={ٵo!B!ĥI;Ai9>)zwޯu(DݯV։|G9IٌbNXT!} kXPRHA'TkRٲZ0XK9RxB+v[o~yΝ;m>B!R#vhRIE$°}ݰ1V)f4ǧÓ+\(rXZ-K&Dְ$Ѱ76_(W :B\>0;; |DB!BKvc,:ۺmǘ?ZV<`3iE:e4(hؿWͯJT-h-TUcccz.T>NqqjݦeJB!BO j}TZ +Kbi6v^{=Nm,fa)DkW=jYځ%֤>er:$V,MzU|}U)B\sm?뮻nO$B!I` txV];|i!`umrmRu~_䲊Jպ֪eyli- Rx9R_'Jc//i6(x+_I4c !B!.A] %ʊaNi+,ƪ3^Œar&$U4N3H(&F>,CCj -ٴX6:xٴbdHlv  cf퓫psX(;8O{ӶDB!BKvյbRqwzZ0͢dUC>\{iVpXH6Tv-6XUqnđ!t3kdBQ"NqqַrR__"nB!( ΡB ^>|Ao~~[n{ny tn/)wo9n~fط#p?tP /%rw`dYELHiYXrCx"b6!SJ !..?ԧ>/~DB!BKv ǎY=)4(mi-D\l^]_^ӗِ{ֿ>/e4k 7nv1ddXf) "C4 ϼVak-y{E oB!2 Γh4(t[. vʸA@BkՂ<MR@3h(W-"|ECa#Vw (F]hq׊(q7.40QX Z[I)vwя~|>cO$B!I`w8pF<\1>"'̀Ӛ^lt)E$eaP.M֚p5-K,ؽ#W,D#.sbذ\HjI&5jCiB\Ԭh򗿜={lB!< Γh4ʮ]}SS[oUE0зtxcq)bf5jX֓4Wd3>E_U&:|s  kQElI%Z!ӊ0t(uB޷-B!Bs=d3;UˑcÃQ6 aa)d`4GD|̆kki!Wc@TB{Kv*%-nuBB!r!yRVT*m9fgg~F]g-+P)C{V(̦BѲbWPلb IJl &,%P)gZ{h\VWWy׻~N1Eoo+܆ӊ x[ߊ1ozӛS!BqH`w:tG=Q}% Xamm ss(g-[~[ŢP.=au&<}ĥkqq?vm?v͇>!~Kg?Y/p|{$B!H`wXkiZ}.uVM̈́P4R&Ogg7q{w׼O|رO4S !B!.'؝Cxxԧ( hy#A6gY^^?!?G=n} EʚaG,ŠZSd3)ښi_Y,#`P(lY}o~??}{Tx1ɟ wux3xֳͧB!B\n$;8o~xWU,k/Y> ~n{ă}=}/|~{%B!lm{`{{w,%}y ūP(txꪫNhczΛ&QJg>},!B!eL `hPӗT%bDe-VlmUwO|ꫯ//DB!BEtϪZ__y;yt %SĢꌁc[F!Y&W]uԧ6o~;oq:t[nۯrFq]wR)w4/Q!B!ε&kyғt;v켟rU[>ߧ90이\BkX+UA)OyI|W~W?ͮ]6lyy__>iRn鼟W\* {z/y3B!B<HՃԜZd2 !+,%v"lorۿ{|e/{_O O|/đe(>կ~pPB!BN%S$øFrl* U%ۨh̦ۭ~ ͢RP\ /|!{.Աey[J"߿%B!xAdwni02Nmá`uu?8}&KKK 7&6O*lj5R 266֫/}):ϑNy_Ϋ_ꓶc( LNNH$صkD:qh6 _Z8nhB!B .#KˆŐ1 ֊ZJ(~4A}O||+_auuV@$aݼ/1y o|~w+_ {/A>{< u]G*=???q>qjl6ˍ7ȋ_b'ozxpkVΝ;y;^B!B<I`wўEkFN?cݢhD+ ,6b _C|~+._R??Ϙ>KKKq3??}n&]vG=C1==<ٱc#oz_zn^ G21_}@^ڪ\24;;//~ˠlVuڰ#DQ«^*Jﻺ__w]xg|;v׼59r/yKxk^]B!BkxUǝ\D"S㬅V1oS'Q.y^Ƨ?3usbXw|_~*oxĥmee׽u'>$NoɄB!f\UW][-tc`i%$<}4[b!WZFS\q+_ʏtG=Q(;xի^՛Uwz뭼}6j-x>FFFhB!BK.{cc>ET1_$Ģ O݆zRXMi?nWJqUW<14 wQ眙LV  w\kԶVVźju-Z?XZE bU\Aİٓlsh,$ș9H#|oeffSX8~]k>Ogu;OSNW\@ fΜק}ۨ}IU4Kc). ]Jvvv\CCns;qT[[]mۭZlֽޫo%6mfϞMX -m`g۶-[˗b۶bN6n 4*~zW_}UiիWO?]'Nlu۶׿M7]yy9AN73evKX7geff<hWv_~.a]6%1Ô^CY I{}Z\]xz@jwqZlYk_|͙3͆UUU6mZ;͙3G{5-[t7+ \D"뮻4l0 }>e~_GuT!ҥK5fT֭kՉmt~HM7r+M7DXK KCqU^.Imo5 ٶy{j#4}]{<'n{+B|>r-:3T]vyt$ѪUv#8B3g]wݥ㪫ۭm'ϧ;CEEE{]C,e]{Ldn}T!Ca'X'Vd;ctjM;Zp^w 뮻:Ų,vm:3Hui„ Zt$Х^`tn۶m7n\+|p8sO>˴roN:$=*++۫:*=ܣ /Pes:._^~Zgyf+%p8W^y%eue!gN8{Ciݺ:Jկtg ^~e-\P}m&Iֈ#4n8viz衇vעEouX\\ & Ad۶~iM:U^z`t@ד_׺kd-YD_LO{lG?j $)33SG֜9sTXXj8"y\ٳu 7h͚5r]Wvz)͘1@E` >6JJJ4y]s9褓Njs>S$Z+VP(ׅ^ Of:F]7|p]5jԨ6UWWׁ" 4HzHtPNvRuvp;m!ɤI_ ?Hϧ6{=][o^XXȾf݈a0aa;)cz]]Νn^{kGyd{t%,RFR~~~͛\OK^}}.\3f( 1c$l: ۷N?t=ӭb1]zGuGiz9{F4vR*nӻᆱk׶yB}FiiONa;t7C>_^7ե *++O<7??_ӧOg |tءC\+ )''?^ ,ɓn2M:UW^yL?;t#<1%8Sxb͝;W?jjjڜk 5b͜9SF"t+vJyy?k{O6lP<Wii=Xxkuo' R_ƍ$egg=x\uuuXU B B.]@J .@~ϮK).. 7ܐ:DMMb TQQn߿s]q:9I$;.5]ed ڛ\jn^IFsU$MkH 榚ZW+PsܕeJŅLKyHT G\q)*'4U]*jch$Jk[,!v^W^;eggkٝ_#2 UV;[f*'$Ƕr]KkȲmQ8H+GTQ)sc7m庮zuYbqu#PS2TYM^]]owAk(#Õij[YC}, HL3j]K>CxnF[^XU4*qkY f:ڴQ$*+\&n{dY;WμƐ#۶ZuH" $#ZնJ[ C+‘DXrkp[؎v\*7 d5XRܖjY ŚY ';bQWM!W9٦vx\".4t`_K~HT KfK{Zڸٖ*7T^mvЭȒa&6\Bk&s@7BVCՆͶqW~֝w\diDخr wlr2uE接uVk[۫UU;*7գؒe%= a*3M֞@ r 2-)#Ðk\)mKD^ `g#eH!mȗXkR((hviD=}z[*)2 X34%e(v 5ͶTT`:?C2MShb\M#񾪈%2d<C8,K4:ygf&»lEcr2Id$P("}&.M<'nK+ItUKF"-6b@E`8aW*+ӔJfW#3*a* 5ǥ|CyE#BD+9;6ċRy)iI=K,mft;+vU(?הϗty Y7cl5r ®9(!ڸ'4v`CncP:CzG{J=- Q$1訡ѕmK^쪨TaUqk.@WE`v54ںѶ펼J-qP80{dI>!#曪u'NݼVPS渫Ẁz8Ҫ-{ە2lK[:*7 %N lŚmW~ǼCZsBm'[VF% #d6/WrqKجLCxd},5Z2M9]a lI0vssLg%c!mƠxU)qoYZ^!L|յʼnNǵubRPYoKːXߐϧ/~Cy됃[JEco8"*??qDMX,, ]ϣ:[[;2 ia^mrIAWk-Tt'vT#E"1i S)ǑaI2ToȕqDYi[5 ?qZr΅!ɕa26tyvgј+ۖdjԣTn)ώLS:2v|CY֬ƑeJiv m14YCa [X"@k ?teYRohfGu  :`sڑ T,*n'RbSյ"QW+]efP^)4IJ"Ci(7MgeZd'ee= 9.U8ySu WYz2 ВJŅLSu K` 0E D8*ȓ ڎXXUa)˒rL;OuUR I[9 mfKJ,DO۶'`UUZ*zZ29Иn@\CYY’+WO+ʖn>yqW}$JLZ@C:#t8,뛴uG67Է,iwJ~0Ы,mWڱ# :В?ÐiZіHMN{Etiv0E=,R渫`Е )NtmUSʶ]2!\}U\}XJ4F"mkX, 9a CRm}b9l#-K:Y$]=o<#z'A8;|SYekeRqQZ,j^tY2@TN_uOoM0gRs\j :4MS۶mS(R(ڇw `o7%@A`f WO*.Jted$UV;Vqz[+I\]F+IUնnwz֞={K/IQPP)Sx<މ@#C دm'LIgYFr`fWYYHURK8BlG*umq 8nڴijй瞫s=w_"N溮6n|*sn,Cz&½3]Y)3`Ȕ+U2x&WјXL*)2ջTn~_|~b db/^,I~W[{ngg9)(1Uo`畚BR]ƠhL7K?B=K{K{9})zW:K/>H4rH"H>;$eI{ԻYו6oU*'ԁ-ed2MKoK=Mm5IjM2Ex[:ٳg+ K.Qvvv#C IJVÕ]54opHM!WlszM\w;HI[oSO=U/h Q__^&L,I:묳4vWvH !$fWq;w]2K6:Ȑ"1QVޖm i͚:fZ 1}:묳4tPv*((PvvVթI}>@@#FPeeen M |49%Gzm\M!WCM!W^kr 54,i@yTW^yϟ .;;['OܹsY {IѦ-ML,&7**QMlCј+PC#ǕLCr'?L;,cǎգ>޽{H ;$U}PQbK֎s'GZVC=jNR,C.@UUU<]ve7,ҰaÔܓ5c =#rGij@`SW*t?ի%%N|'4~jjhhH@:իy1"q͛7OfR($z衚? Zb0 5\WiSO-wKXWZZ~Zg}vkZ|fΜIXHڲeMxtUWnSNN$>ӹ瞫%K$lt!mضuw鮻Tcc$?#<25]Vgy>sIu衇& |O$%|衇t$_]&Mƍ%IŚ7o&L sW@B`OfѨ|>FG}T=zHzMUUU:묳tRIҨQ`% XB?O0 M8QSaaakY~.rKQԯ~+!-|*M b9\]wu+8`bҤIz$IGq-Z>}$ ,]T'NTEE,+3334447|Aqs9GSyyybNqh:uR>}w.uy_$W ,РA^ n4h jllԊ+TWW1cȲ֒1c(??_bV\^zI#GT޽Zt;L(]wݥoeÇGѸqÿ$ٶ.H-gÓ^ =z7T$/4u'&}945rHuQ-!buu.\~q,.) tw[oUuu$iСzꩧ4tP.͛7O{9眓Zte>`566꣏>Rss>SCMo~4x`XB[nU(믿bq)Im6]uUgISO>Ox<.ϧYfi)HgG#GaZd.]C9$%MԠA4f[N_~^~eb1}F CR}gR,Snnj]~*.. `?$fƍ9sx 9Lwy;Sz$Դ/%>s*))IMMU($O[ћo,P4ֶmTS]Gכ{BR<*?;튋5f_^+VP4;Ch4N8A^c'D`NkZhQaanf7IZ(+`P]vIJJJ/ꠃJMMV|*N@wңG{>p6lXp8# KѨugu]-[LXLK.Պ+4btСlւ {{'?3ֻX,3f$/F]}U$*Tԧl-"V^./C 9F\vv&Wy^|ի*hʕztQG|>D`Dtm*++e;8=:餓R~*8_u]r]Wzg4z__j*WhRR0 3H=)/ :Rv^hg ?^oР{N:thJFӦMw-۶HZbѰ/[;C݇btkʻ\gq/_+W^Q}}N>dYn:aUTT?-Z$I̙3um XL>~_*o%\W9ZZ7'e: mXT$ܨ+?P4{]Q~hPm\C>_wjr]WK.U<ҥKgSNQVVV {e:3[z\).aŚ:ud.͙3')Kt+}]ʉOOC=V}D[ug)/x`\C^otI2 CK,mZr>c9REEE>'|u9{϶m KUUU%I*--s=uYgiƍC=T~@8b ( u\@WPW]sj$I}y=?)|lz{g4e$j,++ӟ'z꩝7){-馛nM7$۶%IGq^xo8ѣGkũ.@;ϟꪫ,Y.HWXkʔ)7)ۉ.VΝ֝z*`0T`֯[W!:L{^u5JԤӧkڴi Ý6/|vcV҄ ZnA ҥKk)w\# Ǔ2Ս}|Ҡ tސ`PwqGK'a\%%%6/Hvjź袋i&I\rJK ns:,ԯ^9kʔ)N:t?vحX,'|RӧOW0$vaz4tW+\UdZҁ ٹ0i۶m$ϧ/:;u^_a]ѵ^3f( 0 ;V'ۡYZU at-YlLG֋/KJ"cĉ[DUQQkV-R4Ӆ^[nvD5jk$1-\*ݹڰaL^xAzu{Q Թ|ءw}W3gԲed۶rssuu+PVVVK uZ: Է\:)WA{z3gb И1c?Y71;RQQ'Zl$7߬cRez/ }TRb(/sm[ ,Д)STSS#I;vyNZ3gΝ+qt'j޼y:#dtf tA|9mڴIi귿T Fz84w\y䑄uЎ:Jw$q-i Cp8FIR~4lذWN$m޼Yx<ТY͒P(P( NZ C )W_[nQCCCT[[_-]uCxR]҇a-qZd #?s-_eY+@B ]Zl-[R7Ю2jݺum;@ЀnݺV{"CW_}U+WTeeeSRR!CTZE`  UVVꨣJqU>CKz:Ģө*Iڶmn喖555i֬Y-=ZYYY)@wa릺 {ھ},ĉu5רG Jȳyf]Zh$iРAZlS[nm%\H$")z ~zb1IRaay) @wA`6c=S)@*))ѳ>QFviʕz[o*@(--ոqӟX(v@X iH#v@!F4B`; iH#v@!Fva1:$#F<.F,ؗat} $:ΰϝvtkN%c;Ϟehr]; IENDB`mozilla-vpn-client-2.2.0/macos/pkg/Resources/conclusion.html000066400000000000000000000014601404202232700241330ustar00rootroot00000000000000