diff --git a/.editorconfig b/.editorconfig index ad0694f..ffe8a6b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,13 +1,15 @@ -# top-most EditorConfig file root = true -# Unix-style newlines with a newline ending every file [*] -end_of_line = lf -insert_final_newline = false -trim_trailing_whitespace=true +charset = utf-8 indent_style = space +indent_size = 2 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +[*.{rs,toml}] indent_size = 4 -[*.sh] -indent_style = tab +[*.{c,h}] +indent_size = 4 diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 75c8f61..0000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,2 +0,0 @@ -ko_fi: yuezk -custom: ["https://buymeacoffee.com/yuezk", "https://paypal.me/zongkun"] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 78d0262..0000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,275 +0,0 @@ -name: Build - -on: - push: - branches: - - master - - develop - tags: - - "v*.*.*" - paths-ignore: - - LICENSE - - "*.md" - - .vscode - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - build: - strategy: - matrix: - os: [ubuntu-18.04, ubuntu-20.04, ubuntu-22.04] - - runs-on: ${{ matrix.os }} - - steps: - # Checkout repository and submodules - - uses: actions/checkout@v2 - with: - submodules: recursive - - - name: Build - run: | - ./scripts/install-ubuntu.sh - # assert no library missing - test $(ldd $(which gpclient) | grep 'not found' | wc -l) -eq 0 - - snapshot-archive-all: - if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} - needs: build - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - fetch-depth: 0 - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install git-archive-all - - - name: Archive all - run: | - ./scripts/snapshot-archive-all.sh - - - name: Verify debian package - run: | - ./scripts/verify-debian-package.sh - - - uses: actions/upload-artifact@v2 - with: - name: snapshot-source-code - path: ./artifacts/* - - snapshot-ppa: - if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} - needs: snapshot-archive-all - runs-on: ubuntu-latest - - steps: - - uses: actions/download-artifact@v2 - with: - name: snapshot-source-code - path: artifacts - - - name: Extract source code - run: | - cd $GITHUB_WORKSPACE/artifacts - mkdir deb-build && cp *.tar.gz deb-build && cd deb-build - tar xf *.tar.gz - - - name: Publish PPA - uses: yuezk/publish-ppa-package@develop - with: - repository: 'ppa:yuezk/globalprotect-openconnect-snapshot' - gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }} - gpg_passphrase: ${{ secrets.PPA_GPG_PASSPHRASE }} - pkgdir: '${{ github.workspace }}/artifacts/deb-build/globalprotect-openconnect*/' - - snapshot-aur: - if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} - needs: snapshot-archive-all - runs-on: ubuntu-latest - - steps: - - uses: actions/download-artifact@v2 - with: - name: snapshot-source-code - path: artifacts - - - name: Publish AUR package - uses: yuezk/github-actions-deploy-aur@update-pkgver - with: - pkgname: globalprotect-openconnect-git - pkgbuild: ./artifacts/aur/PKGBUILD - assets: ./artifacts/aur/gp.install - update_pkgver: true - commit_username: ${{ secrets.AUR_USERNAME }} - commit_email: ${{ secrets.AUR_EMAIL }} - ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} - commit_message: 'Snapshot release: git#${{ github.sha }}' - - snapshot-obs: - if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} - needs: snapshot-archive-all - runs-on: ubuntu-latest - - steps: - - uses: actions/download-artifact@v2 - with: - name: snapshot-source-code - path: artifacts - - - uses: yuezk/publish-obs-package@main - with: - project: home:yuezk - package: globalprotect-openconnect-snapshot - username: yuezk - password: ${{ secrets.OBS_PASSWORD }} - files: ./artifacts/obs/* - - snapshot-snap: - # if: ${{ github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' }} - if: ${{ false }} - needs: snapshot-archive-all - runs-on: ubuntu-latest - - steps: - - uses: actions/download-artifact@v2 - with: - name: snapshot-source-code - path: artifacts - - - name: Extract source code - run: | - mkdir snap-source - tar xvf ./artifacts/globalprotect-openconnect-*tar.gz \ - --directory snap-source \ - --strip 1 - - - uses: snapcore/action-build@v1 - id: build - with: - path: ./snap-source - - - uses: snapcore/action-publish@v1 - with: - store_login: ${{ secrets.SNAPSTORE_LOGIN }} - snap: ${{ steps.build.outputs.snap }} - release: edge - - release-archive-all: - if: startsWith(github.ref, 'refs/tags/v') - needs: build - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - with: - submodules: recursive - fetch-depth: 0 - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install git-archive-all - - - name: Archive all - run: | - ./scripts/release-archive-all.sh - - - name: Verify debian package - run: | - ./scripts/verify-debian-package.sh - - - uses: actions/upload-artifact@v2 - with: - name: release-source-code - path: ./artifacts/* - - release-ppa: - if: startsWith(github.ref, 'refs/tags/v') - needs: release-archive-all - runs-on: ubuntu-latest - - steps: - - uses: actions/download-artifact@v2 - with: - name: release-source-code - path: artifacts - - - name: Extract source code - run: | - cd $GITHUB_WORKSPACE/artifacts - mkdir deb-build && cp *.tar.gz deb-build && cd deb-build - tar xf *.tar.gz - - - name: Publish PPA - uses: yuezk/publish-ppa-package@develop - with: - repository: 'ppa:yuezk/globalprotect-openconnect' - gpg_private_key: ${{ secrets.PPA_GPG_PRIVATE_KEY }} - gpg_passphrase: ${{ secrets.PPA_GPG_PASSPHRASE }} - pkgdir: '${{ github.workspace }}/artifacts/deb-build/globalprotect-openconnect*/' - - release-aur: - if: startsWith(github.ref, 'refs/tags/v') - needs: release-archive-all - runs-on: ubuntu-latest - - steps: - - uses: actions/download-artifact@v2 - with: - name: release-source-code - path: artifacts - - - name: Publish AUR package - uses: yuezk/github-actions-deploy-aur@update-pkgver - with: - pkgname: globalprotect-openconnect-git - pkgbuild: ./artifacts/aur/PKGBUILD - assets: ./artifacts/aur/gp.install - update_pkgver: true - commit_username: ${{ secrets.AUR_USERNAME }} - commit_email: ${{ secrets.AUR_EMAIL }} - ssh_private_key: ${{ secrets.AUR_SSH_PRIVATE_KEY }} - commit_message: 'Release ${{ github.ref }}' - - release-obs: - if: startsWith(github.ref, 'refs/tags/v') - needs: release-archive-all - runs-on: ubuntu-latest - - steps: - - uses: actions/download-artifact@v2 - with: - name: release-source-code - path: artifacts - - - uses: yuezk/publish-obs-package@main - with: - project: home:yuezk - package: globalprotect-openconnect - username: yuezk - password: ${{ secrets.OBS_PASSWORD }} - files: ./artifacts/obs/* - - release-github: - if: startsWith(github.ref, 'refs/tags/v') - needs: - - release-ppa - - release-aur - - release-obs - runs-on: ubuntu-latest - - steps: - - uses: actions/download-artifact@v2 - with: - name: release-source-code - path: artifacts - - uses: softprops/action-gh-release@v1 - with: - files: | - ./artifacts/*.tar.gz diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml deleted file mode 100644 index 3a2c2d6..0000000 --- a/.github/workflows/pr.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: PR Build - -on: - pull_request: - branches: - - master - - develop - paths-ignore: - - LICENSE - - "*.md" - - .vscode - workflow_dispatch: - -# A workflow run is made up of one or more jobs that can run sequentially or in parallel -jobs: - build: - strategy: - matrix: - os: [ubuntu-18.04, ubuntu-20.04] - - runs-on: ${{ matrix.os }} - - steps: - # Checkout repository and submodules - - uses: actions/checkout@v2 - with: - submodules: recursive - - - name: Build - run: | - ./scripts/install-ubuntu.sh - # assert no library missing - test $(ldd $(which gpclient) | grep 'not found' | wc -l) -eq 0 diff --git a/.gitignore b/.gitignore index df170ab..ff8b7cd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,70 +1,35 @@ -# Binaries -*.rpm -*.gz -*.snap -.DS_Store -build-debian -build -artifacts +# Created by https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=rust,visualstudiocode -.cmake -.idea +### Rust ### +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ -# Auto generated DBus files -*_adaptor.cpp -*_adaptor.h +# These are backup files generated by rustfmt +**/*.rs.bk -gpservice_interface.* +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb -# C++ objects and libs -*.slo -*.lo -*.o -*.a -*.la -*.lai -*.so -*.so.* -*.dll -*.dylib +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +!.vscode/*.code-snippets -# Qt-es -object_script.*.Release -object_script.*.Debug -*_plugin_import.cpp -/.qmake.cache -/.qmake.stash -*.pro.user -*.pro.user.* -*.qbs.user -*.qbs.user.* -*.moc -moc_*.cpp -moc_*.h -qrc_*.cpp -ui_*.h -*.qmlc -*.jsc -Makefile* -*build-* -*.qm -*.prl +# Local History for Visual Studio Code +.history/ -# Qt unit tests -target_wrapper.* +# Built Visual Studio Code Extensions +*.vsix -# QtCreator -*.autosave +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide -# QtCreator Qml -*.qmlproject.user -*.qmlproject.user.* - -# QtCreator CMake -CMakeLists.txt.user* - -# QtCreator 4.8< compilation database -compile_commands.json - -# QtCreator local machine specific files for imported projects -*creator.user* +# End of https://www.toptal.com/developers/gitignore/api/rust,visualstudiocode diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index 706e889..0000000 --- a/.gitmodules +++ /dev/null @@ -1,10 +0,0 @@ -[submodule "singleapplication"] - path = 3rdparty/SingleApplication - url = https://github.com/itay-grudev/SingleApplication.git - -[submodule "plog"] - path = 3rdparty/plog - url = https://github.com/SergiusTheBest/plog.git -[submodule "3rdparty/qtkeychain"] - path = 3rdparty/qtkeychain - url = git@github.com:frankosterfeld/qtkeychain.git diff --git a/.vscode/settings.json b/.vscode/settings.json index 94d245a..b16b12d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,26 +1,25 @@ { - "files.watcherExclude": { - "**/artifacts/**": true, - }, - "files.associations": { - "qregularexpression": "cpp", - "qfileinfo": "cpp", - "qregularexpressionmatch": "cpp", - "qdatetime": "cpp", - "qprocess": "cpp", - "qobject": "cpp", - "qstandardpaths": "cpp", - "qmainwindow": "cpp", - "qsystemtrayicon": "cpp", - "qpushbutton": "cpp", - "qmenu": "cpp", - "qjsondocument": "cpp", - "qnetworkaccessmanager": "cpp", - "qwebengineview": "cpp", - "qprocessenvironment": "cpp", - "qnetworkreply": "cpp", - "qicon": "cpp", - "qsslsocket": "cpp", - "qapplication": "cpp" - } + "cSpell.words": [ + "bindgen", + "clientos", + "jnlp", + "openconnect", + "prelogin", + "prelogon", + "prelogonuserauthcookie", + "tauri", + "userauthcookie", + "vpninfo" + ], + "files.associations": { + "*.css": "css", + "errno.h": "c", + "stdarg.h": "c", + "wrapper.h": "c", + "cstdlib": "c", + "stdio.h": "c", + "openconnect.h": "c", + "compare": "c", + "stdlib.h": "c" + } } diff --git a/3rdparty/SingleApplication b/3rdparty/SingleApplication deleted file mode 160000 index bdbb09b..0000000 --- a/3rdparty/SingleApplication +++ /dev/null @@ -1 +0,0 @@ -Subproject commit bdbb09b5f21ebea4cd7dfb43b29114a94e04a3a1 diff --git a/3rdparty/inih/CMakeLists.txt b/3rdparty/inih/CMakeLists.txt deleted file mode 100644 index b4faa58..0000000 --- a/3rdparty/inih/CMakeLists.txt +++ /dev/null @@ -1,12 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0) - -set(CMAKE_CXX_STANDARD 17) -project(inih) - -add_library(inih STATIC - ini.h - ini.c - cpp/INIReader.h - cpp/INIReader.cpp -) -target_include_directories(inih PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/cpp") diff --git a/3rdparty/inih/LICENSE.txt b/3rdparty/inih/LICENSE.txt deleted file mode 100644 index cb7ee2d..0000000 --- a/3rdparty/inih/LICENSE.txt +++ /dev/null @@ -1,27 +0,0 @@ - -The "inih" library is distributed under the New BSD license: - -Copyright (c) 2009, Ben Hoyt -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - * Neither the name of Ben Hoyt nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY BEN HOYT ''AS IS'' AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL BEN HOYT BE LIABLE FOR ANY -DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND -ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/3rdparty/inih/cpp/INIReader.cpp b/3rdparty/inih/cpp/INIReader.cpp deleted file mode 100644 index 1bdac40..0000000 --- a/3rdparty/inih/cpp/INIReader.cpp +++ /dev/null @@ -1,116 +0,0 @@ -// Read an INI file into easy-to-access name/value pairs. - -// SPDX-License-Identifier: BSD-3-Clause - -// Copyright (C) 2009-2020, Ben Hoyt - -// inih and INIReader are released under the New BSD license (see LICENSE.txt). -// Go to the project home page for more info: -// -// https://github.com/benhoyt/inih - -#include -#include -#include -#include "../ini.h" -#include "INIReader.h" - -using std::string; - -INIReader::INIReader(const string& filename) -{ - _error = ini_parse(filename.c_str(), ValueHandler, this); -} - -INIReader::INIReader(const char *buffer, size_t buffer_size) -{ - string content(buffer, buffer_size); - _error = ini_parse_string(content.c_str(), ValueHandler, this); -} - -int INIReader::ParseError() const -{ - return _error; -} - -string INIReader::Get(const string& section, const string& name, const string& default_value) const -{ - string key = MakeKey(section, name); - // Use _values.find() here instead of _values.at() to support pre C++11 compilers - return _values.count(key) ? _values.find(key)->second : default_value; -} - -string INIReader::GetString(const string& section, const string& name, const string& default_value) const -{ - const string str = Get(section, name, ""); - return str.empty() ? default_value : str; -} - -long INIReader::GetInteger(const string& section, const string& name, long default_value) const -{ - string valstr = Get(section, name, ""); - const char* value = valstr.c_str(); - char* end; - // This parses "1234" (decimal) and also "0x4D2" (hex) - long n = strtol(value, &end, 0); - return end > value ? n : default_value; -} - -double INIReader::GetReal(const string& section, const string& name, double default_value) const -{ - string valstr = Get(section, name, ""); - const char* value = valstr.c_str(); - char* end; - double n = strtod(value, &end); - return end > value ? n : default_value; -} - -bool INIReader::GetBoolean(const string& section, const string& name, bool default_value) const -{ - string valstr = Get(section, name, ""); - // Convert to lower case to make string comparisons case-insensitive - std::transform(valstr.begin(), valstr.end(), valstr.begin(), ::tolower); - if (valstr == "true" || valstr == "yes" || valstr == "on" || valstr == "1") - return true; - else if (valstr == "false" || valstr == "no" || valstr == "off" || valstr == "0") - return false; - else - return default_value; -} - -bool INIReader::HasSection(const string& section) const -{ - const string key = MakeKey(section, ""); - std::map::const_iterator pos = _values.lower_bound(key); - if (pos == _values.end()) - return false; - // Does the key at the lower_bound pos start with "section"? - return pos->first.compare(0, key.length(), key) == 0; -} - -bool INIReader::HasValue(const string& section, const string& name) const -{ - string key = MakeKey(section, name); - return _values.count(key); -} - -string INIReader::MakeKey(const string& section, const string& name) -{ - string key = section + "=" + name; - // Convert to lower case to make section/name lookups case-insensitive - std::transform(key.begin(), key.end(), key.begin(), ::tolower); - return key; -} - -int INIReader::ValueHandler(void* user, const char* section, const char* name, - const char* value) -{ - if (!name) // Happens when INI_CALL_HANDLER_ON_NEW_SECTION enabled - return 1; - INIReader* reader = static_cast(user); - string key = MakeKey(section, name); - if (reader->_values[key].size() > 0) - reader->_values[key] += "\n"; - reader->_values[key] += value ? value : ""; - return 1; -} diff --git a/3rdparty/inih/cpp/INIReader.h b/3rdparty/inih/cpp/INIReader.h deleted file mode 100644 index 1571756..0000000 --- a/3rdparty/inih/cpp/INIReader.h +++ /dev/null @@ -1,94 +0,0 @@ -// Read an INI file into easy-to-access name/value pairs. - -// SPDX-License-Identifier: BSD-3-Clause - -// Copyright (C) 2009-2020, Ben Hoyt - -// inih and INIReader are released under the New BSD license (see LICENSE.txt). -// Go to the project home page for more info: -// -// https://github.com/benhoyt/inih - -#ifndef INIREADER_H -#define INIREADER_H - -#include -#include - -// Visibility symbols, required for Windows DLLs -#ifndef INI_API -#if defined _WIN32 || defined __CYGWIN__ -# ifdef INI_SHARED_LIB -# ifdef INI_SHARED_LIB_BUILDING -# define INI_API __declspec(dllexport) -# else -# define INI_API __declspec(dllimport) -# endif -# else -# define INI_API -# endif -#else -# if defined(__GNUC__) && __GNUC__ >= 4 -# define INI_API __attribute__ ((visibility ("default"))) -# else -# define INI_API -# endif -#endif -#endif - -// Read an INI file into easy-to-access name/value pairs. (Note that I've gone -// for simplicity here rather than speed, but it should be pretty decent.) -class INIReader -{ -public: - // Construct INIReader and parse given filename. See ini.h for more info - // about the parsing. - INI_API explicit INIReader(const std::string& filename); - - // Construct INIReader and parse given buffer. See ini.h for more info - // about the parsing. - INI_API explicit INIReader(const char *buffer, size_t buffer_size); - - // Return the result of ini_parse(), i.e., 0 on success, line number of - // first error on parse error, or -1 on file open error. - INI_API int ParseError() const; - - // Get a string value from INI file, returning default_value if not found. - INI_API std::string Get(const std::string& section, const std::string& name, - const std::string& default_value) const; - - // Get a string value from INI file, returning default_value if not found, - // empty, or contains only whitespace. - INI_API std::string GetString(const std::string& section, const std::string& name, - const std::string& default_value) const; - - // Get an integer (long) value from INI file, returning default_value if - // not found or not a valid integer (decimal "1234", "-1234", or hex "0x4d2"). - INI_API long GetInteger(const std::string& section, const std::string& name, long default_value) const; - - // Get a real (floating point double) value from INI file, returning - // default_value if not found or not a valid floating point value - // according to strtod(). - INI_API double GetReal(const std::string& section, const std::string& name, double default_value) const; - - // Get a boolean value from INI file, returning default_value if not found or if - // not a valid true/false value. Valid true values are "true", "yes", "on", "1", - // and valid false values are "false", "no", "off", "0" (not case sensitive). - INI_API bool GetBoolean(const std::string& section, const std::string& name, bool default_value) const; - - // Return true if the given section exists (section must contain at least - // one name=value pair). - INI_API bool HasSection(const std::string& section) const; - - // Return true if a value exists with the given section and field names. - INI_API bool HasValue(const std::string& section, const std::string& name) const; - -private: - int _error; - std::map _values; - static std::string MakeKey(const std::string& section, const std::string& name); - static int ValueHandler(void* user, const char* section, const char* name, - const char* value); -}; - -#endif // INIREADER_H diff --git a/3rdparty/inih/ini.c b/3rdparty/inih/ini.c deleted file mode 100644 index f8a3ea3..0000000 --- a/3rdparty/inih/ini.c +++ /dev/null @@ -1,298 +0,0 @@ -/* inih -- simple .INI file parser - -SPDX-License-Identifier: BSD-3-Clause - -Copyright (C) 2009-2020, Ben Hoyt - -inih is released under the New BSD license (see LICENSE.txt). Go to the project -home page for more info: - -https://github.com/benhoyt/inih - -*/ - -#if defined(_MSC_VER) && !defined(_CRT_SECURE_NO_WARNINGS) -#define _CRT_SECURE_NO_WARNINGS -#endif - -#include -#include -#include - -#include "ini.h" - -#if !INI_USE_STACK -#if INI_CUSTOM_ALLOCATOR -#include -void* ini_malloc(size_t size); -void ini_free(void* ptr); -void* ini_realloc(void* ptr, size_t size); -#else -#include -#define ini_malloc malloc -#define ini_free free -#define ini_realloc realloc -#endif -#endif - -#define MAX_SECTION 50 -#define MAX_NAME 50 - -/* Used by ini_parse_string() to keep track of string parsing state. */ -typedef struct { - const char* ptr; - size_t num_left; -} ini_parse_string_ctx; - -/* Strip whitespace chars off end of given string, in place. Return s. */ -static char* rstrip(char* s) -{ - char* p = s + strlen(s); - while (p > s && isspace((unsigned char)(*--p))) - *p = '\0'; - return s; -} - -/* Return pointer to first non-whitespace char in given string. */ -static char* lskip(const char* s) -{ - while (*s && isspace((unsigned char)(*s))) - s++; - return (char*)s; -} - -/* Return pointer to first char (of chars) or inline comment in given string, - or pointer to NUL at end of string if neither found. Inline comment must - be prefixed by a whitespace character to register as a comment. */ -static char* find_chars_or_comment(const char* s, const char* chars) -{ -#if INI_ALLOW_INLINE_COMMENTS - int was_space = 0; - while (*s && (!chars || !strchr(chars, *s)) && - !(was_space && strchr(INI_INLINE_COMMENT_PREFIXES, *s))) { - was_space = isspace((unsigned char)(*s)); - s++; - } -#else - while (*s && (!chars || !strchr(chars, *s))) { - s++; - } -#endif - return (char*)s; -} - -/* Similar to strncpy, but ensures dest (size bytes) is - NUL-terminated, and doesn't pad with NULs. */ -static char* strncpy0(char* dest, const char* src, size_t size) -{ - /* Could use strncpy internally, but it causes gcc warnings (see issue #91) */ - size_t i; - for (i = 0; i < size - 1 && src[i]; i++) - dest[i] = src[i]; - dest[i] = '\0'; - return dest; -} - -/* See documentation in header file. */ -int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, - void* user) -{ - /* Uses a fair bit of stack (use heap instead if you need to) */ -#if INI_USE_STACK - char line[INI_MAX_LINE]; - int max_line = INI_MAX_LINE; -#else - char* line; - size_t max_line = INI_INITIAL_ALLOC; -#endif -#if INI_ALLOW_REALLOC && !INI_USE_STACK - char* new_line; - size_t offset; -#endif - char section[MAX_SECTION] = ""; - char prev_name[MAX_NAME] = ""; - - char* start; - char* end; - char* name; - char* value; - int lineno = 0; - int error = 0; - -#if !INI_USE_STACK - line = (char*)ini_malloc(INI_INITIAL_ALLOC); - if (!line) { - return -2; - } -#endif - -#if INI_HANDLER_LINENO -#define HANDLER(u, s, n, v) handler(u, s, n, v, lineno) -#else -#define HANDLER(u, s, n, v) handler(u, s, n, v) -#endif - - /* Scan through stream line by line */ - while (reader(line, (int)max_line, stream) != NULL) { -#if INI_ALLOW_REALLOC && !INI_USE_STACK - offset = strlen(line); - while (offset == max_line - 1 && line[offset - 1] != '\n') { - max_line *= 2; - if (max_line > INI_MAX_LINE) - max_line = INI_MAX_LINE; - new_line = ini_realloc(line, max_line); - if (!new_line) { - ini_free(line); - return -2; - } - line = new_line; - if (reader(line + offset, (int)(max_line - offset), stream) == NULL) - break; - if (max_line >= INI_MAX_LINE) - break; - offset += strlen(line + offset); - } -#endif - - lineno++; - - start = line; -#if INI_ALLOW_BOM - if (lineno == 1 && (unsigned char)start[0] == 0xEF && - (unsigned char)start[1] == 0xBB && - (unsigned char)start[2] == 0xBF) { - start += 3; - } -#endif - start = lskip(rstrip(start)); - - if (strchr(INI_START_COMMENT_PREFIXES, *start)) { - /* Start-of-line comment */ - } -#if INI_ALLOW_MULTILINE - else if (*prev_name && *start && start > line) { - /* Non-blank line with leading whitespace, treat as continuation - of previous name's value (as per Python configparser). */ - if (!HANDLER(user, section, prev_name, start) && !error) - error = lineno; - } -#endif - else if (*start == '[') { - /* A "[section]" line */ - end = find_chars_or_comment(start + 1, "]"); - if (*end == ']') { - *end = '\0'; - strncpy0(section, start + 1, sizeof(section)); - *prev_name = '\0'; -#if INI_CALL_HANDLER_ON_NEW_SECTION - if (!HANDLER(user, section, NULL, NULL) && !error) - error = lineno; -#endif - } - else if (!error) { - /* No ']' found on section line */ - error = lineno; - } - } - else if (*start) { - /* Not a comment, must be a name[=:]value pair */ - end = find_chars_or_comment(start, "=:"); - if (*end == '=' || *end == ':') { - *end = '\0'; - name = rstrip(start); - value = end + 1; -#if INI_ALLOW_INLINE_COMMENTS - end = find_chars_or_comment(value, NULL); - if (*end) - *end = '\0'; -#endif - value = lskip(value); - rstrip(value); - - /* Valid name[=:]value pair found, call handler */ - strncpy0(prev_name, name, sizeof(prev_name)); - if (!HANDLER(user, section, name, value) && !error) - error = lineno; - } - else if (!error) { - /* No '=' or ':' found on name[=:]value line */ -#if INI_ALLOW_NO_VALUE - *end = '\0'; - name = rstrip(start); - if (!HANDLER(user, section, name, NULL) && !error) - error = lineno; -#else - error = lineno; -#endif - } - } - -#if INI_STOP_ON_FIRST_ERROR - if (error) - break; -#endif - } - -#if !INI_USE_STACK - ini_free(line); -#endif - - return error; -} - -/* See documentation in header file. */ -int ini_parse_file(FILE* file, ini_handler handler, void* user) -{ - return ini_parse_stream((ini_reader)fgets, file, handler, user); -} - -/* See documentation in header file. */ -int ini_parse(const char* filename, ini_handler handler, void* user) -{ - FILE* file; - int error; - - file = fopen(filename, "r"); - if (!file) - return -1; - error = ini_parse_file(file, handler, user); - fclose(file); - return error; -} - -/* An ini_reader function to read the next line from a string buffer. This - is the fgets() equivalent used by ini_parse_string(). */ -static char* ini_reader_string(char* str, int num, void* stream) { - ini_parse_string_ctx* ctx = (ini_parse_string_ctx*)stream; - const char* ctx_ptr = ctx->ptr; - size_t ctx_num_left = ctx->num_left; - char* strp = str; - char c; - - if (ctx_num_left == 0 || num < 2) - return NULL; - - while (num > 1 && ctx_num_left != 0) { - c = *ctx_ptr++; - ctx_num_left--; - *strp++ = c; - if (c == '\n') - break; - num--; - } - - *strp = '\0'; - ctx->ptr = ctx_ptr; - ctx->num_left = ctx_num_left; - return str; -} - -/* See documentation in header file. */ -int ini_parse_string(const char* string, ini_handler handler, void* user) { - ini_parse_string_ctx ctx; - - ctx.ptr = string; - ctx.num_left = strlen(string); - return ini_parse_stream((ini_reader)ini_reader_string, &ctx, handler, - user); -} diff --git a/3rdparty/inih/ini.h b/3rdparty/inih/ini.h deleted file mode 100644 index d1a2ba8..0000000 --- a/3rdparty/inih/ini.h +++ /dev/null @@ -1,178 +0,0 @@ -/* inih -- simple .INI file parser - -SPDX-License-Identifier: BSD-3-Clause - -Copyright (C) 2009-2020, Ben Hoyt - -inih is released under the New BSD license (see LICENSE.txt). Go to the project -home page for more info: - -https://github.com/benhoyt/inih - -*/ - -#ifndef INI_H -#define INI_H - -/* Make this header file easier to include in C++ code */ -#ifdef __cplusplus -extern "C" { -#endif - -#include - -/* Nonzero if ini_handler callback should accept lineno parameter. */ -#ifndef INI_HANDLER_LINENO -#define INI_HANDLER_LINENO 0 -#endif - -/* Visibility symbols, required for Windows DLLs */ -#ifndef INI_API -#if defined _WIN32 || defined __CYGWIN__ -# ifdef INI_SHARED_LIB -# ifdef INI_SHARED_LIB_BUILDING -# define INI_API __declspec(dllexport) -# else -# define INI_API __declspec(dllimport) -# endif -# else -# define INI_API -# endif -#else -# if defined(__GNUC__) && __GNUC__ >= 4 -# define INI_API __attribute__ ((visibility ("default"))) -# else -# define INI_API -# endif -#endif -#endif - -/* Typedef for prototype of handler function. */ -#if INI_HANDLER_LINENO -typedef int (*ini_handler)(void* user, const char* section, - const char* name, const char* value, - int lineno); -#else -typedef int (*ini_handler)(void* user, const char* section, - const char* name, const char* value); -#endif - -/* Typedef for prototype of fgets-style reader function. */ -typedef char* (*ini_reader)(char* str, int num, void* stream); - -/* Parse given INI-style file. May have [section]s, name=value pairs - (whitespace stripped), and comments starting with ';' (semicolon). Section - is "" if name=value pair parsed before any section heading. name:value - pairs are also supported as a concession to Python's configparser. - - For each name=value pair parsed, call handler function with given user - pointer as well as section, name, and value (data only valid for duration - of handler call). Handler should return nonzero on success, zero on error. - - Returns 0 on success, line number of first error on parse error (doesn't - stop on first error), -1 on file open error, or -2 on memory allocation - error (only when INI_USE_STACK is zero). -*/ -INI_API int ini_parse(const char* filename, ini_handler handler, void* user); - -/* Same as ini_parse(), but takes a FILE* instead of filename. This doesn't - close the file when it's finished -- the caller must do that. */ -INI_API int ini_parse_file(FILE* file, ini_handler handler, void* user); - -/* Same as ini_parse(), but takes an ini_reader function pointer instead of - filename. Used for implementing custom or string-based I/O (see also - ini_parse_string). */ -INI_API int ini_parse_stream(ini_reader reader, void* stream, ini_handler handler, - void* user); - -/* Same as ini_parse(), but takes a zero-terminated string with the INI data -instead of a file. Useful for parsing INI data from a network socket or -already in memory. */ -INI_API int ini_parse_string(const char* string, ini_handler handler, void* user); - -/* Nonzero to allow multi-line value parsing, in the style of Python's - configparser. If allowed, ini_parse() will call the handler with the same - name for each subsequent line parsed. */ -#ifndef INI_ALLOW_MULTILINE -#define INI_ALLOW_MULTILINE 1 -#endif - -/* Nonzero to allow a UTF-8 BOM sequence (0xEF 0xBB 0xBF) at the start of - the file. See https://github.com/benhoyt/inih/issues/21 */ -#ifndef INI_ALLOW_BOM -#define INI_ALLOW_BOM 1 -#endif - -/* Chars that begin a start-of-line comment. Per Python configparser, allow - both ; and # comments at the start of a line by default. */ -#ifndef INI_START_COMMENT_PREFIXES -#define INI_START_COMMENT_PREFIXES ";#" -#endif - -/* Nonzero to allow inline comments (with valid inline comment characters - specified by INI_INLINE_COMMENT_PREFIXES). Set to 0 to turn off and match - Python 3.2+ configparser behaviour. */ -#ifndef INI_ALLOW_INLINE_COMMENTS -#define INI_ALLOW_INLINE_COMMENTS 1 -#endif -#ifndef INI_INLINE_COMMENT_PREFIXES -#define INI_INLINE_COMMENT_PREFIXES ";" -#endif - -/* Nonzero to use stack for line buffer, zero to use heap (malloc/free). */ -#ifndef INI_USE_STACK -#define INI_USE_STACK 1 -#endif - -/* Maximum line length for any line in INI file (stack or heap). Note that - this must be 3 more than the longest line (due to '\r', '\n', and '\0'). */ -#ifndef INI_MAX_LINE -#define INI_MAX_LINE 200 -#endif - -/* Nonzero to allow heap line buffer to grow via realloc(), zero for a - fixed-size buffer of INI_MAX_LINE bytes. Only applies if INI_USE_STACK is - zero. */ -#ifndef INI_ALLOW_REALLOC -#define INI_ALLOW_REALLOC 0 -#endif - -/* Initial size in bytes for heap line buffer. Only applies if INI_USE_STACK - is zero. */ -#ifndef INI_INITIAL_ALLOC -#define INI_INITIAL_ALLOC 200 -#endif - -/* Stop parsing on first error (default is to keep parsing). */ -#ifndef INI_STOP_ON_FIRST_ERROR -#define INI_STOP_ON_FIRST_ERROR 0 -#endif - -/* Nonzero to call the handler at the start of each new section (with - name and value NULL). Default is to only call the handler on - each name=value pair. */ -#ifndef INI_CALL_HANDLER_ON_NEW_SECTION -#define INI_CALL_HANDLER_ON_NEW_SECTION 0 -#endif - -/* Nonzero to allow a name without a value (no '=' or ':' on the line) and - call the handler with value NULL in this case. Default is to treat - no-value lines as an error. */ -#ifndef INI_ALLOW_NO_VALUE -#define INI_ALLOW_NO_VALUE 0 -#endif - -/* Nonzero to use custom ini_malloc, ini_free, and ini_realloc memory - allocation functions (INI_USE_STACK must also be 0). These functions must - have the same signatures as malloc/free/realloc and behave in a similar - way. ini_realloc is only needed if INI_ALLOW_REALLOC is set. */ -#ifndef INI_CUSTOM_ALLOCATOR -#define INI_CUSTOM_ALLOCATOR 0 -#endif - - -#ifdef __cplusplus -} -#endif - -#endif /* INI_H */ diff --git a/3rdparty/plog b/3rdparty/plog deleted file mode 160000 index 914e799..0000000 --- a/3rdparty/plog +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 914e799d2b08d790f5d04d1c46928586b3a41250 diff --git a/3rdparty/qt-unix-signals/CMakeLists.txt b/3rdparty/qt-unix-signals/CMakeLists.txt deleted file mode 100644 index c2afbb7..0000000 --- a/3rdparty/qt-unix-signals/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -cmake_minimum_required(VERSION 3.1.0) - -project(QtSignals LANGUAGES CXX) - -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) -# Instruct CMake to run moc automatically when needed. -set(CMAKE_AUTOMOC ON) - -find_package(Qt5 REQUIRED COMPONENTS Core) - -add_library(QtSignals STATIC sigwatch.cpp) -target_include_directories(QtSignals INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) -target_link_libraries(QtSignals Qt5::Core) diff --git a/3rdparty/qt-unix-signals/LICENCE b/3rdparty/qt-unix-signals/LICENCE deleted file mode 100644 index 7cee458..0000000 --- a/3rdparty/qt-unix-signals/LICENCE +++ /dev/null @@ -1,21 +0,0 @@ -Unix signal watcher for Qt. - -Copyright (C) 2014 Simon Knopp - -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. \ No newline at end of file diff --git a/3rdparty/qt-unix-signals/sigwatch.cpp b/3rdparty/qt-unix-signals/sigwatch.cpp deleted file mode 100644 index 0374d64..0000000 --- a/3rdparty/qt-unix-signals/sigwatch.cpp +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Unix signal watcher for Qt. - * - * Copyright (C) 2014 Simon Knopp - * - * 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. - */ - -#include -#include -#include -#include -#include -#include -#include "sigwatch.h" - - -/*! - * \brief The UnixSignalWatcherPrivate class implements the back-end signal - * handling for the UnixSignalWatcher. - * - * \see http://qt-project.org/doc/qt-5.0/qtdoc/unix-signals.html - */ -class UnixSignalWatcherPrivate : public QObject -{ - UnixSignalWatcher * const q_ptr; - Q_DECLARE_PUBLIC(UnixSignalWatcher) - -public: - UnixSignalWatcherPrivate(UnixSignalWatcher *q); - ~UnixSignalWatcherPrivate(); - - void watchForSignal(int signal); - static void signalHandler(int signal); - - void _q_onNotify(int sockfd); - -private: - static int sockpair[2]; - QSocketNotifier *notifier; - QList watchedSignals; -}; - - -int UnixSignalWatcherPrivate::sockpair[2]; - -UnixSignalWatcherPrivate::UnixSignalWatcherPrivate(UnixSignalWatcher *q) : - q_ptr(q) -{ - // Create socket pair - if (::socketpair(AF_UNIX, SOCK_STREAM, 0, sockpair)) { - qDebug() << "UnixSignalWatcher: socketpair: " << ::strerror(errno); - return; - } - - // Create a notifier for the read end of the pair - notifier = new QSocketNotifier(sockpair[1], QSocketNotifier::Read); - QObject::connect(notifier, SIGNAL(activated(int)), q, SLOT(_q_onNotify(int))); - notifier->setEnabled(true); -} - -UnixSignalWatcherPrivate::~UnixSignalWatcherPrivate() -{ - delete notifier; -} - -/*! - * Registers a handler for the given Unix \a signal. The handler will write to - * a socket pair, the other end of which is connected to a QSocketNotifier. - * This provides a way to break out of the asynchronous context from which the - * signal handler is called and back into the Qt event loop. - */ -void UnixSignalWatcherPrivate::watchForSignal(int signal) -{ - if (watchedSignals.contains(signal)) { - qDebug() << "Already watching for signal" << signal; - return; - } - - // Register a sigaction which will write to the socket pair - struct sigaction sigact; - sigact.sa_handler = UnixSignalWatcherPrivate::signalHandler; - sigact.sa_flags = 0; - ::sigemptyset(&sigact.sa_mask); - sigact.sa_flags |= SA_RESTART; - if (::sigaction(signal, &sigact, NULL)) { - qDebug() << "UnixSignalWatcher: sigaction: " << ::strerror(errno); - return; - } - - watchedSignals.append(signal); -} - -/*! - * Called when a Unix \a signal is received. Write to the socket to wake up the - * QSocketNotifier. - */ -void UnixSignalWatcherPrivate::signalHandler(int signal) -{ - ssize_t nBytes = ::write(sockpair[0], &signal, sizeof(signal)); - Q_UNUSED(nBytes); -} - -/*! - * Called when the signal handler has written to the socket pair. Emits the Unix - * signal as a Qt signal. - */ -void UnixSignalWatcherPrivate::_q_onNotify(int sockfd) -{ - Q_Q(UnixSignalWatcher); - - int signal; - ssize_t nBytes = ::read(sockfd, &signal, sizeof(signal)); - Q_UNUSED(nBytes); - qDebug() << "Caught signal:" << ::strsignal(signal); - emit q->unixSignal(signal); -} - - -/*! - * Create a new UnixSignalWatcher as a child of the given \a parent. - */ -UnixSignalWatcher::UnixSignalWatcher(QObject *parent) : - QObject(parent), - d_ptr(new UnixSignalWatcherPrivate(this)) -{ -} - -/*! - * Destroy this UnixSignalWatcher. - */ -UnixSignalWatcher::~UnixSignalWatcher() -{ - delete d_ptr; -} - -/*! - * Register a signal handler for the given \a signal. - * - * After calling this method you can \c connect() to the unixSignal() Qt signal - * to be notified when the Unix signal is received. - */ -void UnixSignalWatcher::watchForSignal(int signal) -{ - Q_D(UnixSignalWatcher); - d->watchForSignal(signal); -} - -/*! - * \fn void UnixSignalWatcher::unixSignal(int signal) - * Emitted when the given Unix \a signal is received. - * - * watchForSignal() must be called for each Unix signal that you want to receive - * via the unixSignal() Qt signal. If a watcher is watching multiple signals, - * unixSignal() will be emitted whenever *any* of the watched Unix signals are - * received, and the \a signal argument can be inspected to find out which one - * was actually received. - */ - -#include "moc_sigwatch.cpp" diff --git a/3rdparty/qt-unix-signals/sigwatch.h b/3rdparty/qt-unix-signals/sigwatch.h deleted file mode 100644 index 135fd66..0000000 --- a/3rdparty/qt-unix-signals/sigwatch.h +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Unix signal watcher for Qt. - * - * Copyright (C) 2014 Simon Knopp - * - * 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. - */ - -#ifndef SIGWATCH_H -#define SIGWATCH_H - -#include -#include - -class UnixSignalWatcherPrivate; - - -/*! - * \brief The UnixSignalWatcher class converts Unix signals to Qt signals. - * - * To watch for a given signal, e.g. \c SIGINT, call \c watchForSignal(SIGINT) - * and \c connect() your handler to unixSignal(). - */ - -class UnixSignalWatcher : public QObject -{ - Q_OBJECT -public: - explicit UnixSignalWatcher(QObject *parent = 0); - ~UnixSignalWatcher(); - - void watchForSignal(int signal); - -signals: - void unixSignal(int signal); - -private: - UnixSignalWatcherPrivate * const d_ptr; - Q_DECLARE_PRIVATE(UnixSignalWatcher) - Q_PRIVATE_SLOT(d_func(), void _q_onNotify(int)) -}; - -#endif // SIGWATCH_H diff --git a/3rdparty/qtkeychain b/3rdparty/qtkeychain deleted file mode 160000 index f197cdb..0000000 --- a/3rdparty/qtkeychain +++ /dev/null @@ -1 +0,0 @@ -Subproject commit f197cdb935b0cfd9881fdc6860874cb8379d1238 diff --git a/CMakeLists.txt b/CMakeLists.txt deleted file mode 100644 index 5e0c087..0000000 --- a/CMakeLists.txt +++ /dev/null @@ -1,39 +0,0 @@ -cmake_minimum_required(VERSION 3.10.0) - -set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH}") -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -set(CMAKE_AUTOMOC ON) -set(CMAKE_AUTORCC ON) -set(CMAKE_AUTOUIC ON) - -file(STRINGS "VERSION" version) -project(GlobalProtect-openconnect LANGUAGES CXX) - -# Set the CMAKE_INSTALL_PREFIX to /usr if not specified -if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) - set(CMAKE_INSTALL_PREFIX "/usr" CACHE PATH "The default install prefix" FORCE) -endif() - -message(STATUS "CMAKE_INSTALL_PREFIX was set to: ${CMAKE_INSTALL_PREFIX}") - -configure_file(version.h.in version.h) - -find_package(Qt5 REQUIRED COMPONENTS - Core - Widgets - Network - WebSockets - WebEngine - WebEngineWidgets - DBus -) - -find_package(Qt5Keychain REQUIRED) - -add_subdirectory(3rdparty/qt-unix-signals) -add_subdirectory(3rdparty/inih) -add_subdirectory(GPService) -add_subdirectory(GPClient) -add_dependencies(gpclient gpservice) diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..f4bac8f --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,3542 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94fb8275041c72129eb51b7d0322c29b8387a0386127718b096429201a5d6ece" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "anyhow" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224afbd727c3d6e4b90103ece64b8d1b67fbb1973b1046c2281eed3f3803f800" + +[[package]] +name = "app" +version = "0.1.0" +dependencies = [ + "common", + "serde", + "serde_json", + "tauri", + "tauri-build", +] + +[[package]] +name = "async-trait" +version = "0.1.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84f9ebcc6c1f5b8cb160f6990096a5c127f423fcb6e1ccc46c370cbdfb75dfc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atk" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3d816ce6f0e2909a96830d6911c2aff044370b1ef92d7f267b43bae5addedd" +dependencies = [ + "atk-sys", + "bitflags", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58aeb089fb698e06db8089971c7ee317ab9644bade33383f63631437b03aafb6" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.0.3", +] + +[[package]] +name = "attohttpc" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fcf00bc6d5abb29b5f97e3c61a90b6d3caa12f3faf897d4a3e3607c050a35a7" +dependencies = [ + "flate2", + "http", + "log", + "native-tls", + "serde", + "serde_json", + "serde_urlencoded", + "url", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[package]] +name = "base64" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" + +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + +[[package]] +name = "brotli" +version = "3.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "2.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b6561fd3f895a11e8f72af2cb7d22e08366bebc2b6b57f7744c4bda27034744" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bstr" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ffdb39cb703212f3c11973452c2861b972f757b021158f3516ba10f2fa8b2c1" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "bytemuck" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "bytes" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" + +[[package]] +name = "cairo-rs" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c76ee391b03d35510d9fa917357c7f1855bd9a6659c95a1b392e33f49b3369bc" +dependencies = [ + "bitflags", + "cairo-sys-rs", + "glib", + "libc", + "thiserror", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c55d429bef56ac9172d25fecb85dc8068307d17acd74b377866b7a1ef25d3c8" +dependencies = [ + "glib-sys", + "libc", + "system-deps 6.0.3", +] + +[[package]] +name = "cargo_toml" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497049e9477329f8f6a559972ee42e117487d01d1e8c2cc9f836ea6fa23a9e1a" +dependencies = [ + "serde", + "toml", +] + +[[package]] +name = "cc" +version = "1.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f" + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74f89d248799e3f15f91b70917f65381062a01bb8e222700ea0e5a7ff9785f9c" +dependencies = [ + "byteorder", + "uuid 0.8.2", +] + +[[package]] +name = "cfg-expr" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3431df59f28accaf4cb4eed4a9acc66bea3f3c3753aa6cdc2f024174ef232af7" +dependencies = [ + "smallvec", +] + +[[package]] +name = "cfg-expr" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0357a6402b295ca3a86bc148e84df46c02e41f41fef186bda662557ef6328aa" +dependencies = [ + "smallvec", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cocoa" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" +dependencies = [ + "bitflags", + "block", + "cocoa-foundation", + "core-foundation", + "core-graphics", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "cocoa-foundation" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ade49b65d560ca58c403a479bb396592b155c0185eada742ee323d1d68d6318" +dependencies = [ + "bitflags", + "block", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", + "objc", +] + +[[package]] +name = "color_quant" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" + +[[package]] +name = "combine" +version = "4.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "common" +version = "0.1.0" +dependencies = [ + "async-trait", + "bytes", + "cc", + "data-encoding", + "ring", + "serde", + "serde_json", + "thiserror", + "tokio", + "tokio-util", +] + +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + +[[package]] +name = "core-foundation" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" + +[[package]] +name = "core-graphics" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" +dependencies = [ + "bitflags", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68b68b3446082644c91ac778bf50cd4104bfb002b5a6a7c44cca5a2c70788b" +dependencies = [ + "bitflags", + "core-foundation", + "foreign-types", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa 0.4.8", + "matches", + "phf 0.8.0", + "proc-macro2", + "quote", + "smallvec", + "syn", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "ctor" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "cty" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" + +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "data-encoding" +version = "2.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d8666cb01533c39dde32bcbab8e227b4ed6679b2c925eba05feabea39508fb" + +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.0", + "syn", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "dispatch" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" + +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "dtoa-short" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dunce" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bd4b30a6560bbd9b4620f4de34c3f14f60848e58a9b7216801afcb4c7b31c3c" + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "encoding_rs" +version = "0.8.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071a31f4ee85403370b58aca746f01041ede6f0da2730960ad001edc2b71b394" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + +[[package]] +name = "field-offset" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e1c54951450cbd39f3dbcf1005ac413b49487dabf18a720ad2383eccfeffb92" +dependencies = [ + "memoffset", + "rustc_version 0.3.3", +] + +[[package]] +name = "filetime" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a3de6e8d11b22ff9edc6d916f890800597d60f8b2da1caf2955c274638d6412" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.45.0", +] + +[[package]] +name = "flate2" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8a2db397cb1c8772f31494cb8917e48cd1e64f0fa7efac59fbd741a0a8ce841" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9c384f161156f5260c24a097c56119f9be8c798586aecc13afbcbe7b7e26bf8" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + +[[package]] +name = "futures-channel" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e5317663a9089767a1ec00a487df42e0ca174b61b4483213ac24448e4664df5" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec90ff4d0fe1f57d600049061dc6bb68ed03c7d2fbd697274c41805dcb3f8608" + +[[package]] +name = "futures-executor" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8de0a35a6ab97ec8869e32a2473f4b1324459e14c29275d14b10cb1fd19b50e" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfb8371b6fb2aeb2d280374607aeabfc99d95c72edfe51692e42d3d7f0d08531" + +[[package]] +name = "futures-macro" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" + +[[package]] +name = "futures-task" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf79a1bf610b10f42aea489289c5a2c478a786509693b80cd39c44ccd936366" + +[[package]] +name = "futures-util" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c1d6de3acfef38d2be4b1f543f553131788603495be83da675e180c8d6b7bd1" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "gdk" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6e05c1f572ab0e1f15be94217f0dc29088c248b14f792a5ff0af0d84bcda9e8" +dependencies = [ + "bitflags", + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad38dd9cc8b099cceecdf41375bb6d481b1b5a7cd5cd603e10a69a9383f8619a" +dependencies = [ + "bitflags", + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140b2f5378256527150350a8346dbdb08fadc13453a7a2d73aecd5fab3c402a7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.0.3", +] + +[[package]] +name = "gdk-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e7a08c1e8f06f4177fb7e51a777b8c1689f743a7bc11ea91d44d2226073a88" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps 6.0.3", +] + +[[package]] +name = "gdkx11-sys" +version = "0.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4b7f8c7a84b407aa9b143877e267e848ff34106578b64d1e0a24bf550716178" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps 6.0.3", + "x11", +] + +[[package]] +name = "generator" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33a20a288a94683f5f4da0adecdbe095c94a77c295e514cc6484e9394dd8376e" +dependencies = [ + "cc", + "libc", + "log", + "rustversion", + "windows 0.44.0", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "gio" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68fdbc90312d462781a395f7a16d96a2b379bb6ef8cd6310a2df272771c4283b" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-io", + "gio-sys", + "glib", + "libc", + "once_cell", + "thiserror", +] + +[[package]] +name = "gio-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32157a475271e2c4a023382e9cab31c4584ee30a97da41d3c4e9fdd605abcf8d" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.0.3", + "winapi", +] + +[[package]] +name = "glib" +version = "0.15.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edb0306fbad0ab5428b0ca674a23893db909a98582969c9b537be4ced78c505d" +dependencies = [ + "bitflags", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "once_cell", + "smallvec", + "thiserror", +] + +[[package]] +name = "glib-macros" +version = "0.15.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a68131a662b04931e71891fb14aaf65ee4b44d08e8abc10f49e77418c86c64" +dependencies = [ + "anyhow", + "heck 0.4.1", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "glib-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef4b192f8e65e9cf76cbf4ea71fa8e3be4a0e18ffe3d68b8da6836974cc5bad4" +dependencies = [ + "libc", + "system-deps 6.0.3", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "globset" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "029d74589adefde59de1a0c4f4732695c32805624aec7b68d91503d4dba79afc" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "gobject-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d57ce44246becd17153bd035ab4d32cfee096a657fc01f2231c9278378d1e0a" +dependencies = [ + "glib-sys", + "libc", + "system-deps 6.0.3", +] + +[[package]] +name = "gpclient" +version = "0.1.0" +dependencies = [ + "common", + "tokio", +] + +[[package]] +name = "gpservice" +version = "0.1.0" +dependencies = [ + "common", + "tokio", +] + +[[package]] +name = "gtk" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e3004a2d5d6d8b5057d2b57b3712c9529b62e82c77f25c1fecde1fd5c23bd0" +dependencies = [ + "atk", + "bitflags", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "once_cell", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.15.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5bc2f0587cba247f60246a0ca11fe25fb733eabc3de12d1965fc07efab87c84" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps 6.0.3", +] + +[[package]] +name = "gtk3-macros" +version = "0.15.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24f518afe90c23fba585b2d7697856f9e6a7bbc62f65588035e66f6afb01a2e9" +dependencies = [ + "anyhow", + "proc-macro-crate", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7" +dependencies = [ + "libc", +] + +[[package]] +name = "html5ever" +version = "0.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5c13fb08e5d4dfc151ee5e88bae63f7773d61852f3bdc73c9f4b9e1bde03148" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "http" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" +dependencies = [ + "bytes", + "fnv", + "itoa 1.0.5", +] + +[[package]] +name = "http-range" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573" + +[[package]] +name = "ico" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031530fe562d8c8d71c0635013d6d155bbfe8ba0aa4b4d2d24ce8af6b71047bd" +dependencies = [ + "byteorder", + "png", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ddfc70884202db2244c223200c204c2bda1bc6e0998d11b5e024d657209e6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + +[[package]] +name = "image" +version = "0.24.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b7ea949b537b0fd0af141fff8c77690f2ce96f4f41f042ccb6c69c6c965945" +dependencies = [ + "bytemuck", + "byteorder", + "color_quant", + "num-rational", + "num-traits", +] + +[[package]] +name = "indexmap" +version = "1.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "infer" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20b2b533137b9cad970793453d4f921c2e91312a6d88b1085c07bc15fc51bb3b" +dependencies = [ + "cfb", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + +[[package]] +name = "itoa" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" + +[[package]] +name = "javascriptcore-rs" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf053e7843f2812ff03ef5afe34bb9c06ffee120385caad4f6b9967fcd37d41c" +dependencies = [ + "bitflags", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "905fbb87419c5cde6e3269537e4ea7d46431f3008c5d057e915ef3f115e7793c" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 5.0.0", +] + +[[package]] +name = "jni" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "039022cdf4d7b1cf548d31f60ae783138e5fd42013f6271049d7df7afadef96c" +dependencies = [ + "cesu8", + "combine", + "jni-sys", + "log", + "thiserror", + "walkdir", +] + +[[package]] +name = "jni-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" + +[[package]] +name = "js-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "445dde2150c55e483f3d8416706b97ec8e8237c307e5b7b4b8dd15e6af2a0730" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb3fa5a61630976fc4c353c70297f2e93f1930e3ccee574d59d618ccbd5154ce" +dependencies = [ + "serde", + "serde_json", + "treediff", +] + +[[package]] +name = "kuchiki" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ea8e9c6e031377cff82ee3001dc8026cdf431ed4e2e6b51f98ab8c73484a358" +dependencies = [ + "cssparser", + "html5ever", + "matches", + "selectors", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.139" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" + +[[package]] +name = "line-wrap" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f30344350a2a51da54c1d53be93fade8a237e545dbcc4bdbe635413f2117cab9" +dependencies = [ + "safemem", +] + +[[package]] +name = "lock_api" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "loom" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "serde", + "serde_json", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "malloc_buf" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" +dependencies = [ + "libc", +] + +[[package]] +name = "markup5ever" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a24f40fb03852d1cdd84330cddcaf98e9ec08a7b7768e952fad3b4cf048ec8fd" +dependencies = [ + "log", + "phf 0.8.0", + "phf_codegen", + "string_cache", + "string_cache_codegen", + "tendril", +] + +[[package]] +name = "matchers" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d732bc30207a6423068df043e3d02e0735b155ad7ce1a6f76fe2baa5b158de" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.42.0", +] + +[[package]] +name = "native-tls" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" +dependencies = [ + "lazy_static", + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "ndk" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2032c77e030ddee34a6787a64166008da93f6a352b629261d0fee232b8742dd4" +dependencies = [ + "bitflags", + "jni-sys", + "ndk-sys", + "num_enum", + "thiserror", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e5a6ae77c8ee183dcbbba6150e2e6b9f3f4196a7666c02a715a95692ec1fa97" +dependencies = [ + "jni-sys", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + +[[package]] +name = "nu-ansi-term" +version = "0.46.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +dependencies = [ + "overload", + "winapi", +] + +[[package]] +name = "num-integer" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "objc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" +dependencies = [ + "malloc_buf", + "objc_exception", +] + +[[package]] +name = "objc_exception" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad970fb455818ad6cba4c122ad012fae53ae8b4795f86378bce65e4f6bab2ca4" +dependencies = [ + "cc", +] + +[[package]] +name = "objc_id" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92d4ddb4bd7b50d730c215ff871754d0da6b2178849f8a2a2ab69712d0c073b" +dependencies = [ + "objc", +] + +[[package]] +name = "once_cell" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" + +[[package]] +name = "openssl" +version = "0.10.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b102428fd03bc5edf97f62620f7298614c45cedf287c271e7ed450bbaf83f2e1" +dependencies = [ + "bitflags", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "openssl-probe" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" + +[[package]] +name = "openssl-sys" +version = "0.9.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23bbbf7854cd45b83958ebe919f0e8e516793727652e27fda10a8384cfc790b7" +dependencies = [ + "autocfg", + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "overload" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" + +[[package]] +name = "pango" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e4045548659aee5313bde6c582b0d83a627b7904dd20dc2d9ef0895d414e4f" +dependencies = [ + "bitflags", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.15.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2a00081cde4661982ed91d80ef437c20eacaf6aa1a5962c0279ae194662c3aa" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps 6.0.3", +] + +[[package]] +name = "parking_lot" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-sys 0.45.0", +] + +[[package]] +name = "paste" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pest" +version = "2.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "028accff104c4e513bad663bbcd2ad7cfd5304144404c31ed0a77ac103d00660" +dependencies = [ + "thiserror", + "ucd-trie", +] + +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_macros 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_macros" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58fdf3184dd560f160dd73922bea2d5cd6e8f064bf4b13110abd81b03697b4e0" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "plist" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9469799ca90293a376f68f6fcb8f11990d9cff55602cfba0ba83893c973a7f46" +dependencies = [ + "base64 0.21.0", + "indexmap", + "line-wrap", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d708eaf860a19b19ce538740d2b4bdeeb8337fa53f7738455e706623ad5c638" +dependencies = [ + "bitflags", + "crc32fast", + "flate2", + "miniz_oxide", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + +[[package]] +name = "proc-macro2" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f50b1c63b38611e7d4d7f68b82d3ad0cc71a2ad2e7f61fc10f1328d917c93cd" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.8", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "raw-window-handle" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed7e3d950b66e19e0c372f3fa3fbbcf85b1746b571f74e0c2af6042a5c93420a" +dependencies = [ + "cty", +] + +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom 0.2.8", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regex" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +dependencies = [ + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" + +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + +[[package]] +name = "ring" +version = "0.16.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver 1.0.16", +] + +[[package]] +name = "rustversion" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70" + +[[package]] +name = "ryu" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" + +[[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schannel" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713cfb06c7059f3588fb8044c0fad1d09e3c01d225e25b9220dbfdcf16dbb1b3" +dependencies = [ + "windows-sys 0.42.0", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "security-framework" +version = "2.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a332be01508d814fed64bf28f798a146d73792121129962fdf335bb3c49a4254" +dependencies = [ + "bitflags", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31c9bb296072e961fcbd8853511dd39c2d8be2deb1e17c6860b1d30732b323b4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "selectors" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +dependencies = [ + "bitflags", + "cssparser", + "derive_more", + "fxhash", + "log", + "matches", + "phf 0.8.0", + "phf_codegen", + "precomputed-hash", + "servo_arc", + "smallvec", + "thin-slice", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" +dependencies = [ + "serde", +] + +[[package]] +name = "semver-parser" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" +dependencies = [ + "pest", +] + +[[package]] +name = "serde" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.91" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" +dependencies = [ + "itoa 1.0.5", + "ryu", + "serde", +] + +[[package]] +name = "serde_repr" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a5ec9fa74a20ebbe5d9ac23dac1fc96ba0ecfe9f50f2843b52e537b10fbcb4e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa 1.0.5", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" +dependencies = [ + "serde", + "serde_with_macros", +] + +[[package]] +name = "serde_with_macros" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c9823f2d3b6a81d98228151fdeaf848206a7855a7a042bbf9bf870449a66cafb" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74064874e9f6a15f04c1f3cb627902d0e6b410abbf36668afa873c61889f1763" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "servo_arc" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" +dependencies = [ + "libc", +] + +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + +[[package]] +name = "slab" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "socket2" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02e2d2db9033d13a1567121ddd7a095ee144db4e1ca1b1bda3419bc0da294ebd" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "soup2" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2b4d76501d8ba387cf0fefbe055c3e0a59891d09f0f995ae4e4b16f6b60f3c0" +dependencies = [ + "bitflags", + "gio", + "glib", + "libc", + "once_cell", + "soup2-sys", +] + +[[package]] +name = "soup2-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "009ef427103fcb17f802871647a7fa6c60cbb654b4c4e4c0ac60a31c5f6dc9cf" +dependencies = [ + "bitflags", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps 5.0.0", +] + +[[package]] +name = "spin" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "state" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbe866e1e51e8260c9eed836a042a5e7f6726bb2b411dffeaa712e19c388f23b" +dependencies = [ + "loom", +] + +[[package]] +name = "string_cache" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot", + "phf_shared 0.10.0", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "syn" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-deps" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18db855554db7bd0e73e06cf7ba3df39f97812cb11d3f75e71c39bf45171797e" +dependencies = [ + "cfg-expr 0.9.1", + "heck 0.3.3", + "pkg-config", + "toml", + "version-compare 0.0.11", +] + +[[package]] +name = "system-deps" +version = "6.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2955b1fe31e1fa2fbd1976b71cc69a606d7d4da16f6de3333d0c92d51419aeff" +dependencies = [ + "cfg-expr 0.11.0", + "heck 0.4.1", + "pkg-config", + "toml", + "version-compare 0.1.1", +] + +[[package]] +name = "tao" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac8e6399427c8494f9849b58694754d7cc741293348a6836b6c8d2c5aa82d8e6" +dependencies = [ + "bitflags", + "cairo-rs", + "cc", + "cocoa", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dispatch", + "gdk", + "gdk-pixbuf", + "gdk-sys", + "gdkx11-sys", + "gio", + "glib", + "glib-sys", + "gtk", + "image", + "instant", + "jni", + "lazy_static", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "objc", + "once_cell", + "parking_lot", + "paste", + "png", + "raw-window-handle", + "scopeguard", + "serde", + "unicode-segmentation", + "uuid 1.3.0", + "windows 0.39.0", + "windows-implement", + "x11-dl", +] + +[[package]] +name = "tar" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b55807c0344e1e6c04d7c965f5289c39a8d94ae23ed5c0b57aabac549f871c6" +dependencies = [ + "filetime", + "libc", + "xattr", +] + +[[package]] +name = "tauri" +version = "1.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7e0f1d535e7cbbbab43c82be4fc992b84f9156c16c160955617e0260ebc449" +dependencies = [ + "anyhow", + "attohttpc", + "cocoa", + "dirs-next", + "embed_plist", + "encoding_rs", + "flate2", + "futures-util", + "glib", + "glob", + "gtk", + "heck 0.4.1", + "http", + "ignore", + "objc", + "once_cell", + "percent-encoding", + "rand 0.8.5", + "raw-window-handle", + "semver 1.0.16", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "state", + "tar", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "tempfile", + "thiserror", + "tokio", + "url", + "uuid 1.3.0", + "webkit2gtk", + "webview2-com", + "windows 0.39.0", +] + +[[package]] +name = "tauri-build" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8807c85d656b2b93927c19fe5a5f1f1f348f96c2de8b90763b3c2d561511f9b4" +dependencies = [ + "anyhow", + "cargo_toml", + "heck 0.4.1", + "json-patch", + "semver 1.0.16", + "serde_json", + "tauri-utils", + "winres", +] + +[[package]] +name = "tauri-codegen" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14388d484b6b1b5dc0f6a7d6cc6433b3b230bec85eaa576adcdf3f9fafa49251" +dependencies = [ + "base64 0.13.1", + "brotli", + "ico", + "json-patch", + "plist", + "png", + "proc-macro2", + "quote", + "semver 1.0.16", + "serde", + "serde_json", + "sha2", + "tauri-utils", + "thiserror", + "time", + "uuid 1.3.0", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "069319e5ecbe653a799b94b0690d9f9bf5d00f7b1d3989aa331c524d4e354075" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-runtime" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c507d954d08ac8705d235bc70ec6975b9054fb95ff7823af72dbb04186596f3b" +dependencies = [ + "gtk", + "http", + "http-range", + "rand 0.8.5", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror", + "uuid 1.3.0", + "webview2-com", + "windows 0.39.0", +] + +[[package]] +name = "tauri-runtime-wry" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36b1c5764a41a13176a4599b5b7bd0881bea7d94dfe45e1e755f789b98317e30" +dependencies = [ + "cocoa", + "gtk", + "percent-encoding", + "rand 0.8.5", + "raw-window-handle", + "tauri-runtime", + "tauri-utils", + "uuid 1.3.0", + "webkit2gtk", + "webview2-com", + "windows 0.39.0", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5abbc109a6eb45127956ffcc26ef0e875d160150ac16cfa45d26a6b2871686f1" +dependencies = [ + "brotli", + "ctor", + "glob", + "heck 0.4.1", + "html5ever", + "infer", + "json-patch", + "kuchiki", + "memchr", + "phf 0.10.1", + "proc-macro2", + "quote", + "semver 1.0.16", + "serde", + "serde_json", + "serde_with", + "thiserror", + "url", + "walkdir", + "windows 0.39.0", +] + +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + +[[package]] +name = "thin-slice" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" + +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "time" +version = "0.3.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" +dependencies = [ + "itoa 1.0.5", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" + +[[package]] +name = "time-macros" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" +dependencies = [ + "time-core", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" + +[[package]] +name = "tokio" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8e00990ebabbe4c14c08aca901caed183ecd5c09562a12c824bb53d3c3fd3af" +dependencies = [ + "autocfg", + "bytes", + "libc", + "memchr", + "mio", + "num_cpus", + "parking_lot", + "pin-project-lite", + "signal-hook-registry", + "socket2", + "tokio-macros", + "windows-sys 0.42.0", +] + +[[package]] +name = "tokio-macros" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio-util" +version = "0.7.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "806fe8c2c87eccc8b3267cbae29ed3ab2d0bd37fca70ab622e46aaa9375ddb7d" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab8ed2edee10b50132aed5f331333428b011c99402b5a534154ed15746f9622" + +[[package]] +name = "toml_edit" +version = "0.19.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a1eb0622d28f4b9c90adc4ea4b2b46b47663fde9ac5fafcb14a1369d5508825" +dependencies = [ + "indexmap", + "toml_datetime", + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" +dependencies = [ + "cfg-if", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922" +dependencies = [ + "lazy_static", + "log", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "treediff" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "761e8d5ad7ce14bb82b7e61ccc0ca961005a275a060b9644a2431aa11553c2ff" +dependencies = [ + "serde_json", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + +[[package]] +name = "ucd-trie" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81" + +[[package]] +name = "unicode-bidi" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54675592c1dbefd78cbd98db9bacd89886e1ca50692a0692baefffdeb92dd58" + +[[package]] +name = "unicode-ident" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" + +[[package]] +name = "unicode-normalization" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5713f0fc4b5db668a2ac63cdb7bb4469d8c9fed047b1d0292cc7b0ce2ba921" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd624098567895118886609431a7c3b8f516e41d30e0643f03d94592a147e36" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + +[[package]] +name = "url" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d68c799ae75762b8c3fe375feb6600ef5602c883c5d21eb51c09f22b83c4643" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "uuid" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" + +[[package]] +name = "uuid" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" +dependencies = [ + "getrandom 0.2.8", +] + +[[package]] +name = "valuable" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "version-compare" +version = "0.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c18c859eead79d8b95d09e4678566e8d70105c4e7b251f707a03df32442661b" + +[[package]] +name = "version-compare" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579a42fc0b8e0c63b76519a339be31bed574929511fa53c1a3acae26eb258f29" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasm-bindgen" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f8dcbc21f30d9b8f2ea926ecb58f6b91192c17e9d33594b3df58b2007ca53b" +dependencies = [ + "cfg-if", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ce90fd5bcc06af55a641a86428ee4229e44e07033963a2290a8e241607ccb9" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c21f77c0bedc37fd5dc21f897894a5ca01e7bb159884559461862ae90c0b4c5" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2aff81306fcac3c7515ad4e177f521b5c9a15f2b08f4e32d823066102f35a5f6" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.84" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" + +[[package]] +name = "web-sys" +version = "0.3.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33b99f4b23ba3eec1a53ac264e35a755f00e966e0065077d6027c0f575b0b97" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "webkit2gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8f859735e4a452aeb28c6c56a852967a8a76c8eb1cc32dbf931ad28a13d6370" +dependencies = [ + "bitflags", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup2", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d76ca6ecc47aeba01ec61e480139dda143796abcae6f83bcddf50d6b5b1dcf3" +dependencies = [ + "atk-sys", + "bitflags", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pango-sys", + "pkg-config", + "soup2-sys", + "system-deps 6.0.3", +] + +[[package]] +name = "webview2-com" +version = "0.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a769c9f1a64a8734bde70caafac2b96cada12cd4aefa49196b3a386b8b4178" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows 0.39.0", + "windows-implement", +] + +[[package]] +name = "webview2-com-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaebe196c01691db62e9e4ca52c5ef1e4fd837dcae27dae3ada599b5a8fd05ac" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "webview2-com-sys" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac48ef20ddf657755fdcda8dfed2a7b4fc7e4581acce6fe9b88c3d64f29dee7" +dependencies = [ + "regex", + "serde", + "serde_json", + "thiserror", + "windows 0.39.0", + "windows-bindgen", + "windows-metadata", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a" +dependencies = [ + "windows-implement", + "windows_aarch64_msvc 0.39.0", + "windows_i686_gnu 0.39.0", + "windows_i686_msvc 0.39.0", + "windows_x86_64_gnu 0.39.0", + "windows_x86_64_msvc 0.39.0", +] + +[[package]] +name = "windows" +version = "0.44.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e745dab35a0c4c77aa3ce42d595e13d2003d6902d6b08c9ef5fc326d08da12b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-bindgen" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68003dbd0e38abc0fb85b939240f4bce37c43a5981d3df37ccbaaa981b47cb41" +dependencies = [ + "windows-metadata", + "windows-tokens", +] + +[[package]] +name = "windows-implement" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba01f98f509cb5dc05f4e5fc95e535f78260f15fea8fe1a8abdd08f774f1cee7" +dependencies = [ + "syn", + "windows-tokens", +] + +[[package]] +name = "windows-metadata" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ee5e275231f07c6e240d14f34e1b635bf1faa1c76c57cfd59a5cdb9848e4278" + +[[package]] +name = "windows-sys" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc 0.42.1", +] + +[[package]] +name = "windows-tokens" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f838de2fe15fe6bac988e74b798f26499a8b21a9d97edec321e79b28d1d7f597" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" + +[[package]] +name = "windows_i686_gnu" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" + +[[package]] +name = "windows_i686_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" + +[[package]] +name = "winnow" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf09497b8f8b5ac5d3bb4d05c0a99be20f26fd3d5f2db7b0716e946d5103658" +dependencies = [ + "memchr", +] + +[[package]] +name = "winres" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b68db261ef59e9e52806f688020631e987592bd83619edccda9c47d42cde4f6c" +dependencies = [ + "toml", +] + +[[package]] +name = "wry" +version = "0.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c1ad8e2424f554cc5bdebe8aa374ef5b433feff817aebabca0389961fc7ef98" +dependencies = [ + "base64 0.13.1", + "block", + "cocoa", + "core-graphics", + "crossbeam-channel", + "dunce", + "gdk", + "gio", + "glib", + "gtk", + "html5ever", + "http", + "kuchiki", + "libc", + "log", + "objc", + "objc_id", + "once_cell", + "serde", + "serde_json", + "sha2", + "soup2", + "tao", + "thiserror", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows 0.39.0", + "windows-implement", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "xattr" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d1526bbe5aaeb5eb06885f4d987bcdfa5e23187055de9b83fe00156a821fabc" +dependencies = [ + "libc", +] diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..c7c15b8 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,12 @@ +[workspace] + +members = [ + "common", + "gpclient", + "gpservice", + "gpgui/src-tauri" +] + +[profile.release] +strip = true +opt-level = "z" diff --git a/GPClient/CMakeLists.txt b/GPClient/CMakeLists.txt deleted file mode 100644 index 94ffaac..0000000 --- a/GPClient/CMakeLists.txt +++ /dev/null @@ -1,109 +0,0 @@ -include("${CMAKE_SOURCE_DIR}/cmake/Add3rdParty.cmake") - -project(GPClient) - -set(gpclient_GENERATED_SOURCES) - -configure_file(com.yuezk.qt.gpclient.desktop.in com.yuezk.qt.gpclient.desktop) -configure_file(com.yuezk.qt.gpclient.metainfo.xml.in com.yuezk.qt.gpclient.metainfo.xml) - -qt5_add_dbus_interface( - gpclient_GENERATED_SOURCES - ${CMAKE_BINARY_DIR}/com.yuezk.qt.GPService.xml - gpserviceinterface -) - -add_executable(gpclient - cdpcommand.cpp - cdpcommandmanager.cpp - enhancedwebview.cpp - gatewayauthenticator.cpp - gatewayauthenticatorparams.cpp - gpgateway.cpp - gphelper.cpp - loginparams.cpp - main.cpp - standardloginwindow.cpp - portalauthenticator.cpp - portalconfigresponse.cpp - preloginresponse.cpp - samlloginwindow.cpp - gpclient.cpp - settingsdialog.cpp - gpclient.ui - standardloginwindow.ui - settingsdialog.ui - challengedialog.h - challengedialog.cpp - challengedialog.ui - vpn_dbus.cpp - vpn_json.cpp - resources.qrc - ${gpclient_GENERATED_SOURCES} -) - -add_3rdparty( - SingleApplication - GIT_REPOSITORY https://github.com/itay-grudev/SingleApplication.git - GIT_TAG v3.3.0 - CMAKE_ARGS - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} - -DCMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH} - -DCMAKE_PREFIX_PATH=$ENV{CMAKE_PREFIX_PATH} - -DQAPPLICATION_CLASS=QApplication -) - -add_3rdparty( - plog - GIT_REPOSITORY https://github.com/SergiusTheBest/plog.git - GIT_TAG master - CMAKE_ARGS - -DPLOG_BUILD_SAMPLES=OFF - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} -) - -ExternalProject_Get_Property(SingleApplication-${PROJECT_NAME} SOURCE_DIR BINARY_DIR) -set(SingleApplication_INCLUDE_DIR ${SOURCE_DIR}) -set(SingleApplication_LIBRARY ${BINARY_DIR}/libSingleApplication.a) - -ExternalProject_Get_Property(plog-${PROJECT_NAME} SOURCE_DIR) -set(plog_INCLUDE_DIR "${SOURCE_DIR}/include") - -add_dependencies(gpclient - SingleApplication-${PROJECT_NAME} - plog-${PROJECT_NAME} -) - -target_include_directories(gpclient PRIVATE - ${CMAKE_BINARY_DIR} - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} - ${SingleApplication_INCLUDE_DIR} - ${plog_INCLUDE_DIR} - ${QTKEYCHAIN_INCLUDE_DIRS}/qt5keychain -) - -target_link_libraries(gpclient - ${SingleApplication_LIBRARY} - Qt5::Widgets - Qt5::Network - Qt5::WebSockets - Qt5::WebEngine - Qt5::WebEngineWidgets - Qt5::DBus - QtSignals - ${QTKEYCHAIN_LIBRARIES} -) - -if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 8.0 AND CMAKE_BUILD_TYPE STREQUAL Release) - target_compile_options(gpclient PUBLIC "-ffile-prefix-map=${CMAKE_SOURCE_DIR}=.") -endif() - -target_compile_definitions(gpclient PUBLIC QAPPLICATION_CLASS=QApplication) - -install(TARGETS gpclient DESTINATION bin) -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.yuezk.qt.gpclient.metainfo.xml" DESTINATION share/metainfo) -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/com.yuezk.qt.gpclient.desktop" DESTINATION share/applications) -install(FILES "com.yuezk.qt.gpclient.svg" DESTINATION share/icons/hicolor/scalable/apps) diff --git a/GPClient/cdpcommand.cpp b/GPClient/cdpcommand.cpp deleted file mode 100644 index 0722aac..0000000 --- a/GPClient/cdpcommand.cpp +++ /dev/null @@ -1,30 +0,0 @@ -#include -#include -#include - -#include "cdpcommand.h" - -CDPCommand::CDPCommand(QObject *parent) : QObject(parent) -{ -} - -CDPCommand::CDPCommand(int id, QString cmd, QVariantMap& params) : - QObject(nullptr), - id(id), - cmd(cmd), - params(¶ms) -{ -} - -QByteArray CDPCommand::toJson() -{ - QVariantMap payloadMap; - payloadMap["id"] = id; - payloadMap["method"] = cmd; - payloadMap["params"] = *params; - - QJsonObject payloadJsonObject = QJsonObject::fromVariantMap(payloadMap); - QJsonDocument payloadJson(payloadJsonObject); - - return payloadJson.toJson(); -} diff --git a/GPClient/cdpcommand.h b/GPClient/cdpcommand.h deleted file mode 100644 index 8f37b7b..0000000 --- a/GPClient/cdpcommand.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef CDPCOMMAND_H -#define CDPCOMMAND_H - -#include - -class CDPCommand : public QObject -{ - Q_OBJECT -public: - explicit CDPCommand(QObject *parent = nullptr); - CDPCommand(int id, QString cmd, QVariantMap& params); - - QByteArray toJson(); - -signals: - void finished(); - -private: - int id; - QString cmd; - QVariantMap *params; -}; - -#endif // CDPCOMMAND_H diff --git a/GPClient/cdpcommandmanager.cpp b/GPClient/cdpcommandmanager.cpp deleted file mode 100644 index 441e192..0000000 --- a/GPClient/cdpcommandmanager.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include -#include - -#include "cdpcommandmanager.h" - -CDPCommandManager::CDPCommandManager(QObject *parent) - : QObject(parent) - , networkManager(new QNetworkAccessManager) - , socket(new QWebSocket) -{ - // WebSocket setup - QObject::connect(socket, &QWebSocket::connected, this, &CDPCommandManager::ready); - QObject::connect(socket, &QWebSocket::textMessageReceived, this, &CDPCommandManager::onTextMessageReceived); - QObject::connect(socket, &QWebSocket::disconnected, this, &CDPCommandManager::onSocketDisconnected); - QObject::connect(socket, QOverload::of(&QWebSocket::error), this, &CDPCommandManager::onSocketError); -} - -CDPCommandManager::~CDPCommandManager() -{ - delete networkManager; - delete socket; -} - -void CDPCommandManager::initialize(QString endpoint) -{ - QNetworkReply *reply = networkManager->get(QNetworkRequest(endpoint)); - - QObject::connect( - reply, &QNetworkReply::finished, - [reply, this]() { - if (reply->error()) { - LOGE << "CDP request error"; - return; - } - - QJsonDocument doc = QJsonDocument::fromJson(reply->readAll()); - QJsonArray pages = doc.array(); - QJsonObject page = pages.first().toObject(); - QString wsUrl = page.value("webSocketDebuggerUrl").toString(); - - socket->open(wsUrl); - } - ); -} - -CDPCommand *CDPCommandManager::sendCommand(QString cmd) -{ - QVariantMap emptyParams; - return sendCommend(cmd, emptyParams); -} - -CDPCommand *CDPCommandManager::sendCommend(QString cmd, QVariantMap ¶ms) -{ - int id = ++commandId; - CDPCommand *command = new CDPCommand(id, cmd, params); - socket->sendTextMessage(command->toJson()); - commandPool.insert(id, command); - - return command; -} - -void CDPCommandManager::onTextMessageReceived(QString message) -{ - QJsonDocument responseDoc = QJsonDocument::fromJson(message.toUtf8()); - QJsonObject response = responseDoc.object(); - - // Response for method - if (response.contains("id")) { - int id = response.value("id").toInt(); - if (commandPool.contains(id)) { - CDPCommand *command = commandPool.take(id); - command->finished(); - } - } else { // Response for event - emit eventReceived(response.value("method").toString(), response.value("params").toObject()); - } -} - -void CDPCommandManager::onSocketDisconnected() -{ - LOGI << "WebSocket disconnected"; -} - -void CDPCommandManager::onSocketError(QAbstractSocket::SocketError error) -{ - LOGE << "WebSocket error" << error; -} diff --git a/GPClient/cdpcommandmanager.h b/GPClient/cdpcommandmanager.h deleted file mode 100644 index ef1238f..0000000 --- a/GPClient/cdpcommandmanager.h +++ /dev/null @@ -1,40 +0,0 @@ -#ifndef CDPCOMMANDMANAGER_H -#define CDPCOMMANDMANAGER_H - -#include -#include -#include -#include - -#include "cdpcommand.h" - -class CDPCommandManager : public QObject -{ - Q_OBJECT -public: - explicit CDPCommandManager(QObject *parent = nullptr); - ~CDPCommandManager(); - - void initialize(QString endpoint); - - CDPCommand *sendCommand(QString cmd); - CDPCommand *sendCommend(QString cmd, QVariantMap& params); - -signals: - void ready(); - void eventReceived(QString eventName, QJsonObject params); - -private: - QNetworkAccessManager *networkManager; - QWebSocket *socket; - - int commandId = 0; - QHash commandPool; - -private slots: - void onTextMessageReceived(QString message); - void onSocketDisconnected(); - void onSocketError(QAbstractSocket::SocketError error); -}; - -#endif // CDPCOMMANDMANAGER_H diff --git a/GPClient/challengedialog.cpp b/GPClient/challengedialog.cpp deleted file mode 100644 index bf842e7..0000000 --- a/GPClient/challengedialog.cpp +++ /dev/null @@ -1,38 +0,0 @@ -#include -#include - -#include "challengedialog.h" -#include "ui_challengedialog.h" - -ChallengeDialog::ChallengeDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::ChallengeDialog) -{ - ui->setupUi(this); - ui->buttonBox->button(QDialogButtonBox::Ok)->setDisabled(true); -} - -ChallengeDialog::~ChallengeDialog() -{ - delete ui; -} - -void ChallengeDialog::setMessage(const QString &message) -{ - ui->challengeMessage->setText(message); -} - -const QString ChallengeDialog::getChallenge() -{ - return ui->challengeInput->text(); -} - -void ChallengeDialog::on_challengeInput_textChanged(const QString &value) -{ - QPushButton *okBtn = ui->buttonBox->button(QDialogButtonBox::Ok); - if (value.isEmpty()) { - okBtn->setDisabled(true); - } else { - okBtn->setEnabled(true); - } -} diff --git a/GPClient/challengedialog.h b/GPClient/challengedialog.h deleted file mode 100644 index bd6b95d..0000000 --- a/GPClient/challengedialog.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef CHALLENGEDIALOG_H -#define CHALLENGEDIALOG_H - -#include - -namespace Ui { -class ChallengeDialog; -} - -class ChallengeDialog : public QDialog -{ - Q_OBJECT - -public: - explicit ChallengeDialog(QWidget *parent = nullptr); - ~ChallengeDialog(); - - void setMessage(const QString &message); - const QString getChallenge(); - -private slots: - void on_challengeInput_textChanged(const QString &arg1); - -private: - Ui::ChallengeDialog *ui; -}; - -#endif // CHALLENGEDIALOG_H diff --git a/GPClient/challengedialog.ui b/GPClient/challengedialog.ui deleted file mode 100644 index 6aafa22..0000000 --- a/GPClient/challengedialog.ui +++ /dev/null @@ -1,111 +0,0 @@ - - - ChallengeDialog - - - - 0 - 0 - 405 - 200 - - - - GlobalProtect Challenge - - - true - - - - - - - - - 14 - 50 - false - - - - Sign In - - - - - - - Duo two-factor login for [redacted] Enter a passcode or select one of the following options: 1. Duo Push to XXX-XXX-[redacted] 2. SMS passcodes to XXX-XXX-[redacted] Passcode or option (1-2): - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop - - - true - - - - - - - - - QLineEdit::Password - - - - - - - Qt::LeftToRight - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - false - - - - - - - - - buttonBox - accepted() - ChallengeDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - ChallengeDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/GPClient/com.yuezk.qt.gpclient.desktop.in b/GPClient/com.yuezk.qt.gpclient.desktop.in deleted file mode 100644 index f15cc09..0000000 --- a/GPClient/com.yuezk.qt.gpclient.desktop.in +++ /dev/null @@ -1,12 +0,0 @@ -[Desktop Entry] - -Type=Application -Version=1.0 -Name=GlobalProtect VPN -Comment=A GlobalProtect VPN client (GUI) for Linux based on OpenConnect and built with Qt5, supports SAML auth mode. -GenericName=GlobalProtect VPN client, supports SAML auth mode -Categories=Network;Dialup; -Exec=env QT_AUTO_SCREEN_SCALE_FACTOR=1 @CMAKE_INSTALL_PREFIX@/bin/gpclient -Icon=com.yuezk.qt.gpclient -Keywords=GlobalProtect;Openconnect;SAML;connection;VPN; -StartupWMClass=gpclient diff --git a/GPClient/com.yuezk.qt.gpclient.metainfo.xml.in b/GPClient/com.yuezk.qt.gpclient.metainfo.xml.in deleted file mode 100644 index 3af48ab..0000000 --- a/GPClient/com.yuezk.qt.gpclient.metainfo.xml.in +++ /dev/null @@ -1,43 +0,0 @@ - - - com.yuezk.qt.gpclient - - globalprotect-openconnect - A GlobalProtect VPN client powered by OpenConnect - - CC0-1.0 - AGPL-3.0-or-later - - -

A GlobalProtect VPN client (GUI) for Linux based on OpenConnect and built with Qt5, supports the SAML auth mode.

-
- - - Network - - - k3vinyue_AT_gmail.com - Kevin Yue - - https://github.com/yuezk/GlobalProtect-openconnect - https://github.com/yuezk/GlobalProtect-openconnect/issues - https://github.com/yuezk/GlobalProtect-openconnect/issues - - - globalprotect - openconnect - vpn - saml - - - com.yuezk.qt.gpclient.desktop - - - https://user-images.githubusercontent.com/3297602/133869036-5c02b0d9-c2d9-4f87-8c81-e44f68cfd6ac.png - - - - @CMAKE_INSTALL_PREFIX@/bin/gpclient - com.yuezk.qt.GPService - -
\ No newline at end of file diff --git a/GPClient/com.yuezk.qt.gpclient.svg b/GPClient/com.yuezk.qt.gpclient.svg deleted file mode 100644 index bccc611..0000000 --- a/GPClient/com.yuezk.qt.gpclient.svg +++ /dev/null @@ -1,99 +0,0 @@ - - - -image/svg+xml - - - - - - - - \ No newline at end of file diff --git a/GPClient/connected.png b/GPClient/connected.png deleted file mode 100644 index e6e4e50..0000000 Binary files a/GPClient/connected.png and /dev/null differ diff --git a/GPClient/enhancedwebview.cpp b/GPClient/enhancedwebview.cpp deleted file mode 100644 index 8415609..0000000 --- a/GPClient/enhancedwebview.cpp +++ /dev/null @@ -1,31 +0,0 @@ -#include -#include - -#include "enhancedwebview.h" -#include "cdpcommandmanager.h" - -EnhancedWebView::EnhancedWebView(QWidget *parent) - : QWebEngineView(parent) - , cdp(new CDPCommandManager) -{ - QObject::connect(cdp, &CDPCommandManager::ready, this, &EnhancedWebView::onCDPReady); - QObject::connect(cdp, &CDPCommandManager::eventReceived, this, &EnhancedWebView::onEventReceived); -} - -void EnhancedWebView::initialize() -{ - auto port = QProcessEnvironment::systemEnvironment().value(ENV_CDP_PORT); - cdp->initialize("http://127.0.0.1:" + port + "/json"); -} - -void EnhancedWebView::onCDPReady() -{ - cdp->sendCommand("Network.enable"); -} - -void EnhancedWebView::onEventReceived(QString eventName, QJsonObject params) -{ - if (eventName == "Network.responseReceived") { - emit responseReceived(params); - } -} diff --git a/GPClient/enhancedwebview.h b/GPClient/enhancedwebview.h deleted file mode 100644 index d402333..0000000 --- a/GPClient/enhancedwebview.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef ENHANCEDWEBVIEW_H -#define ENHANCEDWEBVIEW_H - -#include - -#include "cdpcommandmanager.h" - -#define ENV_CDP_PORT "QTWEBENGINE_REMOTE_DEBUGGING" - -class EnhancedWebView : public QWebEngineView -{ - Q_OBJECT -public: - explicit EnhancedWebView(QWidget *parent = nullptr); - - void initialize(); - -signals: - void responseReceived(QJsonObject params); - -private slots: - void onCDPReady(); - void onEventReceived(QString eventName, QJsonObject params); - -private: - CDPCommandManager *cdp { nullptr }; -}; - -#endif // ENHANCEDWEBVIEW_H diff --git a/GPClient/gatewayauthenticator.cpp b/GPClient/gatewayauthenticator.cpp deleted file mode 100644 index 0d429a5..0000000 --- a/GPClient/gatewayauthenticator.cpp +++ /dev/null @@ -1,226 +0,0 @@ -#include -#include -#include -#include - -#include "gatewayauthenticator.h" -#include "gphelper.h" -#include "loginparams.h" -#include "preloginresponse.h" -#include "challengedialog.h" - -using namespace gpclient::helper; - -GatewayAuthenticator::GatewayAuthenticator(const QString& gateway, GatewayAuthenticatorParams params) - : QObject() - , gateway(gateway) - , params(params) - , preloginUrl("https://" + gateway + "/ssl-vpn/prelogin.esp?tmp=tmp&kerberos-support=yes&ipv6-support=yes&clientVer=4100") - , loginUrl("https://" + gateway + "/ssl-vpn/login.esp") -{ - if (!params.clientos().isEmpty()) { - preloginUrl = preloginUrl + "&clientos=" + params.clientos(); - } -} - -void GatewayAuthenticator::authenticate() -{ - LOGI << "Start gateway authentication..."; - - LoginParams loginParams { params.clientos() }; - loginParams.setUser(params.username()); - loginParams.setPassword(params.password()); - loginParams.setUserAuthCookie(params.userAuthCookie()); - loginParams.setInputStr(params.inputStr()); - - login(loginParams); -} - -void GatewayAuthenticator::login(const LoginParams &loginParams) -{ - LOGI << QString("Trying to login the gateway at %1, with %2").arg(loginUrl).arg(QString(loginParams.toUtf8())); - - auto *reply = createRequest(loginUrl, loginParams.toUtf8()); - connect(reply, &QNetworkReply::finished, this, &GatewayAuthenticator::onLoginFinished); -} - -void GatewayAuthenticator::onLoginFinished() -{ - QNetworkReply *reply = qobject_cast(sender()); - QByteArray response = reply->readAll(); - - if (reply->error() || response.contains("Authentication failure")) { - LOGE << QString("Failed to login the gateway at %1, %2").arg(loginUrl, reply->errorString()); - - if (standardLoginWindow) { - standardLoginWindow->setProcessing(false); - openMessageBox("Gateway login failed.", "Please check your credentials and try again."); - } else { - doAuth(); - } - return; - } - - // 2FA - if (response.contains("Challenge")) { - LOGI << "The server need input the challenge..."; - showChallenge(response); - return; - } - - if (standardLoginWindow) { - standardLoginWindow->close(); - } - - const auto params = gpclient::helper::parseGatewayResponse(response); - emit success(params.toString()); -} - -void GatewayAuthenticator::doAuth() -{ - LOGI << "Perform the gateway prelogin at " << preloginUrl; - - auto *reply = createRequest(preloginUrl); - connect(reply, &QNetworkReply::finished, this, &GatewayAuthenticator::onPreloginFinished); -} - -void GatewayAuthenticator::onPreloginFinished() -{ - auto *reply = qobject_cast(sender()); - - if (reply->error()) { - LOGE << QString("Failed to prelogin the gateway at %1, %2").arg(preloginUrl, reply->errorString()); - - emit fail("Error occurred on the gateway prelogin interface."); - return; - } - - LOGI << "Gateway prelogin succeeded."; - - auto response = PreloginResponse::parse(reply->readAll()); - - if (response.hasSamlAuthFields()) { - samlAuth(response.samlMethod(), response.samlRequest(), reply->url().toString()); - } else if (response.hasNormalAuthFields()) { - normalAuth(response.labelUsername(), response.labelPassword(), response.authMessage()); - } else { - LOGE << QString("Unknown prelogin response for %1, got %2").arg(preloginUrl, QString::fromUtf8(response.rawResponse())); - emit fail("Unknown response for gateway prelogin interface."); - } - - delete reply; -} - -void GatewayAuthenticator::normalAuth(QString labelUsername, QString labelPassword, QString authMessage) -{ - LOGI << QString("Trying to perform the normal login with %1 / %2 credentials").arg(labelUsername, labelPassword); - - standardLoginWindow = new StandardLoginWindow {gateway, labelUsername, labelPassword, authMessage}; - - // Do login - connect(standardLoginWindow, &StandardLoginWindow::performLogin, this, &GatewayAuthenticator::onPerformStandardLogin); - connect(standardLoginWindow, &StandardLoginWindow::rejected, this, &GatewayAuthenticator::onLoginWindowRejected); - connect(standardLoginWindow, &StandardLoginWindow::finished, this, &GatewayAuthenticator::onLoginWindowFinished); - - standardLoginWindow->show(); -} - -void GatewayAuthenticator::onPerformStandardLogin(const QString &username, const QString &password) -{ - LOGI << "Start to perform normal login..."; - - standardLoginWindow->setProcessing(true); - params.setUsername(username); - params.setPassword(password); - - authenticate(); -} - -void GatewayAuthenticator::onLoginWindowRejected() -{ - emit fail(); -} - -void GatewayAuthenticator::onLoginWindowFinished() -{ - delete standardLoginWindow; - standardLoginWindow = nullptr; -} - -void GatewayAuthenticator::samlAuth(QString samlMethod, QString samlRequest, QString preloginUrl) -{ - LOGI << "Trying to perform SAML login with saml-method " << samlMethod; - - auto *loginWindow = new SAMLLoginWindow; - - connect(loginWindow, &SAMLLoginWindow::success, [this, loginWindow](const QMap &samlResult) { - this->onSAMLLoginSuccess(samlResult); - loginWindow->deleteLater(); - }); - connect(loginWindow, &SAMLLoginWindow::fail, [this, loginWindow](const QString &code, const QString &error) { - this->onSAMLLoginFail(code, error); - loginWindow->deleteLater(); - }); - connect(loginWindow, &SAMLLoginWindow::rejected, [this, loginWindow]() { - this->onLoginWindowRejected(); - loginWindow->deleteLater(); - }); - - loginWindow->login(samlMethod, samlRequest, preloginUrl); -} - -void GatewayAuthenticator::onSAMLLoginSuccess(const QMap &samlResult) -{ - if (samlResult.contains("preloginCookie")) { - LOGI << "SAML login succeeded, got the prelogin-cookie " << samlResult.value("preloginCookie"); - } else { - LOGI << "SAML login succeeded, got the portal-userauthcookie " << samlResult.value("userAuthCookie"); - } - - LoginParams loginParams { params.clientos() }; - loginParams.setUser(samlResult.value("username")); - loginParams.setPreloginCookie(samlResult.value("preloginCookie")); - loginParams.setUserAuthCookie(samlResult.value("userAuthCookie")); - - login(loginParams); -} - -void GatewayAuthenticator::onSAMLLoginFail(const QString &code, const QString &msg) -{ - emit fail(msg); -} - -void GatewayAuthenticator::showChallenge(const QString &responseText) -{ - QRegularExpression re("\"(.*?)\";"); - QRegularExpressionMatchIterator i = re.globalMatch(responseText); - - i.next(); // Skip the status value - QString message = i.next().captured(1); - QString inputStr = i.next().captured(1); - // update the inputSrc field - params.setInputStr(inputStr); - - challengeDialog = new ChallengeDialog; - challengeDialog->setMessage(message); - - connect(challengeDialog, &ChallengeDialog::accepted, this, [this] { - params.setPassword(challengeDialog->getChallenge()); - LOGI << "Challenge submitted, try to re-authenticate..."; - authenticate(); - }); - - connect(challengeDialog, &ChallengeDialog::rejected, this, [this] { - if (standardLoginWindow) { - standardLoginWindow->close(); - } - emit fail(); - }); - - connect(challengeDialog, &ChallengeDialog::finished, this, [this] { - delete challengeDialog; - challengeDialog = nullptr; - }); - - challengeDialog->show(); -} diff --git a/GPClient/gatewayauthenticator.h b/GPClient/gatewayauthenticator.h deleted file mode 100644 index e3fba4e..0000000 --- a/GPClient/gatewayauthenticator.h +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef GATEWAYAUTHENTICATOR_H -#define GATEWAYAUTHENTICATOR_H - -#include - -#include "standardloginwindow.h" -#include "challengedialog.h" -#include "loginparams.h" -#include "gatewayauthenticatorparams.h" - -class GatewayAuthenticator : public QObject -{ - Q_OBJECT -public: - explicit GatewayAuthenticator(const QString &gateway, GatewayAuthenticatorParams params); - - void authenticate(); - -signals: - void success(const QString &authCookie); - void fail(const QString &msg = ""); - -private slots: - void onLoginFinished(); - void onPreloginFinished(); - void onPerformStandardLogin(const QString &username, const QString &password); - void onLoginWindowRejected(); - void onLoginWindowFinished(); - void onSAMLLoginSuccess(const QMap &samlResult); - void onSAMLLoginFail(const QString &code, const QString &msg); - -private: - QString gateway; - GatewayAuthenticatorParams params; - QString preloginUrl; - QString loginUrl; - - StandardLoginWindow *standardLoginWindow { nullptr }; - ChallengeDialog *challengeDialog { nullptr }; - - void login(const LoginParams& loginParams); - void doAuth(); - void normalAuth(QString labelUsername, QString labelPassword, QString authMessage); - void samlAuth(QString samlMethod, QString samlRequest, QString preloginUrl = ""); - void showChallenge(const QString &responseText); -}; - -#endif // GATEWAYAUTHENTICATOR_H diff --git a/GPClient/gatewayauthenticatorparams.cpp b/GPClient/gatewayauthenticatorparams.cpp deleted file mode 100644 index 59eb789..0000000 --- a/GPClient/gatewayauthenticatorparams.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "gatewayauthenticatorparams.h" - -GatewayAuthenticatorParams::GatewayAuthenticatorParams() -{ - -} - -GatewayAuthenticatorParams GatewayAuthenticatorParams::fromPortalConfigResponse(const PortalConfigResponse &portalConfig) -{ - GatewayAuthenticatorParams params; - params.setUsername(portalConfig.username()); - params.setPassword(portalConfig.password()); - params.setUserAuthCookie(portalConfig.userAuthCookie()); - - return params; -} - -const QString &GatewayAuthenticatorParams::username() const -{ - return m_username; -} - -void GatewayAuthenticatorParams::setUsername(const QString &newUsername) -{ - m_username = newUsername; -} - -const QString &GatewayAuthenticatorParams::password() const -{ - return m_password; -} - -void GatewayAuthenticatorParams::setPassword(const QString &newPassword) -{ - m_password = newPassword; -} - -const QString &GatewayAuthenticatorParams::userAuthCookie() const -{ - return m_userAuthCookie; -} - -void GatewayAuthenticatorParams::setUserAuthCookie(const QString &newUserAuthCookie) -{ - m_userAuthCookie = newUserAuthCookie; -} - -const QString &GatewayAuthenticatorParams::clientos() const -{ - return m_clientos; -} - -void GatewayAuthenticatorParams::setClientos(const QString &newClientos) -{ - m_clientos = newClientos; -} - -const QString &GatewayAuthenticatorParams::inputStr() const -{ - return m_inputStr; -} - -void GatewayAuthenticatorParams::setInputStr(const QString &inputStr) -{ - m_inputStr = inputStr; -} diff --git a/GPClient/gatewayauthenticatorparams.h b/GPClient/gatewayauthenticatorparams.h deleted file mode 100644 index ec48fc1..0000000 --- a/GPClient/gatewayauthenticatorparams.h +++ /dev/null @@ -1,38 +0,0 @@ -#ifndef GATEWAYAUTHENTICATORPARAMS_H -#define GATEWAYAUTHENTICATORPARAMS_H - -#include - -#include "portalconfigresponse.h" - -class GatewayAuthenticatorParams -{ -public: - GatewayAuthenticatorParams(); - - static GatewayAuthenticatorParams fromPortalConfigResponse(const PortalConfigResponse &portalConfig); - - const QString &username() const; - void setUsername(const QString &newUsername); - - const QString &password() const; - void setPassword(const QString &newPassword); - - const QString &userAuthCookie() const; - void setUserAuthCookie(const QString &newUserAuthCookie); - - const QString &clientos() const; - void setClientos(const QString &newClientos); - - const QString &inputStr() const; - void setInputStr(const QString &inputStr); - -private: - QString m_username; - QString m_password; - QString m_userAuthCookie; - QString m_clientos; - QString m_inputStr; -}; - -#endif // GATEWAYAUTHENTICATORPARAMS_H diff --git a/GPClient/gpclient.cpp b/GPClient/gpclient.cpp deleted file mode 100644 index 6137eff..0000000 --- a/GPClient/gpclient.cpp +++ /dev/null @@ -1,519 +0,0 @@ -#include -#include - -#include "gpclient.h" -#include "gphelper.h" -#include "ui_gpclient.h" -#include "portalauthenticator.h" -#include "gatewayauthenticator.h" -#include "settingsdialog.h" -#include "gatewayauthenticatorparams.h" - -using namespace gpclient::helper; - -GPClient::GPClient(QWidget *parent, IVpn *vpn) - : QMainWindow(parent) - , ui(new Ui::GPClient) - , vpn(vpn) - , settingsDialog(new SettingsDialog(this)) -{ - ui->setupUi(this); - - setWindowTitle("GlobalProtect"); - setFixedSize(width(), height()); - gpclient::helper::moveCenter(this); - - setupSettings(); - - // Restore portal from the previous settings - this->portal(settings::get("portal", "").toString()); - - // DBus service setup - QObject *ov = dynamic_cast(vpn); - connect(ov, SIGNAL(connected()), this, SLOT(onVPNConnected())); - connect(ov, SIGNAL(disconnected()), this, SLOT(onVPNDisconnected())); - connect(ov, SIGNAL(error(QString)), this, SLOT(onVPNError(QString))); - connect(ov, SIGNAL(logAvailable(QString)), this, SLOT(onVPNLogAvailable(QString))); - - // Initialize the context menu of system tray. - initSystemTrayIcon(); - initVpnStatus(); -} - -void GPClient::setupSettings() -{ - settingsButton = new QPushButton(this); - settingsButton->setIcon(QIcon(":/images/settings_icon.png")); - settingsButton->setFixedSize(QSize(28, 28)); - - QRect rect = this->geometry(); - settingsButton->setGeometry( - rect.width() - settingsButton->width() - 15, - 15, - settingsButton->geometry().width(), - settingsButton->geometry().height() - ); - - connect(settingsButton, &QPushButton::clicked, this, &GPClient::onSettingsButtonClicked); - connect(settingsDialog, &QDialog::accepted, this, &GPClient::onSettingsAccepted); -} - -void GPClient::onSettingsButtonClicked() -{ - settingsDialog->setClientos(settings::get("clientos", "Linux").toString()); - settingsDialog->setOsVersion(settings::get("os-version", QSysInfo::prettyProductName()).toString()); - settingsDialog->show(); -} - -void GPClient::onSettingsAccepted() -{ - settings::save("clientos", settingsDialog->clientos()); - settings::save("os-version", settingsDialog->osVersion()); -} - -void GPClient::on_connectButton_clicked() -{ - doConnect(); -} - -void GPClient::on_portalInput_returnPressed() -{ - doConnect(); -} - -void GPClient::on_portalInput_editingFinished() -{ - populateGatewayMenu(); -} - -void GPClient::initSystemTrayIcon() -{ - systemTrayIcon = new QSystemTrayIcon(this); - contextMenu = new QMenu("GlobalProtect", this); - - gatewaySwitchMenu = new QMenu("Switch Gateway", this); - gatewaySwitchMenu->setIcon(QIcon::fromTheme("network-workgroup")); - populateGatewayMenu(); - - systemTrayIcon->setIcon(QIcon(":/images/not_connected.png")); - systemTrayIcon->setToolTip("GlobalProtect"); - systemTrayIcon->setContextMenu(contextMenu); - - connect(systemTrayIcon, &QSystemTrayIcon::activated, this, &GPClient::onSystemTrayActivated); - connect(gatewaySwitchMenu, &QMenu::triggered, this, &GPClient::onGatewayChanged); - - openAction = contextMenu->addAction(QIcon::fromTheme("window-new"), "Open", this, &GPClient::activate); - connectAction = contextMenu->addAction(QIcon::fromTheme("preferences-system-network"), "Connect", this, &GPClient::doConnect); - contextMenu->addMenu(gatewaySwitchMenu); - contextMenu->addSeparator(); - clearAction = contextMenu->addAction(QIcon::fromTheme("edit-clear"), "Reset", this, &GPClient::reset); - quitAction = contextMenu->addAction(QIcon::fromTheme("application-exit"), "Quit", this, &GPClient::quit); - - systemTrayIcon->show(); -} - -void GPClient::initVpnStatus() { - int status = vpn->status(); - - if (status == 1) { - ui->statusLabel->setText("Connecting..."); - updateConnectionStatus(VpnStatus::pending); - } else if (status == 2) { - updateConnectionStatus(VpnStatus::connected); - } else if (status == 3) { - ui->statusLabel->setText("Disconnecting..."); - updateConnectionStatus(VpnStatus::pending); - } else { - updateConnectionStatus(VpnStatus::disconnected); - } -} - -void GPClient::populateGatewayMenu() -{ - LOGI << "Populating the Switch Gateway menu..."; - - const QList gateways = allGateways(); - gatewaySwitchMenu->clear(); - - if (gateways.isEmpty()) { - gatewaySwitchMenu->addAction("")->setData(-1); - return; - } - - const QString currentGatewayName = currentGateway().name(); - for (int i = 0; i < gateways.length(); i++) { - const GPGateway g = gateways.at(i); - QString iconImage = ":/images/radio_unselected.png"; - if (g.name() == currentGatewayName) { - iconImage = ":/images/radio_selected.png"; - } - gatewaySwitchMenu->addAction(QIcon(iconImage), QString("%1 (%2)").arg(g.name(), g.address()))->setData(i); - } -} - -void GPClient::updateConnectionStatus(const GPClient::VpnStatus &status) -{ - switch (status) { - case VpnStatus::disconnected: - ui->statusLabel->setText("Not Connected"); - ui->statusImage->setStyleSheet("image: url(:/images/not_connected.png); padding: 15;"); - ui->connectButton->setText("Connect"); - ui->connectButton->setDisabled(false); - ui->portalInput->setReadOnly(false); - - systemTrayIcon->setIcon(QIcon{ ":/images/not_connected.png" }); - connectAction->setEnabled(true); - connectAction->setText("Connect"); - gatewaySwitchMenu->setEnabled(true); - clearAction->setEnabled(true); - break; - case VpnStatus::pending: - ui->statusImage->setStyleSheet("image: url(:/images/pending.png); padding: 15;"); - ui->connectButton->setDisabled(true); - ui->portalInput->setReadOnly(true); - - systemTrayIcon->setIcon(QIcon{ ":/images/pending.png" }); - connectAction->setEnabled(false); - gatewaySwitchMenu->setEnabled(false); - clearAction->setEnabled(false); - break; - case VpnStatus::connected: - ui->statusLabel->setText("Connected"); - ui->statusImage->setStyleSheet("image: url(:/images/connected.png); padding: 15;"); - ui->connectButton->setText("Disconnect"); - ui->connectButton->setDisabled(false); - ui->portalInput->setReadOnly(true); - - systemTrayIcon->setIcon(QIcon{ ":/images/connected.png" }); - connectAction->setEnabled(true); - connectAction->setText("Disconnect"); - gatewaySwitchMenu->setEnabled(true); - clearAction->setEnabled(false); - break; - default: - break; - } -} - -void GPClient::onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason) -{ - switch (reason) { - case QSystemTrayIcon::Trigger: - case QSystemTrayIcon::DoubleClick: - this->activate(); - break; - default: - break; - } -} - -void GPClient::onGatewayChanged(QAction *action) -{ - const int index = action->data().toInt(); - - if (index == -1) { - return; - } - - const auto g = allGateways().at(index); - - // If the selected gateway is the same as the current gateway - if (g.name() == currentGateway().name()) { - return; - } - - setCurrentGateway(g); - - if (connected()) { - ui->statusLabel->setText("Switching Gateway..."); - ui->connectButton->setEnabled(false); - - vpn->disconnect(); - isSwitchingGateway = true; - } -} - -void GPClient::doConnect() -{ - LOGI << "Start connecting..."; - - const auto btnText = ui->connectButton->text(); - const auto portal = this->portal(); - - // Display the main window if portal is empty - if (portal.isEmpty()) { - activate(); - return; - } - - if (btnText.endsWith("Connect")) { - settings::save("portal", portal); - - // Login to the previously saved gateway - if (!currentGateway().name().isEmpty()) { - LOGI << "Start gateway login using the previously saved gateway..."; - isQuickConnect = true; - gatewayLogin(); - } else { - // Perform the portal login - LOGI << "Start portal login..."; - portalLogin(); - } - } else { - LOGI << "Start disconnecting the VPN..."; - - ui->statusLabel->setText("Disconnecting..."); - updateConnectionStatus(VpnStatus::pending); - vpn->disconnect(); - } -} - -// Login to the portal interface to get the portal config and preferred gateway -void GPClient::portalLogin() -{ - auto *portalAuth = new PortalAuthenticator(portal(), settings::get("clientos", "Linux").toString()); - - connect(portalAuth, &PortalAuthenticator::success, [this, portalAuth](const PortalConfigResponse response, const QString region) { - this->onPortalSuccess(response, region); - portalAuth->deleteLater(); - }); - // Prelogin failed on the portal interface, try to treat the portal as a gateway interface - connect(portalAuth, &PortalAuthenticator::preloginFailed, [this, portalAuth](const QString msg) { - this->onPortalPreloginFail(msg); - portalAuth->deleteLater(); - }); - connect(portalAuth, &PortalAuthenticator::portalConfigFailed, [this, portalAuth](const QString msg) { - this->onPortalConfigFail(msg); - portalAuth->deleteLater(); - }); - // Portal login failed - connect(portalAuth, &PortalAuthenticator::fail, [this, portalAuth](const QString &msg) { - this->onPortalFail(msg); - portalAuth->deleteLater(); - }); - - ui->statusLabel->setText("Authenticating..."); - updateConnectionStatus(VpnStatus::pending); - portalAuth->authenticate(); -} - -void GPClient::onPortalSuccess(const PortalConfigResponse portalConfig, const QString region) -{ - LOGI << "Portal authentication succeeded."; - - // No gateway found in portal configuration - if (portalConfig.allGateways().size() == 0) { - LOGI << "No gateway found in portal configuration, treat the portal address as a gateway."; - tryGatewayLogin(); - return; - } - - GPGateway gateway = filterPreferredGateway(portalConfig.allGateways(), region); - setAllGateways(portalConfig.allGateways()); - setCurrentGateway(gateway); - this->portalConfig = portalConfig; - - gatewayLogin(); -} - -void GPClient::onPortalPreloginFail(const QString msg) -{ - LOGI << "Portal prelogin failed, treat the portal address as a gateway." << msg; - tryGatewayLogin(); -} - -void GPClient::onPortalConfigFail(const QString msg) -{ - LOGI << "Failed to get the portal configuration, " << msg << " Treat the portal address as gateway."; - tryGatewayLogin(); -} - -void GPClient::onPortalFail(const QString &msg) -{ - if (!msg.isEmpty()) { - openMessageBox("Portal authentication failed.", msg); - } - - updateConnectionStatus(VpnStatus::disconnected); -} - -void GPClient::tryGatewayLogin() -{ - LOGI << "Try to perform login on the the gateway interface..."; - - // Treat the portal input as the gateway address - GPGateway g; - g.setName(portal()); - g.setAddress(portal()); - - QList gateways; - gateways.append(g); - - setAllGateways(gateways); - setCurrentGateway(g); - - gatewayLogin(); -} - -// Login to the gateway -void GPClient::gatewayLogin() -{ - LOGI << "Performing gateway login..."; - - GatewayAuthenticatorParams params = GatewayAuthenticatorParams::fromPortalConfigResponse(portalConfig); - params.setClientos(settings::get("clientos", "Linux").toString()); - - GatewayAuthenticator *gatewayAuth; - gatewayAuth = new GatewayAuthenticator(currentGateway().address(), params); - - connect(gatewayAuth, &GatewayAuthenticator::success, [this, gatewayAuth](const QString &authToken) { - this->onGatewaySuccess(authToken); - gatewayAuth->deleteLater(); - }); - connect(gatewayAuth, &GatewayAuthenticator::fail, [this, gatewayAuth](const QString &msg) { - this->onGatewayFail(msg); - gatewayAuth->deleteLater(); - }); - - ui->statusLabel->setText("Authenticating..."); - updateConnectionStatus(VpnStatus::pending); - gatewayAuth->authenticate(); -} - -void GPClient::onGatewaySuccess(const QString &authCookie) -{ - LOGI << "Gateway login succeeded, got the cookie " << authCookie; - - isQuickConnect = false; - QList gatewayAddresses; - for (GPGateway &gw : allGateways()) { - gatewayAddresses.push_back(gw.address()); - } - vpn->connect(currentGateway().address(), gatewayAddresses, portalConfig.username(), authCookie); - ui->statusLabel->setText("Connecting..."); - updateConnectionStatus(VpnStatus::pending); -} - -void GPClient::onGatewayFail(const QString &msg) -{ - // If the quick connect on gateway failed, perform the portal login - if (isQuickConnect && !msg.isEmpty()) { - isQuickConnect = false; - LOGI << "Quick connection failed, trying to portal login..."; - portalLogin(); - return; - } - - if (!msg.isEmpty()) { - openMessageBox("Gateway authentication failed.", msg); - } - - updateConnectionStatus(VpnStatus::disconnected); -} - -void GPClient::activate() -{ - activateWindow(); - showNormal(); -} - -QString GPClient::portal() const -{ - const QString input = ui->portalInput->text().trimmed(); - - if (input.startsWith("http")) { - return QUrl(input).authority(); - } - return input; -} - -void GPClient::portal(QString p) -{ - ui->portalInput->setText(p); -} - -bool GPClient::connected() const -{ - const QString statusText = ui->statusLabel->text(); - return statusText.contains("Connected") && !statusText.contains("Not"); -} - -QList GPClient::allGateways() const -{ - - QList gateways; - - for (auto g :settings::get_all("_gateways$") ){ - - gateways.append(GPGateway::fromJson(settings::get(g).toString())); - } - return gateways; -} - -void GPClient::setAllGateways(QList gateways) -{ - LOGI << "Updating all the gateways..."; - - settings::save(portal() + "_gateways", GPGateway::serialize(gateways)); - populateGatewayMenu(); -} - -GPGateway GPClient::currentGateway() const -{ - const QString selectedGateway = settings::get(portal() + "_selectedGateway").toString(); - - for (auto g : allGateways()) { - if (g.name() == selectedGateway) { - return g; - } - } - return GPGateway{}; -} - -void GPClient::setCurrentGateway(const GPGateway gateway) -{ - LOGI << "Updating the current gateway to " << gateway.name(); - - settings::save(portal() + "_selectedGateway", gateway.name()); - ui->portalInput->setText(gateway.address()); - populateGatewayMenu(); -} - -void GPClient::reset() -{ - settings::clear(); - populateGatewayMenu(); - ui->portalInput->clear(); -} - -void GPClient::quit() -{ - vpn->disconnect(); - QApplication::quit(); -} - -void GPClient::onVPNConnected() -{ - updateConnectionStatus(VpnStatus::connected); -} - -void GPClient::onVPNDisconnected() -{ - updateConnectionStatus(VpnStatus::disconnected); - - if (isSwitchingGateway) { - gatewayLogin(); - isSwitchingGateway = false; - } -} - -void GPClient::onVPNError(QString errorMessage) -{ - updateConnectionStatus(VpnStatus::disconnected); - openMessageBox("Failed to connect", errorMessage); -} - -void GPClient::onVPNLogAvailable(QString log) -{ - LOGI << log; -} diff --git a/GPClient/gpclient.h b/GPClient/gpclient.h deleted file mode 100644 index 20abad4..0000000 --- a/GPClient/gpclient.h +++ /dev/null @@ -1,104 +0,0 @@ -#ifndef GPCLIENT_H -#define GPCLIENT_H - -#include -#include -#include -#include - -#include "portalconfigresponse.h" -#include "settingsdialog.h" -#include "vpn.h" -#include "gatewayauthenticator.h" - -QT_BEGIN_NAMESPACE -namespace Ui { class GPClient; } -QT_END_NAMESPACE - -class GPClient : public QMainWindow -{ - Q_OBJECT - -public: - GPClient(QWidget *parent, IVpn *vpn); - - void activate(); - void quit(); - - QString portal() const; - void portal(QString); - - GPGateway currentGateway() const; - void setCurrentGateway(const GPGateway gateway); - - void doConnect(); - void reset(); - -private slots: - void onSettingsButtonClicked(); - void onSettingsAccepted(); - - void on_connectButton_clicked(); - void on_portalInput_returnPressed(); - void on_portalInput_editingFinished(); - - void onSystemTrayActivated(QSystemTrayIcon::ActivationReason reason); - void onGatewayChanged(QAction *action); - - void onPortalSuccess(const PortalConfigResponse portalConfig, const QString region); - void onPortalPreloginFail(const QString msg); - void onPortalConfigFail(const QString msg); - void onPortalFail(const QString &msg); - - void onGatewaySuccess(const QString &authCookie); - void onGatewayFail(const QString &msg); - - void onVPNConnected(); - void onVPNDisconnected(); - void onVPNError(QString errorMessage); - void onVPNLogAvailable(QString log); - -private: - enum class VpnStatus - { - disconnected, - pending, - connected - }; - - Ui::GPClient *ui; - IVpn *vpn; - - QSystemTrayIcon *systemTrayIcon; - QMenu *contextMenu; - QAction *openAction; - QAction *connectAction; - - QMenu *gatewaySwitchMenu; - QAction *clearAction; - QAction *quitAction; - - SettingsDialog *settingsDialog; - QPushButton *settingsButton; - - bool isQuickConnect { false }; - bool isSwitchingGateway { false }; - PortalConfigResponse portalConfig; - - void setupSettings(); - - void initSystemTrayIcon(); - void initVpnStatus(); - void populateGatewayMenu(); - void updateConnectionStatus(const VpnStatus &status); - - void portalLogin(); - void tryGatewayLogin(); - void gatewayLogin(); - - bool connected() const; - - QList allGateways() const; - void setAllGateways(QList gateways); -}; -#endif // GPCLIENT_H diff --git a/GPClient/gpclient.ui b/GPClient/gpclient.ui deleted file mode 100644 index 0b685d7..0000000 --- a/GPClient/gpclient.ui +++ /dev/null @@ -1,143 +0,0 @@ - - - GPClient - - - - 0 - 0 - 260 - 362 - - - - GlobalProtect OpenConnect - - - - :/images/logo.svg:/images/logo.svg - - - - - - - 22 - 22 - - - - - - 0 - 0 - - - - Qt::LeftToRight - - - - 15 - - - 15 - - - 15 - - - 15 - - - - - 15 - - - - - #statusImage { - image: url(:/images/not_connected.png); - padding: 15 -} - - - - - - - - - - - 14 - 50 - false - false - - - - Not Connected - - - Qt::AlignCenter - - - - - - - - - 0 - - - - - - - - Please enter your portal address - - - - - - - - 0 - 0 - - - - Connect - - - true - - - false - - - - - - - - - <html><head/><body><p align="center"><a href="https://bit.ly/3g5DHqy"><span style=" text-decoration: underline; color:#4c6b8a;">Report a bug</span></a> / <a href="https://bit.ly/3jQYfEi"><span style=" text-decoration: underline; color:#4c6b8a;">Buy me a coffee</span></a></p></body></html> - - - true - - - - - - - - - - - diff --git a/GPClient/gpgateway.cpp b/GPClient/gpgateway.cpp deleted file mode 100644 index 7b587ed..0000000 --- a/GPClient/gpgateway.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include -#include -#include - -#include "gpgateway.h" - -GPGateway::GPGateway() -{ -} - -QString GPGateway::name() const -{ - return _name; -} - -QString GPGateway::address() const -{ - return _address; -} - -void GPGateway::setName(const QString &name) -{ - _name = name; -} - -void GPGateway::setAddress(const QString &address) -{ - _address = address; -} - -void GPGateway::setPriorityRules(const QMap &priorityRules) -{ - _priorityRules = priorityRules; -} - -int GPGateway::priorityOf(QString ruleName) const -{ - if (_priorityRules.contains(ruleName)) { - return _priorityRules.value(ruleName); - } - return 0; -} - -QJsonObject GPGateway::toJsonObject() const -{ - QJsonObject obj; - obj.insert("name", name()); - obj.insert("address", address()); - - return obj; -} - -QString GPGateway::toString() const -{ - QJsonDocument jsonDoc{ toJsonObject() }; - return QString::fromUtf8(jsonDoc.toJson()); -} - -QString GPGateway::serialize(QList &gateways) -{ - QJsonArray arr; - - for (auto g : gateways) { - arr.append(g.toJsonObject()); - } - - QJsonDocument jsonDoc{ arr }; - return QString::fromUtf8(jsonDoc.toJson()); -} - -QList GPGateway::fromJson(const QString &jsonString) -{ - QList gateways; - - if (jsonString.isEmpty()) { - return gateways; - } - - QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonString.toUtf8()); - - for (auto item : jsonDoc.array()) { - GPGateway g = GPGateway::fromJsonObject(item.toObject()); - gateways.append(g); - } - - return gateways; -} - -GPGateway GPGateway::fromJsonObject(const QJsonObject &jsonObj) -{ - GPGateway g; - - g.setName(jsonObj.value("name").toString()); - g.setAddress(jsonObj.value("address").toString()); - - return g; -} diff --git a/GPClient/gpgateway.h b/GPClient/gpgateway.h deleted file mode 100644 index d879259..0000000 --- a/GPClient/gpgateway.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef GPGATEWAY_H -#define GPGATEWAY_H - -#include -#include -#include - -class GPGateway -{ -public: - GPGateway(); - - QString name() const; - QString address() const; - - void setName(const QString &name); - void setAddress(const QString &address); - void setPriorityRules(const QMap &priorityRules); - int priorityOf(QString ruleName) const; - QJsonObject toJsonObject() const; - QString toString() const; - - static QString serialize(QList &gateways); - static QList fromJson(const QString &jsonString); - static GPGateway fromJsonObject(const QJsonObject &jsonObj); - -private: - QString _name; - QString _address; - QMap _priorityRules; -}; - -#endif // GPGATEWAY_H diff --git a/GPClient/gphelper.cpp b/GPClient/gphelper.cpp deleted file mode 100644 index 8e45227..0000000 --- a/GPClient/gphelper.cpp +++ /dev/null @@ -1,178 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "gphelper.h" - -using namespace QKeychain; - -QNetworkAccessManager* gpclient::helper::networkManager = new QNetworkAccessManager; - -QNetworkReply* gpclient::helper::createRequest(QString url, QByteArray params) -{ - QNetworkRequest request(url); - - // Skip the ssl verifying - QSslConfiguration conf = request.sslConfiguration(); - conf.setPeerVerifyMode(QSslSocket::VerifyNone); - conf.setSslOption(QSsl::SslOptionDisableLegacyRenegotiation, false); - request.setSslConfiguration(conf); - - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - request.setHeader(QNetworkRequest::UserAgentHeader, UA); - - if (params == nullptr) { - return networkManager->post(request, QByteArray(nullptr)); - } - return networkManager->post(request, params); -} - -GPGateway gpclient::helper::filterPreferredGateway(QList gateways, const QString ruleName) -{ - LOGI << gateways.size() << " gateway(s) available, filter the gateways with rule: " << ruleName; - - GPGateway gateway = gateways.first(); - - for (GPGateway g : gateways) { - if (g.priorityOf(ruleName) > gateway.priorityOf(ruleName)) { - LOGI << "Find a preferred gateway: " << g.name(); - gateway = g; - } - } - - return gateway; -} - -QUrlQuery gpclient::helper::parseGatewayResponse(const QByteArray &xml) -{ - LOGI << "Start parsing the gateway response..."; - LOGI << "The gateway response is: " << xml; - - QXmlStreamReader xmlReader{xml}; - QList args; - - while (!xmlReader.atEnd()) { - xmlReader.readNextStartElement(); - if (xmlReader.name() == "argument") { - args.append(QUrl::toPercentEncoding(xmlReader.readElementText())); - } - } - - QUrlQuery params{}; - params.addQueryItem("authcookie", args.at(1)); - params.addQueryItem("portal", args.at(3)); - params.addQueryItem("user", args.at(4)); - params.addQueryItem("domain", args.at(7)); - params.addQueryItem("preferred-ip", args.at(15)); - params.addQueryItem("computer", QUrl::toPercentEncoding(QSysInfo::machineHostName())); - - return params; -} - -void gpclient::helper::openMessageBox(const QString &message, const QString& informativeText) -{ - QMessageBox msgBox; - msgBox.setWindowTitle("Notice"); - msgBox.setText(message); - msgBox.setFixedWidth(500); - msgBox.setStyleSheet("QLabel{min-width: 250px}"); - msgBox.setInformativeText(informativeText); - msgBox.exec(); -} - -void gpclient::helper::moveCenter(QWidget *widget) -{ - QDesktopWidget *desktop = QApplication::desktop(); - - int screenWidth, width; - int screenHeight, height; - int x, y; - QSize windowSize; - - screenWidth = desktop->width(); - screenHeight = desktop->height(); - - windowSize = widget->size(); - width = windowSize.width(); - height = windowSize.height(); - - x = (screenWidth - width) / 2; - y = (screenHeight - height) / 2; - y -= 50; - widget->move(x, y); -} - -QSettings *gpclient::helper::settings::_settings = new QSettings("com.yuezk.qt", "GPClient"); - -QVariant gpclient::helper::settings::get(const QString &key, const QVariant &defaultValue) -{ - return _settings->value(key, defaultValue); -} - -QStringList gpclient::helper::settings::get_all(const QString &key, const QVariant &defaultValue) -{ - QRegularExpression re(key); - return _settings->allKeys().filter(re); -} - -void gpclient::helper::settings::save(const QString &key, const QVariant &value) -{ - _settings->setValue(key, value); -} - - -void gpclient::helper::settings::clear() -{ - QStringList keys = _settings->allKeys(); - for (const auto &key : qAsConst(keys)) { - if (!reservedKeys.contains(key)) { - _settings->remove(key); - } - } - - QWebEngineProfile::defaultProfile()->cookieStore()->deleteAllCookies(); -} - - -bool gpclient::helper::settings::secureSave(const QString &key, const QString &value) { - WritePasswordJob job( QLatin1String("gpclient") ); - job.setAutoDelete( false ); - job.setKey( key ); - job.setTextData( value ); - QEventLoop loop; - job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); - job.start(); - loop.exec(); - if ( job.error() ) { - return false; - } - - return true; -} - -bool gpclient::helper::settings::secureGet(const QString &key, QString &value) { - ReadPasswordJob job( QLatin1String("gpclient") ); - job.setAutoDelete( false ); - job.setKey( key ); - QEventLoop loop; - job.connect( &job, SIGNAL(finished(QKeychain::Job*)), &loop, SLOT(quit()) ); - job.start(); - loop.exec(); - - const QString pw = job.textData(); - if ( job.error() ) { - return false; - } - - value = pw; - return true; -} diff --git a/GPClient/gphelper.h b/GPClient/gphelper.h deleted file mode 100644 index 634f82e..0000000 --- a/GPClient/gphelper.h +++ /dev/null @@ -1,47 +0,0 @@ -#ifndef GPHELPER_H -#define GPHELPER_H - -#include -#include -#include -#include -#include -#include - -#include "samlloginwindow.h" -#include "gpgateway.h" - - -const QString UA = "PAN GlobalProtect"; - -namespace gpclient { - namespace helper { - extern QNetworkAccessManager *networkManager; - - QNetworkReply* createRequest(QString url, QByteArray params = nullptr); - - GPGateway filterPreferredGateway(QList gateways, const QString ruleName); - - QUrlQuery parseGatewayResponse(const QByteArray& xml); - - void openMessageBox(const QString& message, const QString& informativeText = ""); - - void moveCenter(QWidget *widget); - - namespace settings { - - extern QSettings *_settings; - static const QStringList reservedKeys {"extraArgs", "clientos"}; - - QVariant get(const QString &key, const QVariant &defaultValue = QVariant()); - QStringList get_all(const QString &key, const QVariant &defaultValue = QVariant()); - void save(const QString &key, const QVariant &value); - void clear(); - - bool secureSave(const QString &key, const QString &value); - bool secureGet(const QString &key, QString &value); - } - } -} - -#endif // GPHELPER_H diff --git a/GPClient/loginparams.cpp b/GPClient/loginparams.cpp deleted file mode 100644 index 21ea259..0000000 --- a/GPClient/loginparams.cpp +++ /dev/null @@ -1,88 +0,0 @@ -#include - -#include "loginparams.h" -#include "gphelper.h" - -using namespace gpclient::helper; - -LoginParams::LoginParams(const QString clientos) -{ - params.addQueryItem("prot", QUrl::toPercentEncoding("https:")); - params.addQueryItem("server", ""); - params.addQueryItem("inputStr", ""); - params.addQueryItem("jnlpReady", "jnlpReady"); - params.addQueryItem("user", ""); - params.addQueryItem("passwd", ""); - params.addQueryItem("computer", QUrl::toPercentEncoding(QSysInfo::machineHostName())); - params.addQueryItem("ok", "Login"); - params.addQueryItem("direct", "yes"); - params.addQueryItem("clientVer", "4100"); - - // add the clientos parameter if not empty - if (!clientos.isEmpty()) { - params.addQueryItem("clientos", clientos); - } - - auto osVersion = settings::get("os-version", "").toString(); - if (osVersion.isEmpty()) { - osVersion = QSysInfo::prettyProductName(); - } - params.addQueryItem("os-version", QUrl::toPercentEncoding(osVersion)); - - params.addQueryItem("portal-userauthcookie", ""); - params.addQueryItem("portal-prelogonuserauthcookie", ""); - params.addQueryItem("prelogin-cookie", ""); - params.addQueryItem("ipv6-support", "yes"); -} - -LoginParams::~LoginParams() -{ -} - -void LoginParams::setUser(const QString user) -{ - updateQueryItem("user", user); -} - -void LoginParams::setServer(const QString server) -{ - updateQueryItem("server", server); -} - -void LoginParams::setPassword(const QString password) -{ - updateQueryItem("passwd", password); -} - -void LoginParams::setUserAuthCookie(const QString cookie) -{ - updateQueryItem("portal-userauthcookie", cookie); -} - -void LoginParams::setPrelogonAuthCookie(const QString cookie) -{ - updateQueryItem("portal-prelogonuserauthcookie", cookie); -} - -void LoginParams::setPreloginCookie(const QString cookie) -{ - updateQueryItem("prelogin-cookie", cookie); -} - -void LoginParams::setInputStr(const QString inputStr) -{ - updateQueryItem("inputStr", inputStr); -} - -QByteArray LoginParams::toUtf8() const -{ - return params.toString().toUtf8(); -} - -void LoginParams::updateQueryItem(const QString key, const QString value) -{ - if (params.hasQueryItem(key)) { - params.removeQueryItem(key); - } - params.addQueryItem(key, QUrl::toPercentEncoding(value)); -} diff --git a/GPClient/loginparams.h b/GPClient/loginparams.h deleted file mode 100644 index 1660ad5..0000000 --- a/GPClient/loginparams.h +++ /dev/null @@ -1,28 +0,0 @@ -#ifndef LOGINPARAMS_H -#define LOGINPARAMS_H - -#include - -class LoginParams -{ -public: - LoginParams(const QString clientos); - ~LoginParams(); - - void setUser(const QString user); - void setServer(const QString server); - void setPassword(const QString password); - void setUserAuthCookie(const QString cookie); - void setPrelogonAuthCookie(const QString cookie); - void setPreloginCookie(const QString cookie); - void setInputStr(const QString inputStr); - - QByteArray toUtf8() const; - -private: - QUrlQuery params; - - void updateQueryItem(const QString key, const QString value); -}; - -#endif // LOGINPARAMS_H diff --git a/GPClient/main.cpp b/GPClient/main.cpp deleted file mode 100644 index 9970681..0000000 --- a/GPClient/main.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -#include "singleapplication.h" -#include "gpclient.h" -#include "vpn_dbus.h" -#include "vpn_json.h" -#include "enhancedwebview.h" -#include "sigwatch.h" -#include "version.h" - -#define QT_AUTO_SCREEN_SCALE_FACTOR "QT_AUTO_SCREEN_SCALE_FACTOR" - -int main(int argc, char *argv[]) -{ - plog::ColorConsoleAppender consoleAppender(plog::streamStdErr); - plog::init(plog::debug, &consoleAppender); - - LOGI << "GlobalProtect started, version: " << VERSION; - - auto port = QString::fromLocal8Bit(qgetenv(ENV_CDP_PORT)); - auto hidpiSupport = QString::fromLocal8Bit(qgetenv(QT_AUTO_SCREEN_SCALE_FACTOR)); - - if (port.isEmpty()) { - qputenv(ENV_CDP_PORT, "12315"); - } - - if (hidpiSupport.isEmpty()) { - qputenv(QT_AUTO_SCREEN_SCALE_FACTOR, "1"); - } - - SingleApplication app(argc, argv); - app.setQuitOnLastWindowClosed(false); - - QCommandLineParser parser; - parser.addHelpOption(); - parser.addVersionOption(); - parser.addPositionalArgument("server", "The URL of the VPN server. Optional."); - parser.addPositionalArgument("gateway", "The URL of the specific VPN gateway. Optional."); - parser.addOptions({ - {"json", "Write the result of the handshake with the GlobalConnect server to stdout as JSON and terminate. Useful for scripting."}, - {"now", "Do not show the dialog with the connect button; connect immediately instead."}, - {"start-minimized", "Launch the client minimized."}, - {"reset", "Reset the client's settings."}, - }); - parser.process(app); - - const auto positional = parser.positionalArguments(); - - auto *vpn = parser.isSet("json") // yes it leaks, but this is cleared on exit anyway - ? static_cast(new VpnJson(nullptr)) // Print to stdout and exit - : static_cast(new VpnDbus(nullptr)); // Contact GPService daemon via dbus - GPClient w(nullptr, vpn); - - if (positional.size() > 0) { - w.portal(positional.at(0)); - } - if (positional.size() > 1) { - GPGateway gw; - gw.setName(positional.at(1)); - gw.setAddress(positional.at(1)); - w.setCurrentGateway(gw); - } - - QObject::connect(&app, &SingleApplication::instanceStarted, &w, &GPClient::activate); - - UnixSignalWatcher sigwatch; - sigwatch.watchForSignal(SIGINT); - sigwatch.watchForSignal(SIGTERM); - sigwatch.watchForSignal(SIGQUIT); - sigwatch.watchForSignal(SIGHUP); - QObject::connect(&sigwatch, &UnixSignalWatcher::unixSignal, &w, &GPClient::quit); - - if (parser.isSet("json")) { - QObject::connect(static_cast(vpn), &VpnJson::connected, &w, &GPClient::quit); - } - - if (parser.isSet("reset")) { - w.reset(); - } - - if (parser.isSet("now")) { - w.doConnect(); - } else if (parser.isSet("start-minimized")) { - w.showMinimized(); - } else { - w.show(); - } - - return app.exec(); -} diff --git a/GPClient/not_connected.png b/GPClient/not_connected.png deleted file mode 100644 index 99adc1b..0000000 Binary files a/GPClient/not_connected.png and /dev/null differ diff --git a/GPClient/pending.png b/GPClient/pending.png deleted file mode 100644 index 2130be7..0000000 Binary files a/GPClient/pending.png and /dev/null differ diff --git a/GPClient/portalauthenticator.cpp b/GPClient/portalauthenticator.cpp deleted file mode 100644 index 6ba00ce..0000000 --- a/GPClient/portalauthenticator.cpp +++ /dev/null @@ -1,219 +0,0 @@ -#include -#include - -#include "portalauthenticator.h" -#include "gphelper.h" -#include "standardloginwindow.h" -#include "samlloginwindow.h" -#include "loginparams.h" -#include "preloginresponse.h" -#include "portalconfigresponse.h" -#include "gpgateway.h" - -using namespace gpclient::helper; - -PortalAuthenticator::PortalAuthenticator(const QString& portal, const QString& clientos) : QObject() - , portal(portal) - , clientos(clientos) - , preloginUrl("https://" + portal + "/global-protect/prelogin.esp?tmp=tmp&kerberos-support=yes&ipv6-support=yes&clientVer=4100") - , configUrl("https://" + portal + "/global-protect/getconfig.esp") -{ - if (!clientos.isEmpty()) { - preloginUrl = preloginUrl + "&clientos=" + clientos; - } -} - -PortalAuthenticator::~PortalAuthenticator() -{ - delete standardLoginWindow; -} - -void PortalAuthenticator::authenticate() -{ - attempts++; - - LOGI << QString("(%1/%2) attempts").arg(attempts).arg(MAX_ATTEMPTS) << ", perform portal prelogin at " << preloginUrl; - - QNetworkReply *reply = createRequest(preloginUrl); - connect(reply, &QNetworkReply::finished, this, &PortalAuthenticator::onPreloginFinished); -} - -void PortalAuthenticator::onPreloginFinished() -{ - auto *reply = qobject_cast(sender()); - - if (reply->error()) { - LOGE << QString("Error occurred while accessing %1, %2").arg(preloginUrl, reply->errorString()); - emit preloginFailed("Error occurred on the portal prelogin interface."); - delete reply; - return; - } - - LOGI << "Portal prelogin succeeded."; - - preloginResponse = PreloginResponse::parse(reply->readAll()); - - LOGI << "Finished parsing the prelogin response. The region field is: " << preloginResponse.region(); - - if (preloginResponse.hasSamlAuthFields()) { - // Do SAML authentication - samlAuth(); - } else if (preloginResponse.hasNormalAuthFields()) { - // Do normal username/password authentication - tryAutoLogin(); - } else { - LOGE << QString("Unknown prelogin response for %1 got %2").arg(preloginUrl).arg(QString::fromUtf8(preloginResponse.rawResponse())); - emit preloginFailed("Unknown response for portal prelogin interface."); - } - - delete reply; -} - -void PortalAuthenticator::tryAutoLogin() -{ - const QString username = settings::get("username").toString(); - const QString password = settings::get("password").toString(); - - if (!username.isEmpty() && !password.isEmpty()) { - LOGI << "Trying auto login using the saved credentials"; - isAutoLogin = true; - fetchConfig(settings::get("username").toString(), settings::get("password").toString()); - } else { - normalAuth(); - } -} - -void PortalAuthenticator::normalAuth() -{ - LOGI << "Trying to launch the normal login window..."; - - standardLoginWindow = new StandardLoginWindow {portal, preloginResponse.labelUsername(), preloginResponse.labelPassword(), preloginResponse.authMessage() }; - - // Do login - connect(standardLoginWindow, &StandardLoginWindow::performLogin, this, &PortalAuthenticator::onPerformNormalLogin); - connect(standardLoginWindow, &StandardLoginWindow::rejected, this, &PortalAuthenticator::onLoginWindowRejected); - connect(standardLoginWindow, &StandardLoginWindow::finished, this, &PortalAuthenticator::onLoginWindowFinished); - - standardLoginWindow->show(); -} - -void PortalAuthenticator::onPerformNormalLogin(const QString &username, const QString &password) -{ - standardLoginWindow->setProcessing(true); - fetchConfig(username, password); -} - -void PortalAuthenticator::onLoginWindowRejected() -{ - emitFail(); -} - -void PortalAuthenticator::onLoginWindowFinished() -{ - delete standardLoginWindow; - standardLoginWindow = nullptr; -} - -void PortalAuthenticator::samlAuth() -{ - LOGI << "Trying to perform SAML login with saml-method " << preloginResponse.samlMethod(); - - auto *loginWindow = new SAMLLoginWindow; - - connect(loginWindow, &SAMLLoginWindow::success, [this, loginWindow](const QMap samlResult) { - this->onSAMLLoginSuccess(samlResult); - loginWindow->deleteLater(); - }); - connect(loginWindow, &SAMLLoginWindow::fail, [this, loginWindow](const QString &code, const QString msg) { - this->onSAMLLoginFail(code, msg); - loginWindow->deleteLater(); - }); - connect(loginWindow, &SAMLLoginWindow::rejected, [this, loginWindow]() { - this->onLoginWindowRejected(); - loginWindow->deleteLater(); - }); - - loginWindow->login(preloginResponse.samlMethod(), preloginResponse.samlRequest(), preloginUrl); -} - -void PortalAuthenticator::onSAMLLoginSuccess(const QMap samlResult) -{ - if (samlResult.contains("preloginCookie")) { - LOGI << "SAML login succeeded, got the prelogin-cookie"; - } else { - LOGI << "SAML login succeeded, got the portal-userauthcookie"; - } - - fetchConfig(samlResult.value("username"), "", samlResult.value("preloginCookie"), samlResult.value("userAuthCookie")); -} - -void PortalAuthenticator::onSAMLLoginFail(const QString &code, const QString &msg) -{ - if (code == "ERR002" && attempts < MAX_ATTEMPTS) { - LOGI << "Failed to authenticate, trying to re-authenticate..."; - authenticate(); - } else { - emitFail(msg); - } -} - -void PortalAuthenticator::fetchConfig(QString username, QString password, QString preloginCookie, QString userAuthCookie) -{ - LoginParams loginParams { clientos }; - loginParams.setServer(portal); - loginParams.setUser(username); - loginParams.setPassword(password); - loginParams.setPreloginCookie(preloginCookie); - loginParams.setUserAuthCookie(userAuthCookie); - - // Save the username and password for future use. - this->username = username; - this->password = password; - - LOGI << "Fetching the portal config from " << configUrl; - - auto *reply = createRequest(configUrl, loginParams.toUtf8()); - connect(reply, &QNetworkReply::finished, this, &PortalAuthenticator::onFetchConfigFinished); -} - -void PortalAuthenticator::onFetchConfigFinished() -{ - QNetworkReply *reply = qobject_cast(sender()); - - if (reply->error()) { - LOGE << QString("Failed to fetch the portal config from %1, %2").arg(configUrl).arg(reply->errorString()); - - // Login failed, enable the fields of the normal login window - if (standardLoginWindow) { - standardLoginWindow->setProcessing(false); - openMessageBox("Portal login failed.", "Please check your credentials and try again."); - } else if (isAutoLogin) { - isAutoLogin = false; - normalAuth(); - } else { - emit portalConfigFailed("Failed to fetch the portal config."); - } - return; - } - - LOGI << "Fetch the portal config succeeded."; - PortalConfigResponse response = PortalConfigResponse::parse(reply->readAll()); - - // Add the username & password to the response object - response.setUsername(username); - response.setPassword(password); - - // Close the login window - if (standardLoginWindow) { - LOGI << "Closing the StandardLoginWindow..."; - - standardLoginWindow->close(); - } - - emit success(response, preloginResponse.region()); -} - -void PortalAuthenticator::emitFail(const QString& msg) -{ - emit fail(msg); -} diff --git a/GPClient/portalauthenticator.h b/GPClient/portalauthenticator.h deleted file mode 100644 index b0dadf8..0000000 --- a/GPClient/portalauthenticator.h +++ /dev/null @@ -1,60 +0,0 @@ -#ifndef PORTALAUTHENTICATOR_H -#define PORTALAUTHENTICATOR_H - -#include - -#include "portalconfigresponse.h" -#include "standardloginwindow.h" -#include "samlloginwindow.h" -#include "preloginresponse.h" - - -class PortalAuthenticator : public QObject -{ - Q_OBJECT -public: - explicit PortalAuthenticator(const QString& portal, const QString& clientos); - ~PortalAuthenticator(); - - void authenticate(); - -signals: - void success(const PortalConfigResponse response, const QString region); - void fail(const QString& msg); - void preloginFailed(const QString& msg); - void portalConfigFailed(const QString msg); - -private slots: - void onPreloginFinished(); - void onPerformNormalLogin(const QString &username, const QString &password); - void onLoginWindowRejected(); - void onLoginWindowFinished(); - void onSAMLLoginSuccess(const QMap samlResult); - void onSAMLLoginFail(const QString &code, const QString &msg); - void onFetchConfigFinished(); - -private: - static const auto MAX_ATTEMPTS{ 5 }; - - QString portal; - QString clientos; - QString preloginUrl; - QString configUrl; - QString username; - QString password; - - int attempts{ 0 }; - PreloginResponse preloginResponse; - - bool isAutoLogin{ false }; - - StandardLoginWindow *standardLoginWindow { nullptr }; - - void tryAutoLogin(); - void normalAuth(); - void samlAuth(); - void fetchConfig(QString username, QString password, QString preloginCookie = "", QString userAuthCookie = ""); - void emitFail(const QString& msg = ""); -}; - -#endif // PORTALAUTHENTICATOR_H diff --git a/GPClient/portalconfigresponse.cpp b/GPClient/portalconfigresponse.cpp deleted file mode 100644 index 45e117d..0000000 --- a/GPClient/portalconfigresponse.cpp +++ /dev/null @@ -1,174 +0,0 @@ -#include -#include - -#include "portalconfigresponse.h" - -QString PortalConfigResponse::xmlUserAuthCookie = "portal-userauthcookie"; -QString PortalConfigResponse::xmlPrelogonUserAuthCookie = "portal-prelogonuserauthcookie"; -QString PortalConfigResponse::xmlGateways = "gateways"; - -PortalConfigResponse::PortalConfigResponse() -{ -} - -PortalConfigResponse::~PortalConfigResponse() -{ -} - -PortalConfigResponse PortalConfigResponse::parse(const QByteArray xml) -{ - LOGI << "Start parsing the portal configuration..."; - - QXmlStreamReader xmlReader(xml); - PortalConfigResponse response; - response.setRawResponse(xml); - - while (!xmlReader.atEnd()) { - xmlReader.readNextStartElement(); - - QString name = xmlReader.name().toString(); - - if (name == xmlUserAuthCookie) { - LOGI << "Start reading " << name; - response.setUserAuthCookie(xmlReader.readElementText()); - } else if (name == xmlPrelogonUserAuthCookie) { - LOGI << "Start reading " << name; - response.setPrelogonUserAuthCookie(xmlReader.readElementText()); - } else if (name == xmlGateways) { - response.setAllGateways(parseGateways(xmlReader)); - } - } - - LOGI << "Finished parsing portal configuration."; - - return response; -} - -const QByteArray PortalConfigResponse::rawResponse() const -{ - return m_rawResponse; -} - -const QString &PortalConfigResponse::username() const -{ - return m_username; -} - -QString PortalConfigResponse::password() const -{ - return m_password; -} - -QList PortalConfigResponse::parseGateways(QXmlStreamReader &xmlReader) -{ - LOGI << "Start parsing the gateways from portal configuration..."; - - QList gateways; - - while (xmlReader.name() != "external"){ - xmlReader.readNext(); - } - - while (xmlReader.name() != "list"){ - xmlReader.readNext(); - } - - while (xmlReader.name() != xmlGateways || !xmlReader.isEndElement()) { - xmlReader.readNext(); - // Parse the gateways -> external -> list -> entry - if (xmlReader.name() == "entry" && xmlReader.isStartElement()) { - GPGateway g; - parseGateway(xmlReader, g); - gateways.append(g); - } - } - - LOGI << "Finished parsing the gateways."; - - return gateways; -} - -void PortalConfigResponse::parseGateway(QXmlStreamReader &reader, GPGateway &gateway) { - LOGI << "Start parsing gateway..."; - - auto finished = false; - while (!finished) { - if (reader.name() == "entry" && reader.isStartElement()) { - auto address = reader.attributes().value("name").toString(); - gateway.setAddress(address); - } else if (reader.name() == "description" && reader.isStartElement()) { // gateway name - gateway.setName(reader.readElementText()); - } else if (reader.name() == "priority-rule" && reader.isStartElement()) { // priority rules - parsePriorityRule(reader, gateway); - } - - auto result = reader.readNext(); - finished = result == QXmlStreamReader::Invalid || (reader.name() == "entry" && reader.isEndElement()); - } -} - -void PortalConfigResponse::parsePriorityRule(QXmlStreamReader &reader, GPGateway &gateway) { - LOGI << "Start parsing priority rule..."; - - QMap priorityRules; - auto finished = false; - - while (!finished) { - // Parse the priority-rule -> entry - if (reader.name() == "entry" && reader.isStartElement()) { - auto ruleName = reader.attributes().value("name").toString(); - // move to the priority value - while (reader.readNextStartElement()) { - if (reader.name() == "priority") { - auto priority = reader.readElementText().toInt(); - priorityRules.insert(ruleName, priority); - break; - } - } - } - auto result = reader.readNext(); - finished = result == QXmlStreamReader::Invalid || (reader.name() == "priority-rule" && reader.isEndElement()); - } - - gateway.setPriorityRules(priorityRules); -} - -QString PortalConfigResponse::userAuthCookie() const -{ - return m_userAuthCookie; -} - -QList PortalConfigResponse::allGateways() const -{ - return m_gateways; -} - -void PortalConfigResponse::setAllGateways(QList gateways) -{ - m_gateways = gateways; -} - -void PortalConfigResponse::setRawResponse(const QByteArray response) -{ - m_rawResponse = response; -} - -void PortalConfigResponse::setUsername(const QString username) -{ - m_username = username; -} - -void PortalConfigResponse::setPassword(const QString password) -{ - m_password = password; -} - -void PortalConfigResponse::setUserAuthCookie(const QString cookie) -{ - m_userAuthCookie = cookie; -} - -void PortalConfigResponse::setPrelogonUserAuthCookie(const QString cookie) -{ - m_prelogonAuthCookie = cookie; -} diff --git a/GPClient/portalconfigresponse.h b/GPClient/portalconfigresponse.h deleted file mode 100644 index bbfda12..0000000 --- a/GPClient/portalconfigresponse.h +++ /dev/null @@ -1,51 +0,0 @@ -#ifndef PORTALCONFIGRESPONSE_H -#define PORTALCONFIGRESPONSE_H - -#include -#include -#include - -#include "gpgateway.h" - -class PortalConfigResponse -{ -public: - PortalConfigResponse(); - ~PortalConfigResponse(); - - static PortalConfigResponse parse(const QByteArray xml); - - const QByteArray rawResponse() const; - const QString &username() const; - QString password() const; - QString userAuthCookie() const; - QList allGateways() const; - void setAllGateways(QList gateways); - - void setUsername(const QString username); - void setPassword(const QString password); - -private: - static QString xmlUserAuthCookie; - static QString xmlPrelogonUserAuthCookie; - static QString xmlGateways; - - QByteArray m_rawResponse; - QString m_username; - QString m_password; - QString m_userAuthCookie; - QString m_prelogonAuthCookie; - - QList m_gateways; - - void setRawResponse(const QByteArray response); - void setUserAuthCookie(const QString cookie); - void setPrelogonUserAuthCookie(const QString cookie); - - static QList parseGateways(QXmlStreamReader &xmlReader); - static void parseGateway(QXmlStreamReader &reader, GPGateway &gateway); - static void parsePriorityRule(QXmlStreamReader &reader, GPGateway &gateway); - -}; - -#endif // PORTALCONFIGRESPONSE_H diff --git a/GPClient/preloginresponse.cpp b/GPClient/preloginresponse.cpp deleted file mode 100644 index 1c378f1..0000000 --- a/GPClient/preloginresponse.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include -#include -#include - -#include "preloginresponse.h" - -QString PreloginResponse::xmlAuthMessage = "authentication-message"; -QString PreloginResponse::xmlLabelUsername = "username-label"; -QString PreloginResponse::xmlLabelPassword = "password-label"; -QString PreloginResponse::xmlSamlMethod = "saml-auth-method"; -QString PreloginResponse::xmlSamlRequest = "saml-request"; -QString PreloginResponse::xmlRegion = "region"; - -PreloginResponse::PreloginResponse() -{ - add(xmlAuthMessage, ""); - add(xmlLabelUsername, ""); - add(xmlLabelPassword, ""); - add(xmlSamlMethod, ""); - add(xmlSamlRequest, ""); - add(xmlRegion, ""); -} - -PreloginResponse PreloginResponse::parse(const QByteArray& xml) -{ - LOGI << "Start parsing the prelogin response..."; - - QXmlStreamReader xmlReader(xml); - PreloginResponse response; - response.setRawResponse(xml); - - while (!xmlReader.atEnd()) { - xmlReader.readNextStartElement(); - QString name = xmlReader.name().toString(); - if (response.has(name)) { - response.add(name, xmlReader.readElementText()); - } - } - return response; -} - -const QByteArray& PreloginResponse::rawResponse() const -{ - return _rawResponse; -} - -QString PreloginResponse::authMessage() const -{ - return resultMap.value(xmlAuthMessage); -} - -QString PreloginResponse::labelUsername() const -{ - return resultMap.value(xmlLabelUsername); -} - -QString PreloginResponse::labelPassword() const -{ - return resultMap.value(xmlLabelPassword); -} - -QString PreloginResponse::samlMethod() const -{ - return resultMap.value(xmlSamlMethod); -} - -QString PreloginResponse::samlRequest() const -{ - return QByteArray::fromBase64(resultMap.value(xmlSamlRequest).toUtf8()); -} - -QString PreloginResponse::region() const -{ - return resultMap.value(xmlRegion); -} - -bool PreloginResponse::hasSamlAuthFields() const -{ - return !samlMethod().isEmpty() && !samlRequest().isEmpty(); -} - -bool PreloginResponse::hasNormalAuthFields() const -{ - return !labelUsername().isEmpty() && !labelPassword().isEmpty(); -} - -void PreloginResponse::setRawResponse(const QByteArray response) -{ - _rawResponse = response; -} - -bool PreloginResponse::has(const QString name) const -{ - return resultMap.contains(name); -} - -void PreloginResponse::add(const QString name, const QString value) -{ - resultMap.insert(name, value); -} diff --git a/GPClient/preloginresponse.h b/GPClient/preloginresponse.h deleted file mode 100644 index 772a037..0000000 --- a/GPClient/preloginresponse.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef PRELOGINRESPONSE_H -#define PRELOGINRESPONSE_H - -#include -#include - -class PreloginResponse -{ -public: - PreloginResponse(); - - static PreloginResponse parse(const QByteArray& xml); - - const QByteArray& rawResponse() const; - QString authMessage() const; - QString labelUsername() const; - QString labelPassword() const; - QString samlMethod() const; - QString samlRequest() const; - QString region() const; - - bool hasSamlAuthFields() const; - bool hasNormalAuthFields() const; - -private: - static QString xmlAuthMessage; - static QString xmlLabelUsername; - static QString xmlLabelPassword; - static QString xmlSamlMethod; - static QString xmlSamlRequest; - static QString xmlRegion; - - QMap resultMap; - QByteArray _rawResponse; - - void setRawResponse(const QByteArray response); - void add(const QString name, const QString value); - bool has(const QString name) const; -}; - -#endif // PRELOGINRESPONSE_H diff --git a/GPClient/radio_selected.png b/GPClient/radio_selected.png deleted file mode 100644 index adc3d69..0000000 Binary files a/GPClient/radio_selected.png and /dev/null differ diff --git a/GPClient/radio_unselected.png b/GPClient/radio_unselected.png deleted file mode 100644 index 4542f73..0000000 Binary files a/GPClient/radio_unselected.png and /dev/null differ diff --git a/GPClient/resources.qrc b/GPClient/resources.qrc deleted file mode 100644 index cfeaa4c..0000000 --- a/GPClient/resources.qrc +++ /dev/null @@ -1,11 +0,0 @@ - - - com.yuezk.qt.gpclient.svg - connected.png - pending.png - not_connected.png - radio_unselected.png - radio_selected.png - settings_icon.png - - diff --git a/GPClient/samlloginwindow.cpp b/GPClient/samlloginwindow.cpp deleted file mode 100644 index d7b8d16..0000000 --- a/GPClient/samlloginwindow.cpp +++ /dev/null @@ -1,136 +0,0 @@ -#include -#include -#include -#include -#include - -#include "samlloginwindow.h" - -SAMLLoginWindow::SAMLLoginWindow(QWidget *parent) - : QDialog(parent) - , webView(new EnhancedWebView(this)) -{ - setWindowTitle("GlobalProtect Login"); - setModal(true); - resize(700, 550); - - QVBoxLayout *verticalLayout = new QVBoxLayout(this); - webView->setUrl(QUrl("about:blank")); - webView->setAttribute(Qt::WA_DeleteOnClose); - verticalLayout->addWidget(webView); - - webView->initialize(); - connect(webView, &EnhancedWebView::responseReceived, this, &SAMLLoginWindow::onResponseReceived); - connect(webView, &EnhancedWebView::loadFinished, this, &SAMLLoginWindow::onLoadFinished); - - // Show the login window automatically when exceeds the MAX_WAIT_TIME - QTimer::singleShot(MAX_WAIT_TIME, this, [this]() { - if (failed) { - return; - } - LOGI << "MAX_WAIT_TIME exceeded, display the login window."; - this->show(); - }); -} - -void SAMLLoginWindow::closeEvent(QCloseEvent *event) -{ - event->accept(); - reject(); -} - -void SAMLLoginWindow::login(const QString samlMethod, const QString samlRequest, const QString preloginUrl) -{ - webView->page()->profile()->cookieStore()->deleteSessionCookies(); - - if (samlMethod == "POST") { - webView->setHtml(samlRequest, preloginUrl); - } else if (samlMethod == "REDIRECT") { - LOGI << "Redirect to " << samlRequest; - webView->load(samlRequest); - } else { - LOGE << "Unknown saml-auth-method expected POST or REDIRECT, got " << samlMethod; - failed = true; - emit fail("ERR001", "Unknown saml-auth-method, got " + samlMethod); - } -} - -void SAMLLoginWindow::onResponseReceived(QJsonObject params) -{ - const auto type = params.value("type").toString(); - // Skip non-document response - if (type != "Document") { - return; - } - - auto response = params.value("response").toObject(); - auto headers = response.value("headers").toObject(); - - LOGI << "Trying to receive authentication cookie from " << response.value("url").toString(); - - const auto username = headers.value("saml-username").toString(); - const auto preloginCookie = headers.value("prelogin-cookie").toString(); - const auto userAuthCookie = headers.value("portal-userauthcookie").toString(); - - this->checkSamlResult(username, preloginCookie, userAuthCookie); -} - -void SAMLLoginWindow::checkSamlResult(QString username, QString preloginCookie, QString userAuthCookie) -{ - LOGI << "Checking the authentication result..."; - - if (!username.isEmpty()) { - samlResult.insert("username", username); - } - - if (!preloginCookie.isEmpty()) { - samlResult.insert("preloginCookie", preloginCookie); - } - - if (!userAuthCookie.isEmpty()) { - samlResult.insert("userAuthCookie", userAuthCookie); - } - - // Check the SAML result - if (samlResult.contains("username") - && (samlResult.contains("preloginCookie") || samlResult.contains("userAuthCookie"))) { - LOGI << "Got the SAML authentication information successfully. " - << "username: " << samlResult.value("username") - << ", preloginCookie: " << samlResult.value("preloginCookie") - << ", userAuthCookie: " << samlResult.value("userAuthCookie"); - - emit success(samlResult); - accept(); - } -} - -void SAMLLoginWindow::onLoadFinished() -{ - LOGI << "Load finished " << webView->page()->url().toString(); - webView->page()->toHtml([this] (const QString &html) { this->handleHtml(html); }); -} - -void SAMLLoginWindow::handleHtml(const QString &html) -{ - // try to check the html body and extract from there - const auto samlAuthStatus = parseTag("saml-auth-status", html); - - if (samlAuthStatus == "1") { - const auto preloginCookie = parseTag("prelogin-cookie", html); - const auto username = parseTag("saml-username", html); - const auto userAuthCookie = parseTag("portal-userauthcookie", html); - - checkSamlResult(username, preloginCookie, userAuthCookie); - } else if (samlAuthStatus == "-1") { - LOGI << "SAML authentication failed..."; - failed = true; - emit fail("ERR002", "Authentication failed, please try again."); - } else { - show(); - } -} - -QString SAMLLoginWindow::parseTag(const QString &tag, const QString &html) { - const QRegularExpression expression(QString("<%1>(.*)").arg(tag)); - return expression.match(html).captured(1); -} diff --git a/GPClient/samlloginwindow.h b/GPClient/samlloginwindow.h deleted file mode 100644 index 805368a..0000000 --- a/GPClient/samlloginwindow.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef SAMLLOGINWINDOW_H -#define SAMLLOGINWINDOW_H - -#include -#include -#include - -#include "enhancedwebview.h" - -class SAMLLoginWindow : public QDialog -{ - Q_OBJECT - -public: - explicit SAMLLoginWindow(QWidget *parent = nullptr); - - void login(const QString samlMethod, const QString samlRequest, const QString preloginUrl); - -signals: - void success(QMap samlResult); - void fail(const QString code, const QString msg); - -private slots: - void onResponseReceived(QJsonObject params); - void onLoadFinished(); - void checkSamlResult(QString username, QString preloginCookie, QString userAuthCookie); - -private: - static const auto MAX_WAIT_TIME { 10 * 1000 }; - - bool failed { false }; - EnhancedWebView *webView { nullptr }; - QMap samlResult; - - void closeEvent(QCloseEvent *event); - void handleHtml(const QString &html); - - static QString parseTag(const QString &tag, const QString &html); -}; - -#endif // SAMLLOGINWINDOW_H diff --git a/GPClient/settings_icon.png b/GPClient/settings_icon.png deleted file mode 100644 index 619a882..0000000 Binary files a/GPClient/settings_icon.png and /dev/null differ diff --git a/GPClient/settingsdialog.cpp b/GPClient/settingsdialog.cpp deleted file mode 100644 index 51e230f..0000000 --- a/GPClient/settingsdialog.cpp +++ /dev/null @@ -1,42 +0,0 @@ -#include "settingsdialog.h" -#include "ui_settingsdialog.h" - -SettingsDialog::SettingsDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::SettingsDialog) -{ - ui->setupUi(this); -} - -SettingsDialog::~SettingsDialog() -{ - delete ui; -} - -void SettingsDialog::setExtraArgs(QString extraArgs) -{ - ui->extraArgsInput->setPlainText(extraArgs); -} - -QString SettingsDialog::extraArgs() -{ - return ui->extraArgsInput->toPlainText().trimmed(); -} - -void SettingsDialog::setClientos(QString clientos) -{ - ui->clientosInput->setText(clientos); -} - -QString SettingsDialog::clientos() -{ - return ui->clientosInput->text(); -} - -void SettingsDialog::setOsVersion(QString osVersion) { - ui->osVersionInput->setText(osVersion); -} - -QString SettingsDialog::osVersion() { - return ui->osVersionInput->text(); -} diff --git a/GPClient/settingsdialog.h b/GPClient/settingsdialog.h deleted file mode 100644 index ab2a607..0000000 --- a/GPClient/settingsdialog.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef SETTINGSDIALOG_H -#define SETTINGSDIALOG_H - -#include - -namespace Ui { -class SettingsDialog; -} - -class SettingsDialog : public QDialog -{ - Q_OBJECT - -public: - explicit SettingsDialog(QWidget *parent = nullptr); - ~SettingsDialog(); - - void setExtraArgs(QString extraArgs); - QString extraArgs(); - - void setClientos(QString clientos); - QString clientos(); - - void setOsVersion(QString osVersion); - QString osVersion(); - -private: - Ui::SettingsDialog *ui; -}; - -#endif // SETTINGSDIALOG_H diff --git a/GPClient/settingsdialog.ui b/GPClient/settingsdialog.ui deleted file mode 100644 index ba27742..0000000 --- a/GPClient/settingsdialog.ui +++ /dev/null @@ -1,117 +0,0 @@ - - - SettingsDialog - - - - 0 - 0 - 488 - 220 - - - - - 0 - 0 - - - - Settings - - - - :/images/connected.png:/images/connected.png - - - - - - Custom Parameters: - - - - - - - true - - - The configuration has been moved to "/etc/gpservice/gp.conf" - - - - - - - clientos: - - - - - - - e.g., Windows - - - - - - - Qt::Horizontal - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - - - - - - - - - os-version: - - - - - - - - - - - buttonBox - accepted() - SettingsDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - SettingsDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - - diff --git a/GPClient/standardloginwindow.cpp b/GPClient/standardloginwindow.cpp deleted file mode 100644 index 7964d1c..0000000 --- a/GPClient/standardloginwindow.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include - -#include "standardloginwindow.h" -#include "ui_standardloginwindow.h" -#include "gphelper.h" - -using namespace gpclient::helper; - -StandardLoginWindow::StandardLoginWindow(const QString &portalAddress, const QString &labelUsername, - const QString &labelPassword, const QString &authMessage) : - QDialog(nullptr), - ui(new Ui::StandardLoginWindow) { - ui->setupUi(this); - ui->portalAddress->setText(portalAddress); - ui->username->setPlaceholderText(labelUsername); - ui->password->setPlaceholderText(labelPassword); - ui->authMessage->setText(authMessage); - - autocomplete(); - - setWindowTitle("GlobalProtect Login"); - setFixedSize(width(), height()); - setModal(true); -} - -void StandardLoginWindow::autocomplete() { - QString username, password; - settings::secureGet("username", username); - settings::secureGet("password", password); - - if (!username.isEmpty() && !password.isEmpty()) { - ui->username->setText(username); - ui->password->setText(password); - } -} - -void StandardLoginWindow::setProcessing(bool isProcessing) { - ui->username->setReadOnly(isProcessing); - ui->password->setReadOnly(isProcessing); - ui->loginButton->setDisabled(isProcessing); -} - -void StandardLoginWindow::on_loginButton_clicked() { - const QString username = ui->username->text().trimmed(); - const QString password = ui->password->text().trimmed(); - - if (username.isEmpty() || password.isEmpty()) { - return; - } - - settings::secureSave("username", username); - settings::secureSave("password", password); - - emit performLogin(username, password); -} - -void StandardLoginWindow::closeEvent(QCloseEvent *event) { - event->accept(); - reject(); -} diff --git a/GPClient/standardloginwindow.h b/GPClient/standardloginwindow.h deleted file mode 100644 index 894e7e3..0000000 --- a/GPClient/standardloginwindow.h +++ /dev/null @@ -1,34 +0,0 @@ -#ifndef STANDARDLOGINWINDOW_H -#define STANDARDLOGINWINDOW_H - -#include - -namespace Ui { - class StandardLoginWindow; -} - -class StandardLoginWindow : public QDialog { -Q_OBJECT - -public: - explicit StandardLoginWindow(const QString &portalAddress, const QString &labelUsername, - const QString &labelPassword, const QString &authMessage); - - void setProcessing(bool isProcessing); - -private slots: - - void on_loginButton_clicked(); - -signals: - - void performLogin(QString username, QString password); - -private: - Ui::StandardLoginWindow *ui; - - void closeEvent(QCloseEvent *event); - void autocomplete(); -}; - -#endif // STANDARDLOGINWINDOW_H diff --git a/GPClient/standardloginwindow.ui b/GPClient/standardloginwindow.ui deleted file mode 100644 index 1190534..0000000 --- a/GPClient/standardloginwindow.ui +++ /dev/null @@ -1,148 +0,0 @@ - - - StandardLoginWindow - - - - 0 - 0 - 255 - 269 - - - - - 0 - 0 - - - - ArrowCursor - - - Login - - - true - - - - - - - - - - - 20 - - - - Login - - - Qt::AlignCenter - - - - - - - true - - - - 0 - 2 - - - - Please enter the login credentials - - - Qt::AlignCenter - - - - - - - - - 0 - - - 6 - - - - - - 0 - 0 - - - - Portal: - - - 0 - - - - - - - - 0 - 0 - - - - vpn.example.com - - - - - - - - - - - Username - - - - - - - - - - QLineEdit::Password - - - Password - - - false - - - - - - - Login - - - - - - - - - - - - diff --git a/GPClient/vpn.h b/GPClient/vpn.h deleted file mode 100644 index b389edd..0000000 --- a/GPClient/vpn.h +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef VPN_H -#define VPN_H -#include -#include - -class IVpn -{ -public: - virtual ~IVpn() = default; - - virtual void connect(const QString &preferredServer, const QList &servers, const QString &username, const QString &passwd) = 0; - virtual void disconnect() = 0; - virtual int status() = 0; - -// signals: // SIGNALS -// virtual void connected(); -// virtual void disconnected(); -// virtual void error(const QString &errorMessage); -// virtual void logAvailable(const QString &log); -}; - -Q_DECLARE_INTERFACE(IVpn, "IVpn") // define this out of namespace scope - -#endif diff --git a/GPClient/vpn_dbus.cpp b/GPClient/vpn_dbus.cpp deleted file mode 100644 index 937d15d..0000000 --- a/GPClient/vpn_dbus.cpp +++ /dev/null @@ -1,13 +0,0 @@ -#include "vpn_dbus.h" - -void VpnDbus::connect(const QString &preferredServer, const QList &servers, const QString &username, const QString &passwd) { - inner->connect(preferredServer, username, passwd); -} - -void VpnDbus::disconnect() { - inner->disconnect(); -} - -int VpnDbus::status() { - return inner->status(); -} diff --git a/GPClient/vpn_dbus.h b/GPClient/vpn_dbus.h deleted file mode 100644 index 107e3d8..0000000 --- a/GPClient/vpn_dbus.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef VPN_DBUS_H -#define VPN_DBUS_H -#include "vpn.h" -#include "gpserviceinterface.h" - -class VpnDbus : public QObject, public IVpn -{ - Q_OBJECT - Q_INTERFACES(IVpn) - -private: - com::yuezk::qt::GPService *inner; - -public: - VpnDbus(QObject *parent) : QObject(parent) { - inner = new com::yuezk::qt::GPService("com.yuezk.qt.GPService", "/", QDBusConnection::systemBus(), this); - QObject::connect(inner, &com::yuezk::qt::GPService::connected, this, &VpnDbus::connected); - QObject::connect(inner, &com::yuezk::qt::GPService::disconnected, this, &VpnDbus::disconnected); - QObject::connect(inner, &com::yuezk::qt::GPService::error, this, &VpnDbus::error); - QObject::connect(inner, &com::yuezk::qt::GPService::logAvailable, this, &VpnDbus::logAvailable); - } - - void connect(const QString &preferredServer, const QList &servers, const QString &username, const QString &passwd); - void disconnect(); - int status(); - -signals: // SIGNALS - void connected(); - void disconnected(); - void error(QString errorMessage); - void logAvailable(QString log); -}; -#endif diff --git a/GPClient/vpn_json.cpp b/GPClient/vpn_json.cpp deleted file mode 100644 index dcfe213..0000000 --- a/GPClient/vpn_json.cpp +++ /dev/null @@ -1,24 +0,0 @@ -#include "vpn_json.h" -#include -#include -#include -#include - -void VpnJson::connect(const QString &preferredServer, const QList &servers, const QString &username, const QString &passwd) { - QJsonArray sl; - for (const QString &srv : servers) { - sl.push_back(QJsonValue(srv)); - } - QJsonObject j; - j["server"] = preferredServer; - j["availableServers"] = sl; - j["cookie"] = passwd; - QTextStream(stdout) << QJsonDocument(j).toJson(QJsonDocument::Compact) << "\n"; - emit connected(); -} - -void VpnJson::disconnect() { /* nop */ } - -int VpnJson::status() { - return 4; // disconnected -} diff --git a/GPClient/vpn_json.h b/GPClient/vpn_json.h deleted file mode 100644 index 8fbbfe0..0000000 --- a/GPClient/vpn_json.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef VPN_JSON_H -#define VPN_JSON_H -#include "vpn.h" - -class VpnJson : public QObject, public IVpn -{ - Q_OBJECT - Q_INTERFACES(IVpn) - -public: - VpnJson(QObject *parent) : QObject(parent) {} - - void connect(const QString &preferredServer, const QList &servers, const QString &username, const QString &passwd); - void disconnect(); - int status(); - -signals: // SIGNALS - void connected(); - void disconnected(); - void error(const QString &errorMessage); - void logAvailable(const QString &log); -}; -#endif diff --git a/GPService/CMakeLists.txt b/GPService/CMakeLists.txt deleted file mode 100644 index 7f20655..0000000 --- a/GPService/CMakeLists.txt +++ /dev/null @@ -1,83 +0,0 @@ -include("${CMAKE_SOURCE_DIR}/cmake/Add3rdParty.cmake") - -project(GPService) - -set(gpservice_GENERATED_SOURCES) - -execute_process(COMMAND logname OUTPUT_VARIABLE CMAKE_LOGNAME) -string(STRIP "${CMAKE_LOGNAME}" CMAKE_LOGNAME) - -message(STATUS "CMAKE_LOGNAME: ${CMAKE_LOGNAME}") - -configure_file(dbus/com.yuezk.qt.GPService.conf.in dbus/com.yuezk.qt.GPService.conf) -configure_file(dbus/com.yuezk.qt.GPService.service.in dbus/com.yuezk.qt.GPService.service) -configure_file(systemd/gpservice.service.in systemd/gpservice.service) - -# generate the dbus xml definition -qt5_generate_dbus_interface( - gpservice.h - ${CMAKE_BINARY_DIR}/com.yuezk.qt.GPService.xml -) - -# generate dbus adaptor -qt5_add_dbus_adaptor( - gpservice_GENERATED_SOURCES - ${CMAKE_BINARY_DIR}/com.yuezk.qt.GPService.xml - gpservice.h - GPService -) - -add_executable(gpservice - gpservice.h - gpservice.cpp - main.cpp - ${gpservice_GENERATED_SOURCES} -) - -add_3rdparty( - SingleApplication - GIT_REPOSITORY https://github.com/itay-grudev/SingleApplication.git - GIT_TAG v3.3.0 - CMAKE_ARGS - -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} - -DCMAKE_CXX_FLAGS_RELEASE=${CMAKE_CXX_FLAGS_RELEASE} - -DCMAKE_FIND_ROOT_PATH=${CMAKE_FIND_ROOT_PATH} - -DCMAKE_PREFIX_PATH=$ENV{CMAKE_PREFIX_PATH} - -DQAPPLICATION_CLASS=QCoreApplication -) - -ExternalProject_Get_Property(SingleApplication-${PROJECT_NAME} SOURCE_DIR BINARY_DIR) - -set(SingleApplication_INCLUDE_DIR ${SOURCE_DIR}) -set(SingleApplication_LIBRARY ${BINARY_DIR}/libSingleApplication.a) - -add_dependencies(gpservice SingleApplication-${PROJECT_NAME}) - -target_include_directories(gpservice PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_CURRENT_BINARY_DIR} - ${SingleApplication_INCLUDE_DIR} -) - -target_link_libraries(gpservice - ${SingleApplication_LIBRARY} - Qt5::Core - Qt5::Network - Qt5::DBus - QtSignals - inih -) - -target_compile_definitions(gpservice PUBLIC QAPPLICATION_CLASS=QCoreApplication) - -install(TARGETS gpservice DESTINATION bin) -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/dbus/com.yuezk.qt.GPService.conf" DESTINATION share/dbus-1/system.d ) -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/dbus/com.yuezk.qt.GPService.service" DESTINATION share/dbus-1/system-services) -install(FILES "gp.conf" DESTINATION /etc/gpservice) - -if("$ENV{DEBIAN_PACKAGE}") - # Install the systemd unit files to /lib/systemd/system for debian package - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/systemd/gpservice.service" DESTINATION /lib/systemd/system) -else() - install(FILES "${CMAKE_CURRENT_BINARY_DIR}/systemd/gpservice.service" DESTINATION lib/systemd/system) -endif() diff --git a/GPService/dbus/com.yuezk.qt.GPService.conf.in b/GPService/dbus/com.yuezk.qt.GPService.conf.in deleted file mode 100644 index 41069f1..0000000 --- a/GPService/dbus/com.yuezk.qt.GPService.conf.in +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - diff --git a/GPService/dbus/com.yuezk.qt.GPService.service.in b/GPService/dbus/com.yuezk.qt.GPService.service.in deleted file mode 100644 index ac21bea..0000000 --- a/GPService/dbus/com.yuezk.qt.GPService.service.in +++ /dev/null @@ -1,5 +0,0 @@ -[D-BUS Service] -Name=com.yuezk.qt.GPService -Exec=@CMAKE_INSTALL_PREFIX@/bin/gpservice -User=root -SystemdService=gpservice.service diff --git a/GPService/gp.conf b/GPService/gp.conf deleted file mode 100644 index 7d7f824..0000000 --- a/GPService/gp.conf +++ /dev/null @@ -1,17 +0,0 @@ -# Configuration file for GlobalProtect-openconnect -# -# Description: -# -# Each section is a VPN gateway address, and [*] is a special section that defines the default configuration. -# See https://github.com/yuezk/GlobalProtect-openconnect/wiki/Configuration for more details. -# -# Example: -# -# [*] -# openconnect-args= -# -# [vpn1.company.com] -# openconnect-args=--script=/path/to/vpnc-script - -[*] -openconnect-args= diff --git a/GPService/gpservice.cpp b/GPService/gpservice.cpp deleted file mode 100644 index acfb646..0000000 --- a/GPService/gpservice.cpp +++ /dev/null @@ -1,229 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "INIReader.h" -#include "gpservice.h" -#include "gpserviceadaptor.h" - -GPService::GPService(QObject *parent) - : QObject(parent) - , openconnect(new QProcess) -{ - // Register the DBus service - new GPServiceAdaptor(this); - QDBusConnection dbus = QDBusConnection::systemBus(); - dbus.registerObject("/", this); - dbus.registerService("com.yuezk.qt.GPService"); - - // Setup the openconnect process - QObject::connect(openconnect, &QProcess::started, this, &GPService::onProcessStarted); - QObject::connect(openconnect, &QProcess::errorOccurred, this, &GPService::onProcessError); - QObject::connect(openconnect, &QProcess::readyReadStandardOutput, this, &GPService::onProcessStdout); - QObject::connect(openconnect, &QProcess::readyReadStandardError, this, &GPService::onProcessStderr); - QObject::connect(openconnect, QOverload::of(&QProcess::finished), this, &GPService::onProcessFinished); -} - -GPService::~GPService() -{ - delete openconnect; -} - -QString GPService::findBinary() -{ - for (auto& binaryPath : binaryPaths) { - if (QFileInfo::exists(binaryPath)) { - return binaryPath; - } - } - return nullptr; -} - -QString GPService::extraOpenconnectArgs(const QString &gateway) -{ - INIReader reader("/etc/gpservice/gp.conf"); - - if (reader.ParseError() < 0) { - return ""; - } - - std::string defaultArgs = reader.Get("*", "openconnect-args", ""); - std::string extraArgs = reader.Get(gateway.toStdString(), "openconnect-args", defaultArgs); - - return QString::fromStdString(extraArgs); -} - -/* Port from https://github.com/qt/qtbase/blob/11d1dcc6e263c5059f34b44d531c9ccdf7c0b1d6/src/corelib/io/qprocess.cpp#L2115 */ -QStringList GPService::splitCommand(const QString &command) -{ - QStringList args; - QString tmp; - int quoteCount = 0; - bool inQuote = false; - - // handle quoting. tokens can be surrounded by double quotes - // "hello world". three consecutive double quotes represent - // the quote character itself. - for (int i = 0; i < command.size(); ++i) { - if (command.at(i) == QLatin1Char('"')) { - ++quoteCount; - if (quoteCount == 3) { - // third consecutive quote - quoteCount = 0; - tmp += command.at(i); - } - continue; - } - if (quoteCount) { - if (quoteCount == 1) - inQuote = !inQuote; - quoteCount = 0; - } - if (!inQuote && command.at(i).isSpace()) { - if (!tmp.isEmpty()) { - args += tmp; - tmp.clear(); - } - } else { - tmp += command.at(i); - } - } - if (!tmp.isEmpty()) - args += tmp; - - return args; -} - -void GPService::quit() -{ - if (openconnect->state() == QProcess::NotRunning) { - exit(0); - } else { - aboutToQuit = true; - openconnect->terminate(); - } -} - -void GPService::connect(QString server, QString username, QString passwd) -{ - if (vpnStatus != GPService::VpnNotConnected) { - log("VPN status is: " + QVariant::fromValue(vpnStatus).toString()); - return; - } - - QString bin = findBinary(); - if (bin == nullptr) { - log("Could not find openconnect binary, make sure openconnect is installed, exiting."); - emit error("The OpenConect CLI was not found, make sure it has been installed!"); - return; - } - - if (!isValidVersion(bin)) { - return; - } - - const QString extraArgs = extraOpenconnectArgs(server); - log(QString("Got extra OpenConnect args for server: %1, %2").arg(server, extraArgs.isEmpty() ? "" : extraArgs)); - - QStringList args; - args << QCoreApplication::arguments().mid(1) - << "--protocol=gp" - << splitCommand(extraArgs) - << "-u" << username - << "--cookie-on-stdin" - << server; - - log("Start process with arguments: " + args.join(", ")); - - openconnect->start(bin, args); - openconnect->write((passwd + "\n").toUtf8()); -} - -bool GPService::isValidVersion(QString &bin) { - QProcess p; - p.start(bin, QStringList("--version")); - p.waitForFinished(); - QString output = p.readAllStandardError() + p.readAllStandardOutput(); - - QRegularExpression re("v(\\d+).*?(\\s|\\n)"); - QRegularExpressionMatch match = re.match(output); - - if (match.hasMatch()) { - log("Output of `openconnect --version`: " + output); - - QString fullVersion = match.captured(0); - QString majorVersion = match.captured(1); - - if (majorVersion.toInt() < 8) { - emit error("The OpenConnect version must greater than v8.0.0, got " + fullVersion); - return false; - } - } else { - log("Failed to parse the OpenConnect version from " + output); - } - - return true; -} - -void GPService::disconnect() -{ - if (openconnect->state() != QProcess::NotRunning) { - vpnStatus = GPService::VpnDisconnecting; - openconnect->terminate(); - } -} - -int GPService::status() -{ - return vpnStatus; -} - -void GPService::onProcessStarted() -{ - log("Openconnect started successfully, PID=" + QString::number(openconnect->processId())); - vpnStatus = GPService::VpnConnecting; -} - -void GPService::onProcessError(QProcess::ProcessError error) -{ - log("Error occurred: " + QVariant::fromValue(error).toString()); - vpnStatus = GPService::VpnNotConnected; - emit disconnected(); -} - -void GPService::onProcessStdout() -{ - QString output = openconnect->readAllStandardOutput(); - - log(output); - if (output.indexOf("Connected as") >= 0 || - output.indexOf("Configured as") >= 0 || - output.indexOf("Configurado como") >= 0) { - vpnStatus = GPService::VpnConnected; - emit connected(); - } -} - -void GPService::onProcessStderr() -{ - log(openconnect->readAllStandardError()); -} - -void GPService::onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) -{ - log("Openconnect process exited with code " + QString::number(exitCode) + " and exit status " + QVariant::fromValue(exitStatus).toString()); - vpnStatus = GPService::VpnNotConnected; - emit disconnected(); - - if (aboutToQuit) { - exit(0); - }; -} - -void GPService::log(QString msg) -{ - emit logAvailable(msg); -} diff --git a/GPService/gpservice.h b/GPService/gpservice.h deleted file mode 100644 index 22e9fd4..0000000 --- a/GPService/gpservice.h +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef GLOBALPROTECTSERVICE_H -#define GLOBALPROTECTSERVICE_H - -#include -#include - -static QList binaryPaths = QList() << - "/usr/local/bin/openconnect" << - "/usr/local/sbin/openconnect" << - "/usr/bin/openconnect" << - "/usr/sbin/openconnect" << - "/opt/bin/openconnect" << - "/opt/sbin/openconnect"; - -class GPService : public QObject -{ - Q_OBJECT - Q_CLASSINFO("D-Bus Interface", "com.yuezk.qt.GPService") -public: - explicit GPService(QObject *parent = nullptr); - ~GPService(); - - void quit(); - - enum VpnStatus { - VpnNotConnected, - VpnConnecting, - VpnConnected, - VpnDisconnecting, - }; - -signals: - void connected(); - void disconnected(); - void error(QString errorMessage); - void logAvailable(QString log); - -public slots: - void connect(QString server, QString username, QString passwd); - void disconnect(); - int status(); - -private slots: - void onProcessStarted(); - void onProcessError(QProcess::ProcessError error); - void onProcessStdout(); - void onProcessStderr(); - void onProcessFinished(int exitCode, QProcess::ExitStatus exitStatus); - -private: - QProcess *openconnect; - bool aboutToQuit = false; - int vpnStatus = GPService::VpnNotConnected; - - void log(QString msg); - bool isValidVersion(QString &bin); - static QString findBinary(); - static QString extraOpenconnectArgs(const QString &gateway); - static QStringList splitCommand(const QString &command); -}; - -#endif // GLOBALPROTECTSERVICE_H diff --git a/GPService/main.cpp b/GPService/main.cpp deleted file mode 100644 index 06f8743..0000000 --- a/GPService/main.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include - -#include "gpservice.h" -#include "singleapplication.h" -#include "sigwatch.h" - -int main(int argc, char *argv[]) -{ - SingleApplication app(argc, argv); - - if (!QDBusConnection::systemBus().isConnected()) { - qWarning("Cannot connect to the D-Bus session bus.\n" - "Please check your system settings and try again.\n"); - return 1; - } - - GPService service; - - UnixSignalWatcher sigwatch; - sigwatch.watchForSignal(SIGINT); - sigwatch.watchForSignal(SIGTERM); - sigwatch.watchForSignal(SIGQUIT); - sigwatch.watchForSignal(SIGHUP); - QObject::connect(&sigwatch, &UnixSignalWatcher::unixSignal, &service, &GPService::quit); - - return app.exec(); -} diff --git a/GPService/systemd/gpservice.service.in b/GPService/systemd/gpservice.service.in deleted file mode 100644 index 29beb08..0000000 --- a/GPService/systemd/gpservice.service.in +++ /dev/null @@ -1,11 +0,0 @@ -[Unit] -Description=GlobalProtect openconnect DBus service - -[Service] -Environment="LANG=en_US.utf8" -Type=dbus -BusName=com.yuezk.qt.GPService -ExecStart=@CMAKE_INSTALL_PREFIX@/bin/gpservice - -[Install] -WantedBy=multi-user.target diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f288702..0000000 --- a/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If 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 convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state 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 program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program 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 General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/README.md b/README.md index 5ae12b4..73f3e42 100644 --- a/README.md +++ b/README.md @@ -1,193 +1,9 @@ -# GlobalProtect-openconnect -A GlobalProtect VPN client (GUI) for Linux based on Openconnect and built with Qt5, supports SAML auth mode, inspired by [gp-saml-gui](https://github.com/dlenski/gp-saml-gui). +## Development -

- -

+### Start the GUI -Buy me a coffee via Paypal -Support me on Ko-fi -Buy Me A Coffee - - -## Features - -- Similar user experience as the official client in macOS. -- Supports both SAML and non-SAML authentication modes. -- Supports automatically selecting the preferred gateway from the multiple gateways. -- Supports switching gateway from the system tray menu manually. - - -## Install - -|OS|Stable version | Development version| -|---|--------------|--------------------| -|Linux Mint, Ubuntu 18.04 or later|[ppa:yuezk/globalprotect-openconnect](https://launchpad.net/~yuezk/+archive/ubuntu/globalprotect-openconnect)|[ppa:yuezk/globalprotect-openconnect-snapshot](https://launchpad.net/~yuezk/+archive/ubuntu/globalprotect-openconnect-snapshot)| -|Arch, Manjaro|[globalprotect-openconnect](https://archlinux.org/packages/community/x86_64/globalprotect-openconnect/)|[AUR: globalprotect-openconnect-git](https://aur.archlinux.org/packages/globalprotect-openconnect-git/)| -|Fedora|[copr: yuezk/globalprotect-openconnect](https://copr.fedorainfracloud.org/coprs/yuezk/globalprotect-openconnect/)|[copr: yuezk/globalprotect-openconnect](https://copr.fedorainfracloud.org/coprs/yuezk/globalprotect-openconnect/)| -|openSUSE, CentOS 8|[OBS: globalprotect-openconnect](https://build.opensuse.org/package/show/home:yuezk/globalprotect-openconnect)|[OBS: globalprotect-openconnect-snapshot](https://build.opensuse.org/package/show/home:yuezk/globalprotect-openconnect-snapshot)| - -Add the repository in the above table and install it with your favorite package manager tool. - -[![Arch package](https://repology.org/badge/version-for-repo/arch/globalprotect-openconnect.svg)](https://repology.org/project/globalprotect-openconnect/versions) -[![AUR package](https://repology.org/badge/version-for-repo/aur/globalprotect-openconnect.svg)](https://repology.org/project/globalprotect-openconnect/versions) -[![Manjaro Stable package](https://repology.org/badge/version-for-repo/manjaro_stable/globalprotect-openconnect.svg)](https://repology.org/project/globalprotect-openconnect/versions) -[![Manjaro Testing package](https://repology.org/badge/version-for-repo/manjaro_testing/globalprotect-openconnect.svg)](https://repology.org/project/globalprotect-openconnect/versions) -[![Manjaro Unstable package](https://repology.org/badge/version-for-repo/manjaro_unstable/globalprotect-openconnect.svg)](https://repology.org/project/globalprotect-openconnect/versions) -[![nixpkgs unstable package](https://repology.org/badge/version-for-repo/nix_unstable/globalprotect-openconnect.svg)](https://repology.org/project/globalprotect-openconnect/versions) -[![Parabola package](https://repology.org/badge/version-for-repo/parabola/globalprotect-openconnect.svg)](https://repology.org/project/globalprotect-openconnect/versions) - -### Linux Mint, Ubuntu 18.04 or later - -```sh -sudo add-apt-repository ppa:yuezk/globalprotect-openconnect -sudo apt-get update -sudo apt-get install globalprotect-openconnect ``` - -> For Linux Mint, you might need to import the GPG key with: `sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 7937C393082992E5D6E4A60453FC26B43838D761` if you encountered an error `gpg: keyserver receive failed: General error`. - -### Arch Linux / Manjaro - -```sh -sudo pacman -S globalprotect-openconnect +cd gpgui +pnpm install +pnpm tauri dev ``` - -### AUR snapshot version - -```sh -yay -S globalprotect-openconnect-git -``` - -### Fedora - -```sh -sudo dnf copr enable yuezk/globalprotect-openconnect -sudo dnf install globalprotect-openconnect -``` - -### openSUSE - -- openSUSE Tumbleweed - ```sh - sudo zypper ar https://download.opensuse.org/repositories/home:/yuezk/openSUSE_Tumbleweed/home:yuezk.repo - sudo zypper ref - sudo zypper install globalprotect-openconnect - ``` - -- openSUSE Leap - - ```sh - sudo zypper ar https://download.opensuse.org/repositories/home:/yuezk/openSUSE_Leap_15.2/home:yuezk.repo - sudo zypper ref - sudo zypper install globalprotect-openconnect - ``` -### CentOS 8 - -1. Add the repository: `https://download.opensuse.org/repositories/home:/yuezk/CentOS_8/home:yuezk.repo` -1. Install `globalprotect-openconnect` - - -## Build & Install from source code - -Clone this repo with: - -```sh -git clone https://github.com/yuezk/GlobalProtect-openconnect.git -cd GlobalProtect-openconnect -``` - -### MX Linux -The following instructions are for **MX-21.2.1_x64 KDE**. - -```sh -sudo apt install qttools5-dev libsecret-1-dev libqt5keychain1 -./scripts/install-debian.sh -``` - -### Ubuntu/Mint - -> **⚠️ REQUIRED for Ubuntu 18.04 ⚠️** -> -> Add this [dwmw2/openconnect](https://launchpad.net/~dwmw2/+archive/ubuntu/openconnect) PPA first to install the latest openconnect. -> -> ```sh -> sudo add-apt-repository ppa:dwmw2/openconnect -> sudo apt-get update -> ``` - -Build and install with: - -```sh -./scripts/install-ubuntu.sh -``` -### openSUSE - -Build and install with: - -```sh -./scripts/install-opensuse.sh -``` - -### Fedora - -Build and install with: - -```sh -./scripts/install-fedora.sh -``` - -### Other Linux - -Install the Qt5 dependencies and OpenConnect: - -- QtCore -- QtWebEngine -- QtWebSockets -- QtDBus -- openconnect v8.x -- qtkeychain - -...then build and install with: - -```sh -./scripts/install.sh -``` - - -### NixOS - In `configuration.nix`: - - ``` - services.globalprotect = { - enable = true; - # if you need a Host Integrity Protection report - csdWrapper = "${pkgs.openconnect}/libexec/openconnect/hipreport.sh"; - }; - - environment.systemPackages = [ globalprotect-openconnect ]; - ``` - -## Run - -Once the software is installed, you can run `gpclient` to start the UI. - -## Passing the Custom Parameters to `OpenConnect` CLI - -See [Configuration](https://github.com/yuezk/GlobalProtect-openconnect/wiki/Configuration) - -## Display the system tray icon on Gnome 40 - -Install the [AppIndicator and KStatusNotifierItem Support](https://extensions.gnome.org/extension/615/appindicator-support/) extension and you will see the system try icon (Restart the system after the installation). - -

- -

- - -## Troubleshooting - -Run `gpclient` in the Terminal and collect the logs. - -## [License](./LICENSE) -GPLv3 diff --git a/VERSION b/VERSION deleted file mode 100644 index 5596554..0000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -1.4.9 \ No newline at end of file diff --git a/cmake/Add3rdParty.cmake b/cmake/Add3rdParty.cmake deleted file mode 100644 index 3809a14..0000000 --- a/cmake/Add3rdParty.cmake +++ /dev/null @@ -1,27 +0,0 @@ -include(ExternalProject) - -function(add_3rdparty NAME) - set(oneValueArgs GIT_REPOSITORY GIT_TAG) - cmake_parse_arguments(add_3rdparty_args "" "${oneValueArgs}" "" ${ARGN}) - - if(EXISTS "${CMAKE_SOURCE_DIR}/3rdparty/${NAME}/CMakeLists.txt") - message(STATUS "Found third party locally for ${NAME}") - - ExternalProject_Add( - ${NAME}-${PROJECT_NAME} - PREFIX ${CMAKE_CURRENT_BINARY_DIR}/${NAME} - SOURCE_DIR "${CMAKE_SOURCE_DIR}/3rdparty/${NAME}" - INSTALL_COMMAND "" - "${add_3rdparty_args_UNPARSED_ARGUMENTS}" - ) - return() - endif() - - message(STATUS "Using ExternalProject to download ${NAME}") - ExternalProject_Add( - ${NAME}-${PROJECT_NAME} - PREFIX ${CMAKE_CURRENT_BINARY_DIR}/${NAME} - INSTALL_COMMAND "" - "${ARGN}" - ) -endfunction() diff --git a/cmake/FindNetworkManager.cmake b/cmake/FindNetworkManager.cmake deleted file mode 100644 index 14da5d8..0000000 --- a/cmake/FindNetworkManager.cmake +++ /dev/null @@ -1,59 +0,0 @@ -# - Try to find NetworkManager -# Once done this will define -# -# NETWORKMANAGER_FOUND - system has NetworkManager -# NETWORKMANAGER_INCLUDE_DIRS - the NetworkManager include directories -# NETWORKMANAGER_LIBRARIES - the libraries needed to use NetworkManager -# NETWORKMANAGER_CFLAGS - Compiler switches required for using NetworkManager -# NETWORKMANAGER_VERSION - version number of NetworkManager - -# Copyright (c) 2006, Alexander Neundorf, -# Copyright (c) 2007, Will Stephenson, -# Copyright (c) 2015-2018, Jan Grulich, - -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# 3. Neither the name of the University nor the names of its contributors -# may be used to endorse or promote products derived from this software -# without specific prior written permission. -# -# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND -# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -# SUCH DAMAGE. - -IF (NETWORKMANAGER_INCLUDE_DIRS) - # in cache already - SET(NetworkManager_FIND_QUIETLY TRUE) -ENDIF (NETWORKMANAGER_INCLUDE_DIRS) - -IF (NOT WIN32) - find_package(PkgConfig) - PKG_SEARCH_MODULE(NETWORKMANAGER libnm) - IF (NETWORKMANAGER_FOUND) - IF (NetworkManager_FIND_VERSION AND ("${NETWORKMANAGER_VERSION}" VERSION_LESS "${NetworkManager_FIND_VERSION}")) - MESSAGE(FATAL_ERROR "NetworkManager ${NETWORKMANAGER_VERSION} is too old, need at least ${NetworkManager_FIND_VERSION}") - ELSE () - IF (NOT NetworkManager_FIND_QUIETLY) - MESSAGE(STATUS "Found NetworkManager: ${NETWORKMANAGER_LIBRARY_DIRS}") - ENDIF () - ENDIF () - ELSE () - MESSAGE(FATAL_ERROR "Could NOT find NetworkManager, check FindPkgConfig output above!") - ENDIF () -ENDIF (NOT WIN32) - -MARK_AS_ADVANCED(NETWORKMANAGER_INCLUDE_DIRS) diff --git a/cmakew b/cmakew deleted file mode 100755 index 2cdacc2..0000000 --- a/cmakew +++ /dev/null @@ -1,102 +0,0 @@ -#!/bin/bash - -cmake_version="3.21.2" - -arr_cmake_v=(${cmake_version//./ }) -cmake_version_major=(${arr_cmake_v[0]}) -cmake_version_minor=(${arr_cmake_v[1]}) -cmake_version_patch=(${arr_cmake_v[2]}) - -# 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 - -# 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 - -cmake_base="./.cmake" -cmake_bin="${cmake_base}/cmake-$cmake_version/bin/cmake" - -# download cmake if necessary -if [ ! -f "$cmake_bin" ]; then - download_link="" - - if [ "$darwin" = true ]; then - download_link="https://cmake.org/files/v$cmake_version_major.$cmake_version_minor/cmake-$cmake_version-Darwin-x86_64.tar.gz" - else - download_link="https://github.com/Kitware/CMake/releases/download/v${cmake_version}/cmake-${cmake_version}-linux-x86_64.tar.gz" - fi - - wget -nv --show-progress "$download_link" -O "/tmp/cmake-$cmake_version.tar.gz" - mkdir -p "${cmake_base}/cmake-$cmake_version" - tar -xzf "/tmp/cmake-$cmake_version.tar.gz" -C "${cmake_base}/cmake-$cmake_version" --strip-components=1 - rm "/tmp/cmake-$cmake_version.tar.gz" -fi - -# We build the pattern for arguments to be converted via cygpath -if [ "$cygwin" = true ]; then - 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 - -# run cmake -exec "$cmake_bin" "$@" diff --git a/common/.gitignore b/common/.gitignore new file mode 100644 index 0000000..4fffb2f --- /dev/null +++ b/common/.gitignore @@ -0,0 +1,2 @@ +/target +/Cargo.lock diff --git a/common/Cargo.toml b/common/Cargo.toml new file mode 100644 index 0000000..510be70 --- /dev/null +++ b/common/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "common" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +tokio = { version = "1.14", features = ["full"] } +tokio-util = "0.7" +thiserror = "1.0" +serde = { version = "1.0", features = ["derive"] } +bytes = "1.0" +serde_json = "1.0" +async-trait = "0.1" +ring = "0.16" +data-encoding = "2.3" + +[build-dependencies] +cc = "1.0" diff --git a/common/build.rs b/common/build.rs new file mode 100644 index 0000000..66d83b5 --- /dev/null +++ b/common/build.rs @@ -0,0 +1,12 @@ +fn main() { + // Link to the native openconnect library + println!("cargo:rustc-link-lib=openconnect"); + println!("cargo:rerun-if-changed=src/vpn/vpn.c"); + println!("cargo:rerun-if-changed=src/vpn/vpn.h"); + + // Compile the wrapper.c file + cc::Build::new() + .file("src/vpn/vpn.c") + .include("src/vpn") + .compile("vpn"); +} diff --git a/common/src/client.rs b/common/src/client.rs new file mode 100644 index 0000000..1b45a87 --- /dev/null +++ b/common/src/client.rs @@ -0,0 +1,255 @@ +use crate::cmd::{Connect, Disconnect, Status}; +use crate::reader::Reader; +use crate::request::CommandPayload; +use crate::response::ResponseData; +use crate::writer::Writer; +use crate::RequestPool; +use crate::Response; +use crate::SOCKET_PATH; +use crate::{Request, VpnStatus}; +use serde::{Deserialize, Serialize}; +use std::fmt::Display; +use std::sync::Arc; +use tokio::io::{self, ReadHalf, WriteHalf}; +use tokio::net::UnixStream; +use tokio::sync::{mpsc, Mutex, RwLock}; +use tokio_util::sync::CancellationToken; + +#[derive(Debug)] +enum ServerEvent { + Response(Response), + ServerDisconnected, +} + +impl From for ServerEvent { + fn from(response: Response) -> Self { + Self::Response(response) + } +} + +#[derive(Debug)] +pub struct Client { + // pool of requests that are waiting for responses + request_pool: Arc, + // tx for sending requests to the channel + request_tx: mpsc::Sender, + // rx for receiving requests from the channel + request_rx: Arc>>, + // tx for sending responses to the channel + server_event_tx: mpsc::Sender, + // rx for receiving responses from the channel + server_event_rx: Arc>>, + is_healthy: Arc>, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ServerApiError { + pub message: String, +} + +impl Display for ServerApiError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{message}", message = self.message) + } +} + +impl From for ServerApiError { + fn from(message: String) -> Self { + Self { message } + } +} + +impl From<&str> for ServerApiError { + fn from(message: &str) -> Self { + Self { + message: message.to_string(), + } + } +} + +impl Default for Client { + fn default() -> Self { + let (request_tx, request_rx) = mpsc::channel::(32); + let (server_event_tx, server_event_rx) = mpsc::channel::(32); + + Self { + request_pool: Default::default(), + request_tx, + request_rx: Arc::new(Mutex::new(request_rx)), + server_event_tx, + server_event_rx: Arc::new(Mutex::new(server_event_rx)), + is_healthy: Default::default(), + } + } +} + +impl Client { + pub fn subscribe_status(&self, callback: impl Fn(VpnStatus) + Send + Sync + 'static) { + let server_event_rx = self.server_event_rx.clone(); + + tokio::spawn(async move { + loop { + let mut server_event_rx = server_event_rx.lock().await; + if let Some(server_event) = server_event_rx.recv().await { + match server_event { + ServerEvent::ServerDisconnected => { + callback(VpnStatus::Disconnected); + } + ServerEvent::Response(response) => { + if let ResponseData::Status(vpn_status) = response.data() { + callback(vpn_status); + } + } + } + } + } + }); + } + + pub async fn run(&self) { + loop { + match self.connect_to_server().await { + Ok(_) => { + println!("Disconnected from server, reconnecting..."); + } + Err(err) => { + println!( + "Disconnected from server with error: {:?}, reconnecting...", + err + ) + } + } + + // wait for a second before trying to reconnect + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + } + + async fn connect_to_server(&self) -> Result<(), Box> { + let stream = UnixStream::connect(SOCKET_PATH).await?; + let (read_stream, write_stream) = io::split(stream); + let cancel_token = CancellationToken::new(); + + let read_handle = tokio::spawn(handle_read( + read_stream, + self.request_pool.clone(), + self.server_event_tx.clone(), + cancel_token.clone(), + )); + + let write_handle = tokio::spawn(handle_write( + write_stream, + self.request_rx.clone(), + cancel_token, + )); + + *self.is_healthy.write().await = true; + println!("Connected to server"); + let _ = tokio::join!(read_handle, write_handle); + *self.is_healthy.write().await = false; + + Ok(()) + } + + async fn send_command>( + &self, + payload: CommandPayload, + ) -> Result { + if !*self.is_healthy.read().await { + return Err("Background service is not running".into()); + } + + let (request, response_rx) = self.request_pool.create_request(payload).await; + + if let Err(err) = self.request_tx.send(request).await { + return Err(format!("Error sending request to the channel: {}", err).into()); + } + + if let Ok(response) = response_rx.await { + if response.success() { + match response.data().try_into() { + Ok(it) => Ok(it), + Err(_) => Err("Error parsing response data".into()), + } + } else { + Err(response.message().into()) + } + } else { + Err("Error receiving response from the channel".into()) + } + } + + pub async fn connect(&self, server: String, cookie: String) -> Result<(), ServerApiError> { + self.send_command(Connect::new(server, cookie).into()).await + } + + pub async fn disconnect(&self) -> Result<(), ServerApiError> { + self.send_command(Disconnect.into()).await + } + + pub async fn status(&self) -> Result { + self.send_command(Status.into()).await + } +} + +async fn handle_read( + read_stream: ReadHalf, + request_pool: Arc, + server_event_tx: mpsc::Sender, + cancel_token: CancellationToken, +) { + let mut reader: Reader = read_stream.into(); + + loop { + match reader.read_multiple::().await { + Ok(responses) => { + for response in responses { + match response.request_id() { + Some(id) => request_pool.complete_request(id, response).await, + None => { + if let Err(err) = server_event_tx.send(response.into()).await { + println!("Error sending response to output channel: {}", err); + } + } + } + } + } + Err(err) if err.kind() == io::ErrorKind::ConnectionAborted => { + println!("Server disconnected"); + if let Err(err) = server_event_tx.send(ServerEvent::ServerDisconnected).await { + println!("Error sending server disconnected event: {}", err); + } + cancel_token.cancel(); + break; + } + Err(err) => { + println!("Error reading from server: {}", err); + } + } + } +} + +async fn handle_write( + write_stream: WriteHalf, + request_rx: Arc>>, + cancel_token: CancellationToken, +) { + let mut writer: Writer = write_stream.into(); + loop { + let mut request_rx = request_rx.lock().await; + tokio::select! { + Some(request) = request_rx.recv() => { + if let Err(err) = writer.write(&request).await { + println!("Error writing to server: {}", err); + } + } + _ = cancel_token.cancelled() => { + println!("The read loop has been cancelled, exiting the write loop"); + break; + } + else => { + println!("Error reading command from channel"); + } + } + } +} diff --git a/common/src/cmd/connect.rs b/common/src/cmd/connect.rs new file mode 100644 index 0000000..d1e9688 --- /dev/null +++ b/common/src/cmd/connect.rs @@ -0,0 +1,34 @@ +use super::{Command, CommandContext, CommandError}; +use crate::{ResponseData, VpnStatus}; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Connect { + server: String, + cookie: String, +} + +impl Connect { + pub fn new(server: String, cookie: String) -> Self { + Self { server, cookie } + } +} + +#[async_trait] +impl Command for Connect { + async fn handle(&self, context: CommandContext) -> Result { + let vpn = context.server_context.vpn(); + let status = vpn.status().await; + + if status != VpnStatus::Disconnected { + return Err(format!("VPN is already in state: {:?}", status).into()); + } + + if let Err(err) = vpn.connect(&self.server, &self.cookie).await { + return Err(err.to_string().into()); + } + + Ok(ResponseData::Empty) + } +} diff --git a/common/src/cmd/disconnect.rs b/common/src/cmd/disconnect.rs new file mode 100644 index 0000000..e4f56d1 --- /dev/null +++ b/common/src/cmd/disconnect.rs @@ -0,0 +1,15 @@ +use super::{Command, CommandContext, CommandError}; +use crate::ResponseData; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Disconnect; + +#[async_trait] +impl Command for Disconnect { + async fn handle(&self, context: CommandContext) -> Result { + context.server_context.vpn().disconnect().await; + Ok(ResponseData::Empty) + } +} diff --git a/common/src/cmd/mod.rs b/common/src/cmd/mod.rs new file mode 100644 index 0000000..414eac1 --- /dev/null +++ b/common/src/cmd/mod.rs @@ -0,0 +1,54 @@ +use crate::{response::ResponseData, server::ServerContext}; +use async_trait::async_trait; +use core::fmt::Debug; +use std::{ + fmt::{self, Display}, + sync::Arc, +}; + +mod connect; +mod disconnect; +mod status; + +pub use connect::Connect; +pub use disconnect::Disconnect; +pub use status::Status; + +#[derive(Debug)] +pub(crate) struct CommandContext { + server_context: Arc, +} + +impl From> for CommandContext { + fn from(server_context: Arc) -> Self { + Self { server_context } + } +} + +#[derive(Debug)] +pub(crate) struct CommandError { + message: String, +} + +impl From for CommandError { + fn from(message: String) -> Self { + Self { message } + } +} + +impl Display for CommandError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "CommandError {:#?}", self.message) + } +} + +#[async_trait] +pub(crate) trait Command: Send + Sync { + async fn handle(&self, context: CommandContext) -> Result; +} + +impl Debug for dyn Command { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "Command") + } +} diff --git a/common/src/cmd/status.rs b/common/src/cmd/status.rs new file mode 100644 index 0000000..ead146c --- /dev/null +++ b/common/src/cmd/status.rs @@ -0,0 +1,16 @@ +use super::{Command, CommandContext, CommandError}; +use crate::ResponseData; +use async_trait::async_trait; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Status; + +#[async_trait] +impl Command for Status { + async fn handle(&self, context: CommandContext) -> Result { + let status = context.server_context.vpn().status().await; + + Ok(ResponseData::Status(status)) + } +} diff --git a/common/src/connection.rs b/common/src/connection.rs new file mode 100644 index 0000000..d228055 --- /dev/null +++ b/common/src/connection.rs @@ -0,0 +1,144 @@ +use crate::request::Request; +use crate::server::ServerContext; +use crate::Reader; +use crate::Response; +use crate::ResponseData; +use crate::VpnStatus; +use crate::Writer; +use std::sync::Arc; +use tokio::io::{self, ReadHalf, WriteHalf}; +use tokio::net::UnixStream; +use tokio::sync::{mpsc, watch}; +use tokio_util::sync::CancellationToken; + +async fn handle_read( + read_stream: ReadHalf, + server_context: Arc, + response_tx: mpsc::Sender, + cancel_token: CancellationToken, +) { + let mut reader: Reader = read_stream.into(); + + loop { + match reader.read::().await { + Ok(request) => { + println!("Received request: {:?}", request); + let command = request.command(); + let context = server_context.clone().into(); + + let mut response = match command.handle(context).await { + Ok(data) => Response::from(data), + Err(err) => Response::from(err.to_string()), + }; + response.set_request_id(request.id()); + + let _ = response_tx.send(response).await; + } + + Err(err) if err.kind() == io::ErrorKind::ConnectionAborted => { + println!("Client disconnected"); + cancel_token.cancel(); + break; + } + + Err(err) => { + println!("Error receiving command: {:?}", err); + } + } + } +} + +async fn handle_write( + write_stream: WriteHalf, + mut response_rx: mpsc::Receiver, + cancel_token: CancellationToken, +) { + let mut writer: Writer = write_stream.into(); + + loop { + tokio::select! { + Some(response) = response_rx.recv() => { + println!("Sending response: {:?}", response); + if let Err(err) = writer.write(&response).await { + println!("Error sending response: {:?}", err); + } else { + println!("Response sent"); + } + } + _ = cancel_token.cancelled() => { + println!("Exiting write loop"); + break; + } + else => { + println!("Error receiving response"); + } + } + } +} + +async fn handle_status_change( + mut status_rx: watch::Receiver, + response_tx: mpsc::Sender, + cancel_token: CancellationToken, +) { + send_status(&status_rx, &response_tx).await; + println!("Waiting for status change"); + let start_time = std::time::Instant::now(); + + loop { + tokio::select! { + _ = status_rx.changed() => { + println!("Status changed: {:?}", start_time.elapsed()); + send_status(&status_rx, &response_tx).await; + } + _ = cancel_token.cancelled() => { + println!("Exiting status loop"); + break; + } + else => { + println!("Error receiving status"); + } + } + } +} + +async fn send_status(status_rx: &watch::Receiver, response_tx: &mpsc::Sender) { + let status = *status_rx.borrow(); + println!("received = {:?}", status); + if let Err(err) = response_tx + .send(Response::from(ResponseData::Status(status))) + .await + { + println!("Error sending status: {:?}", err); + } +} + +pub(crate) async fn handle_connection(socket: UnixStream, context: Arc) { + let (read_stream, write_stream) = io::split(socket); + let (response_tx, response_rx) = mpsc::channel::(32); + let cancel_token = CancellationToken::new(); + let status_rx = context.vpn().status_rx().await; + + let read_handle = tokio::spawn(handle_read( + read_stream, + context.clone(), + response_tx.clone(), + cancel_token.clone(), + )); + + let write_handle = tokio::spawn(handle_write( + write_stream, + response_rx, + cancel_token.clone(), + )); + + let status_handle = tokio::spawn(handle_status_change( + status_rx, + response_tx.clone(), + cancel_token, + )); + + let _ = tokio::join!(read_handle, write_handle, status_handle); + + println!("Connection closed") +} diff --git a/common/src/lib.rs b/common/src/lib.rs new file mode 100644 index 0000000..53632a2 --- /dev/null +++ b/common/src/lib.rs @@ -0,0 +1,50 @@ +use data_encoding::HEXUPPER; +use ring::digest::{Context, SHA256}; +use std::{ + fs::File, + io::{BufReader, Read}, + path::Path, +}; + +pub const SOCKET_PATH: &str = "/tmp/gpservice.sock"; + +mod client; +mod cmd; +mod connection; +mod reader; +mod request; +mod response; +pub mod server; +mod vpn; +mod writer; + +pub(crate) use request::Request; +pub(crate) use request::RequestPool; + +pub use response::Response; +pub use response::ResponseData; +pub use response::TryFromResponseDataError; + +pub(crate) use reader::Reader; +pub(crate) use writer::Writer; + +pub use client::Client; +pub use client::ServerApiError; +pub use vpn::VpnStatus; + +pub fn sha256_digest>(file_path: P) -> Result { + let input = File::open(file_path)?; + let mut reader = BufReader::new(input); + + let mut context = Context::new(&SHA256); + let mut buffer = [0; 1024]; + + loop { + let count = reader.read(&mut buffer)?; + if count == 0 { + break; + } + context.update(&buffer[..count]); + } + Ok(HEXUPPER.encode(context.finish().as_ref())) +} diff --git a/common/src/reader.rs b/common/src/reader.rs new file mode 100644 index 0000000..e01b9e5 --- /dev/null +++ b/common/src/reader.rs @@ -0,0 +1,59 @@ +use serde::Deserialize; +use tokio::io::{self, AsyncReadExt, ReadHalf}; +use tokio::net::UnixStream; + +pub(crate) struct Reader { + stream: ReadHalf, +} + +impl From> for Reader { + fn from(stream: ReadHalf) -> Self { + Self { stream } + } +} + +impl Reader { + pub async fn read Deserialize<'a>>(&mut self) -> Result { + let mut buffer = [0; 2048]; + + match self.stream.read(&mut buffer).await { + Ok(0) => Err(io::Error::new( + io::ErrorKind::ConnectionAborted, + "Peer disconnected", + )), + Ok(bytes_read) => { + let data = serde_json::from_slice::(&buffer[..bytes_read])?; + Ok(data) + } + Err(err) => Err(err), + } + } + + pub async fn read_multiple Deserialize<'a>>(&mut self) -> Result, io::Error> { + let mut buffer = [0; 2048]; + + match self.stream.read(&mut buffer).await { + Ok(0) => Err(io::Error::new( + io::ErrorKind::ConnectionAborted, + "Peer disconnected", + )), + Ok(bytes_read) => { + let response_str = String::from_utf8_lossy(&buffer[..bytes_read]); + let responses: Vec<&str> = response_str.split("\n\n").collect(); + let responses = responses + .iter() + .filter_map(|r| { + if !r.is_empty() { + serde_json::from_str(r).ok() + } else { + None + } + }) + .collect::>(); + + Ok(responses) + } + Err(err) => Err(err), + } + } +} diff --git a/common/src/request.rs b/common/src/request.rs new file mode 100644 index 0000000..7002c07 --- /dev/null +++ b/common/src/request.rs @@ -0,0 +1,105 @@ +use crate::cmd::{Command, Connect, Disconnect, Status}; +use crate::Response; +use serde::{Deserialize, Serialize}; +use std::sync::Arc; +use tokio::sync::{oneshot, RwLock}; + +#[derive(Debug, Serialize, Deserialize)] +pub(crate) struct Request { + id: u64, + payload: CommandPayload, +} + +impl Request { + fn new(id: u64, payload: CommandPayload) -> Self { + Self { id, payload } + } + + pub fn id(&self) -> u64 { + self.id + } + + pub fn command(&self) -> Box { + match &self.payload { + CommandPayload::Status(status) => Box::new(status.clone()), + CommandPayload::Connect(connect) => Box::new(connect.clone()), + CommandPayload::Disconnect(disconnect) => Box::new(disconnect.clone()), + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub(crate) enum CommandPayload { + Status(Status), + Connect(Connect), + Disconnect(Disconnect), +} + +impl From for CommandPayload { + fn from(status: Status) -> Self { + Self::Status(status) + } +} + +impl From for CommandPayload { + fn from(connect: Connect) -> Self { + Self::Connect(connect) + } +} + +impl From for CommandPayload { + fn from(disconnect: Disconnect) -> Self { + Self::Disconnect(disconnect) + } +} + +#[derive(Debug)] +struct RequestHandle { + id: u64, + response_tx: oneshot::Sender, +} + +#[derive(Debug, Default)] +struct IdGenerator { + current_id: u64, +} + +impl IdGenerator { + fn next(&mut self) -> u64 { + let current_id = self.current_id; + self.current_id = self.current_id.wrapping_add(1); + current_id + } +} + +#[derive(Debug, Default)] +pub(crate) struct RequestPool { + id_generator: Arc>, + request_handles: Arc>>, +} + +impl RequestPool { + pub async fn create_request( + &self, + payload: CommandPayload, + ) -> (Request, oneshot::Receiver) { + let id = self.id_generator.write().await.next(); + let (response_tx, response_rx) = oneshot::channel(); + let request_handle = RequestHandle { id, response_tx }; + + self.request_handles.write().await.push(request_handle); + (Request::new(id, payload), response_rx) + } + + pub async fn complete_request(&self, id: u64, response: Response) { + let mut request_handles = self.request_handles.write().await; + let request_handle = request_handles + .iter() + .position(|handle| handle.id == id) + .map(|index| request_handles.remove(index)); + + if let Some(request_handle) = request_handle { + let _ = request_handle.response_tx.send(response); + } + } +} diff --git a/common/src/response.rs b/common/src/response.rs new file mode 100644 index 0000000..c34ff9a --- /dev/null +++ b/common/src/response.rs @@ -0,0 +1,113 @@ +use crate::vpn::VpnStatus; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Response { + request_id: Option, + success: bool, + message: String, + data: ResponseData, +} + +impl From for Response { + fn from(data: ResponseData) -> Self { + Self { + request_id: None, + success: true, + message: String::from("Success"), + data, + } + } +} + +impl From for Response { + fn from(message: String) -> Self { + Self { + request_id: None, + success: false, + message, + data: ResponseData::Empty, + } + } +} + +impl Response { + pub fn success(&self) -> bool { + self.success + } + + pub fn message(&self) -> &str { + &self.message + } + + pub fn set_request_id(&mut self, command_id: u64) { + self.request_id = Some(command_id); + } + + pub fn request_id(&self) -> Option { + self.request_id + } + + pub fn data(&self) -> ResponseData { + self.data + } +} + +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +pub enum ResponseData { + Status(VpnStatus), + Empty, +} + +impl From for ResponseData { + fn from(status: VpnStatus) -> Self { + Self::Status(status) + } +} + +impl From<()> for ResponseData { + fn from(_: ()) -> Self { + Self::Empty + } +} + +#[derive(Debug)] +pub struct TryFromResponseDataError { + message: String, +} + +impl std::fmt::Display for TryFromResponseDataError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Invalid ResponseData: {}", self.message) + } +} + +impl From<&str> for TryFromResponseDataError { + fn from(message: &str) -> Self { + Self { + message: message.into(), + } + } +} + +impl TryFrom for VpnStatus { + type Error = TryFromResponseDataError; + + fn try_from(value: ResponseData) -> Result { + match value { + ResponseData::Status(status) => Ok(status), + _ => Err("ResponseData is not a VpnStatus".into()), + } + } +} + +impl TryFrom for () { + type Error = TryFromResponseDataError; + + fn try_from(value: ResponseData) -> Result { + match value { + ResponseData::Empty => Ok(()), + _ => Err("ResponseData is not empty".into()), + } + } +} diff --git a/common/src/server.rs b/common/src/server.rs new file mode 100644 index 0000000..fd30bd9 --- /dev/null +++ b/common/src/server.rs @@ -0,0 +1,81 @@ +use crate::{connection::handle_connection, vpn::Vpn}; +use std::{future::Future, os::unix::prelude::PermissionsExt, path::Path, sync::Arc}; +use tokio::{fs, net::UnixListener}; + +#[derive(Debug, Default)] +pub(crate) struct ServerContext { + vpn: Arc, +} + +struct Server { + socket_path: String, + context: Arc, +} + +impl ServerContext { + pub fn vpn(&self) -> Arc { + self.vpn.clone() + } +} + +impl Server { + fn new(socket_path: String) -> Self { + Self { + socket_path, + context: Default::default(), + } + } + + async fn start(&self) -> Result<(), Box> { + if Path::new(&self.socket_path).exists() { + fs::remove_file(&self.socket_path).await?; + } + + let listener = UnixListener::bind(&self.socket_path)?; + println!("Listening on socket: {:?}", listener.local_addr()?); + + let metadata = fs::metadata(&self.socket_path).await?; + let mut permissions = metadata.permissions(); + permissions.set_mode(0o666); + fs::set_permissions(&self.socket_path, permissions).await?; + + loop { + match listener.accept().await { + Ok((socket, _)) => { + println!("Accepted connection: {:?}", socket.peer_addr()?); + tokio::spawn(handle_connection(socket, self.context.clone())); + } + Err(err) => { + println!("Error accepting connection: {:?}", err); + } + } + } + } + + async fn stop(&self) -> Result<(), Box> { + self.context.vpn().disconnect().await; + fs::remove_file(&self.socket_path).await?; + Ok(()) + } +} + +pub async fn run( + socket_path: &str, + shutdown: impl Future, +) -> Result<(), Box> { + let server = Server::new(socket_path.to_string()); + + tokio::select! { + res = server.start() => { + if let Err(err) = res { + println!("Error starting server: {:?}", err); + } + }, + _ = shutdown => { + println!("Shutting down"); + server.stop().await?; + }, + } + + Ok(()) +} diff --git a/common/src/vpn/ffi.rs b/common/src/vpn/ffi.rs new file mode 100644 index 0000000..8f24b49 --- /dev/null +++ b/common/src/vpn/ffi.rs @@ -0,0 +1,22 @@ +use std::ffi::c_void; + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub(crate) struct Options { + pub server: *const ::std::os::raw::c_char, + pub cookie: *const ::std::os::raw::c_char, + pub script: *const ::std::os::raw::c_char, + pub user_data: *mut c_void, +} + +#[link(name = "vpn")] +extern "C" { + #[link_name = "start"] + pub(crate) fn connect( + options: *const Options, + on_connected: extern "C" fn(i32, *mut c_void), + ) -> ::std::os::raw::c_int; + + #[link_name = "stop"] + pub(crate) fn disconnect(); +} diff --git a/common/src/vpn/mod.rs b/common/src/vpn/mod.rs new file mode 100644 index 0000000..27ea692 --- /dev/null +++ b/common/src/vpn/mod.rs @@ -0,0 +1,161 @@ +mod ffi; + +use serde::{Deserialize, Serialize}; +use std::ffi::{c_void, CString}; +use std::sync::Arc; +use std::thread; +use tokio::sync::watch; +use tokio::sync::{mpsc, Mutex}; + +#[no_mangle] +extern "C" fn on_connected(value: i32, sender: *mut c_void) { + let sender = unsafe { &*(sender as *const mpsc::Sender) }; + sender + .blocking_send(value) + .expect("Failed to send VPN connection code"); +} + +#[derive(Debug, PartialEq, Clone, Copy, Serialize, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum VpnStatus { + Disconnected, + Connecting, + Connected, + Disconnecting, +} + +#[derive(Debug)] +struct StatusHolder { + status: VpnStatus, + status_tx: watch::Sender, + status_rx: watch::Receiver, +} + +impl Default for StatusHolder { + fn default() -> Self { + Self::new() + } +} + +impl StatusHolder { + fn new() -> Self { + let (status_tx, status_rx) = watch::channel(VpnStatus::Disconnected); + + Self { + status: VpnStatus::Disconnected, + status_tx, + status_rx, + } + } + + fn set(&mut self, status: VpnStatus) { + self.status = status; + if let Err(err) = self.status_tx.send(status) { + println!("Failed to send VPN status: {}", err); + } + } + + fn status_rx(&self) -> watch::Receiver { + self.status_rx.clone() + } +} + +#[derive(Debug)] +pub(crate) struct VpnOptions { + server: CString, + cookie: CString, + script: CString, +} + +impl VpnOptions { + fn as_oc_options(&self, user_data: *mut c_void) -> ffi::Options { + ffi::Options { + server: self.server.as_ptr(), + cookie: self.cookie.as_ptr(), + script: self.script.as_ptr(), + user_data, + } + } + + fn to_cstr(value: &str) -> CString { + CString::new(value.to_string()).expect("Failed to convert to CString") + } +} + +#[derive(Debug, Default)] +pub(crate) struct Vpn { + status_holder: Arc>, + vpn_options: Arc>>, +} + +impl Vpn { + pub async fn status_rx(&self) -> watch::Receiver { + self.status_holder.lock().await.status_rx() + } + + pub async fn connect( + &self, + server: &str, + cookie: &str, + ) -> Result<(), Box> { + // Save the VPN options so we can use them later, e.g. reconnect + *self.vpn_options.lock().await = Some(VpnOptions { + server: VpnOptions::to_cstr(server), + cookie: VpnOptions::to_cstr(cookie), + script: VpnOptions::to_cstr("/usr/share/vpnc-scripts/vpnc-script") + }); + + let vpn_options = self.vpn_options.clone(); + let status_holder = self.status_holder.clone(); + let (vpn_tx, mut vpn_rx) = mpsc::channel::(1); + + thread::spawn(move || { + let vpn_tx = &vpn_tx as *const _ as *mut c_void; + let oc_options = vpn_options + .blocking_lock() + .as_ref() + .expect("Failed to unwrap vpn_options") + .as_oc_options(vpn_tx); + + // Start the VPN connection, this will block until the connection is closed + status_holder.blocking_lock().set(VpnStatus::Connecting); + let ret = unsafe { ffi::connect(&oc_options, on_connected) }; + + println!("VPN connection closed with code: {}", ret); + status_holder.blocking_lock().set(VpnStatus::Disconnected); + }); + + println!("Waiting for the VPN connection..."); + + if let Some(cmd_pipe_fd) = vpn_rx.recv().await { + println!("VPN connection started, code: {}", cmd_pipe_fd); + self.status_holder.lock().await.set(VpnStatus::Connected); + } else { + println!("VPN connection failed to start"); + } + + Ok(()) + } + + pub async fn disconnect(&self) { + if self.status().await == VpnStatus::Disconnected { + println!("VPN already disconnected"); + return; + } + + unsafe { ffi::disconnect() }; + // Wait for the VPN to disconnect + println!("VPN disconnect waiting for disconnect..."); + let mut status_rx = self.status_rx().await; + + while status_rx.changed().await.is_ok() { + if *status_rx.borrow() == VpnStatus::Disconnected { + break; + } + } + } + + pub async fn status(&self) -> VpnStatus { + self.status_holder.lock().await.status + } +} diff --git a/common/src/vpn/vpn.c b/common/src/vpn/vpn.c new file mode 100644 index 0000000..fe8eb0d --- /dev/null +++ b/common/src/vpn/vpn.c @@ -0,0 +1,127 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "vpn.h" + +void *g_user_data; +on_connected_cb g_on_connected_cb; + +static int g_cmd_pipe_fd; +const char *g_vpnc_script; + +/* Validate the peer certificate */ +static int validate_peer_cert(__attribute__((unused)) void *_vpninfo, const char *reason) +{ + printf("Validating peer cert: %s\n", reason); + return 0; +} + +/* Print progress messages */ +static void print_progress(__attribute__((unused)) void *_vpninfo, int level, const char *fmt, ...) +{ + FILE *outf = level ? stdout : stderr; + va_list args; + + char ts[64]; + time_t t = time(NULL); + struct tm *tm = localtime(&t); + + strftime(ts, 64, "[%Y-%m-%d %H:%M:%S] ", tm); + fprintf(outf, "%s", ts); + + va_start(args, fmt); + vfprintf(outf, fmt, args); + va_end(args); + fflush(outf); +} + +static void setup_tun_handler(void *_vpninfo) +{ + openconnect_setup_tun_device(_vpninfo, g_vpnc_script, NULL); + + if (g_on_connected_cb) + { + g_on_connected_cb(g_cmd_pipe_fd, g_user_data); + } +} + +/* Initialize VPN connection */ +int start(const Options *options, on_connected_cb cb) +{ + struct openconnect_info *vpninfo; + struct utsname utsbuf; + + vpninfo = openconnect_vpninfo_new("PAN GlobalProtect", validate_peer_cert, NULL, NULL, print_progress, NULL); + + if (!vpninfo) + { + printf("openconnect_vpninfo_new failed\n"); + return 1; + } + + openconnect_set_loglevel(vpninfo, 1); + openconnect_init_ssl(); + openconnect_set_protocol(vpninfo, "gp"); + openconnect_set_hostname(vpninfo, options->server); + openconnect_set_cookie(vpninfo, options->cookie); + + g_cmd_pipe_fd = openconnect_setup_cmd_pipe(vpninfo); + if (g_cmd_pipe_fd < 0) + { + printf("openconnect_setup_cmd_pipe failed\n"); + return 1; + } + + if (!uname(&utsbuf)) + { + openconnect_set_localname(vpninfo, utsbuf.nodename); + } + + // Essential step + if (openconnect_make_cstp_connection(vpninfo) != 0) + { + printf("openconnect_make_cstp_connection failed\n"); + return 1; + } + + if (openconnect_setup_dtls(vpninfo, 60) != 0) + { + openconnect_disable_dtls(vpninfo); + } + + // Essential step + // openconnect_setup_tun_device(vpninfo, options->script, NULL); + g_user_data = options->user_data; + g_on_connected_cb = cb; + g_vpnc_script = options->script; + openconnect_set_setup_tun_handler(vpninfo, setup_tun_handler); + + while (1) + { + int ret = openconnect_mainloop(vpninfo, 300, 10); + printf("openconnect_mainloop returned %d\n", ret); + + if (ret) + { + openconnect_vpninfo_free(vpninfo); + return ret; + } + + printf("openconnect_mainloop returned\n"); + } +} + +/* Stop the VPN connection */ +void stop() +{ + char cmd = OC_CMD_CANCEL; + if (write(g_cmd_pipe_fd, &cmd, 1) < 0) + { + printf("Stopping VPN failed\n"); + } +} diff --git a/common/src/vpn/vpn.h b/common/src/vpn/vpn.h new file mode 100644 index 0000000..0e28b76 --- /dev/null +++ b/common/src/vpn/vpn.h @@ -0,0 +1,11 @@ +typedef void (*on_connected_cb)(int32_t, void *); + +typedef struct Options { + const char *server; + const char *cookie; + const char *script; + void *user_data; +} Options; + +int start(const Options *options, on_connected_cb cb); +void stop(); diff --git a/common/src/writer.rs b/common/src/writer.rs new file mode 100644 index 0000000..9161db0 --- /dev/null +++ b/common/src/writer.rs @@ -0,0 +1,24 @@ +use serde::Serialize; +use tokio::io::{self, AsyncWriteExt, WriteHalf}; +use tokio::net::UnixStream; + +pub(crate) struct Writer { + stream: WriteHalf, +} + +impl From> for Writer { + fn from(stream: WriteHalf) -> Self { + Self { stream } + } +} + +impl Writer { + pub async fn write(&mut self, data: &T) -> Result<(), io::Error> { + let data = serde_json::to_string(data)?; + let data = format!("{}\n\n", data); + + self.stream.write_all(data.as_bytes()).await?; + self.stream.flush().await?; + Ok(()) + } +} diff --git a/debian/README.Debian b/debian/README.Debian deleted file mode 100644 index ab3e77e..0000000 --- a/debian/README.Debian +++ /dev/null @@ -1,5 +0,0 @@ -globalprotect-openconnect for Debian - -Added debian packaging - - -- Amit Joshi <> Fri, 29 May 2020 21:52:59 -0400 diff --git a/debian/changelog b/debian/changelog deleted file mode 100644 index 8d97c92..0000000 --- a/debian/changelog +++ /dev/null @@ -1,154 +0,0 @@ -globalprotect-openconnect (1.4.9-1) unstable; urgency=medium - - * Updated VERSION, Bumped 1.4.8 –> 1.4.9 - * fix: update cmake version - * fix: correct the package name - * fix: use the dev package - * fix: use qtkeychain package - * fix: add qt5-tools - * fix: add libsecret-1-dev - * fix: add pkg-config - * fix: use cmake 3.16 - * fix: add missing build dependency - * ci: fix CI - * Merge branch 'master' into develop - * feat: expose os-version to settings - * Add two missing dependencies for building on debian (#198) - * ci: assert no library missing - * fix: update qtkeychain - * ci: run gpclient after build - * fix: add qtkeychain - * chore: update CMake file - * Added install instructions for MX Linux. (#190) - * Credentials autocompleting (secure version) (#179) - * Read all saved Gateways (for selecting in Systray) (#181) - * copy install script for debian (#180) - * add es and pt support to shange status when connected to vpn (#162) - * fix: improve the cli support - * feat: add --reset option to gpclient - - -- Kevin Yue Sun, 08 Jan 2023 20:58:32 +0800 - -globalprotect-openconnect (1.4.8-1) unstable; urgency=medium - - * Updated VERSION, Bumped 1.4.7 –> 1.4.8 - * fix: fix compile error - * refactor: simplify the code - * chore: use auto to declare variables - * chore: use c++ 17 - * fix: clear cookies when click the Reset button - * fix: refine the authentication workflow - * chore: PLOG -> LOG - - -- Kevin Yue Sun, 12 Jun 2022 20:28:58 +0800 - -globalprotect-openconnect (1.4.7-1) unstable; urgency=medium - - * Updated VERSION, Bumped 1.4.6 –> 1.4.7 - * fix: release resources when properly - * fix: add support for parsing tokens from HTML - * handle html comment for saml result with okta 2fa (#156) - * chore: use auto to declare variable - * chore: simplify readme - - -- Kevin Yue Tue, 07 Jun 2022 21:46:04 +0800 - -globalprotect-openconnect (1.4.6-1) unstable; urgency=medium - - * Updated VERSION, Bumped 1.4.5 –> 1.4.6 - * feat: display address in gateway menu item - * fix: fix bug of parsing the portal response - - -- Kevin Yue Wed, 01 Jun 2022 23:55:50 +0800 - -globalprotect-openconnect (1.4.5-1) unstable; urgency=medium - - * Updated VERSION, Bumped 1.4.4 –> 1.4.5 - * chore: refine vscode settings - * fix: rollback dbus configuration - * feat: add option to start minimized - * packaging: fix postinst for debian - * packaging: add postinst for debian - * test: test debian packaging - * ci: fix the folder path - * chore: apt -> apt-get - * ci: verify debian package - * Revert "Revert "fix: improve the dbus security"" - * fix: improve the portal config parsing - * Revert "fix: improve the dbus security" - * fix: improve the dbus security - * fix: free resources in slots - * chore: refine cmake files - * fix: support high DPI screen - - -- Kevin Yue Sun, 29 May 2022 21:15:40 +0800 - -globalprotect-openconnect (1.4.4-1) unstable; urgency=medium - - * Updated VERSION, Bumped 1.4.3 –> 1.4.4 - * fix: support the HighDPI displays - * [misc] update the build script - * [ci] Enable build job for master branch - * [ci] Add ubuntu 22.04 - - -- Kevin Yue Sat, 14 May 2022 19:21:14 +0800 - -globalprotect-openconnect (1.4.3-1) unstable; urgency=medium - - * Updated VERSION, Bumped 1.4.2 –> 1.4.3 - * refine AUR packaging - * Prepare release 1.4.3 (#149) - - -- Kevin Yue Mon, 09 May 2022 22:20:54 +0800 - -globalprotect-openconnect (1.4.2-1) unstable; urgency=medium - - * Updated VERSION, Bumped 1.4.1 –> 1.4.2 - * Clear SSL_OP_LEGACY_SERVER_CONNECT (#146) - - -- Kevin Yue Fri, 06 May 2022 22:18:19 +0800 - -globalprotect-openconnect (1.4.1-1) unstable; urgency=medium - - * Updated VERSION, Bumped 1.4.0 –> 1.4.1 - * print the gpservice logs - * update AUR packaging - - -- Kevin Yue Thu, 03 Mar 2022 21:58:59 +0800 - -globalprotect-openconnect (1.4.0-1) unstable; urgency=medium - - * Updated VERSION, Bumped 1.3.4 –> 1.4.0 - * Fix gpservice after openconnect v8.20 (#124) - * Add 2FA support (#112) - * Add a scripting mode to GPClient (#110) - * Stop saving credentials (#111) - * update CI - * add editorconfig - * Update README.md - * Add a run entry (#108) - * update the installation instruction of Arch Linux - - -- Kevin Yue Wed, 02 Mar 2022 21:34:19 +0800 - -globalprotect-openconnect (1.3.4-1) unstable; urgency=medium - - * Updated VERSION, Bumped 1.3.3 –> 1.3.4 - * update packaging (#100) - * shorten the sponsor links - * Update README.md - * add sponsor links - * Adding application logs location in the README (#95) - * improve the doc - * Add snap packaging (#93) - * update doc - * Migrate to cmake and refine the code structure (#92) - * QStringView -> QString - - -- Kevin Yue Sun, 24 Oct 2021 12:13:24 +0800 - -globalprotect-openconnect (1.3.0-1) unstable; urgency=medium - - * Bump version to 1.3.0 - - -- Kevin Yue Thu, 09 Jul 2020 10:13:46 +0800 diff --git a/debian/compat b/debian/compat deleted file mode 100644 index b4de394..0000000 --- a/debian/compat +++ /dev/null @@ -1 +0,0 @@ -11 diff --git a/debian/control b/debian/control deleted file mode 100644 index b8645c3..0000000 --- a/debian/control +++ /dev/null @@ -1,13 +0,0 @@ -Source: globalprotect-openconnect -Section: net -Priority: optional -Maintainer: Kevin Yue -Build-Depends: cmake (>=3.10), pkg-config, debhelper (>=11~), qtbase5-dev, qttools5-dev, libqt5websockets5-dev (>=5.9), qtwebengine5-dev (>=5.9), qt5keychain-dev -Standards-Version: 4.1.4 -Homepage: https://github.com/yuezk/GlobalProtect-openconnect - -Package: globalprotect-openconnect -Architecture: any -Multi-Arch: foreign -Depends: ${misc:Depends}, ${shlibs:Depends}, openconnect (>=8.0), libqt5websockets5 (>=5.9), libqt5webengine5 (>=5.9), libqt5keychain1 -Description: A GlobalProtect VPN client (GUI) based on OpenConnect. diff --git a/debian/copyright b/debian/copyright deleted file mode 100644 index bdc7b6a..0000000 --- a/debian/copyright +++ /dev/null @@ -1,15 +0,0 @@ -Files: * -Copyright: 1975-present Kevin Yue -License: GPL-3+ - -Files: 3rdparty/plog -Copyright: 2016 Sergey Podobry (sergey.podobry at gmail.com) -License: MPL-2.0 - -Files: 3rdparty/qt-unix-signals -Copyright: 2014 Simon Knopp -License: MIT - -Files: 3rdparty/SingleApplication -Copyright: Itay Grudev 2015 - 2020 -License: MIT diff --git a/debian/patches/series b/debian/patches/series deleted file mode 100644 index 4a97dfa..0000000 --- a/debian/patches/series +++ /dev/null @@ -1 +0,0 @@ -# You must remove unused comment lines for the released package. diff --git a/debian/rules b/debian/rules deleted file mode 100755 index 6637bfa..0000000 --- a/debian/rules +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/make -f -# You must remove unused comment lines for the released package. -export DH_VERBOSE = 1 -export DEB_BUILD_MAINT_OPTIONS = hardening=+all -export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic -export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed - -export DEBIAN_PACKAGE=1 - -%: - dh $@ -override_dh_installsystemd: - dh_installsystemd gpservice.service diff --git a/debian/source/format b/debian/source/format deleted file mode 100644 index 9f67427..0000000 --- a/debian/source/format +++ /dev/null @@ -1 +0,0 @@ -3.0 (native) \ No newline at end of file diff --git a/debian/source/local-options b/debian/source/local-options deleted file mode 100644 index 00131ee..0000000 --- a/debian/source/local-options +++ /dev/null @@ -1,2 +0,0 @@ -#abort-on-upstream-changes -#unapply-patches diff --git a/debian/watch b/debian/watch deleted file mode 100644 index 1459ccd..0000000 --- a/debian/watch +++ /dev/null @@ -1,3 +0,0 @@ -version=4 -opts="mode=git,pgpmode=gittag" \ -https://github.com/yuezk/GlobalProtect-openconnect.git refs/tags/v([\d\.]+) diff --git a/gpclient/.gitignore b/gpclient/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/gpclient/.gitignore @@ -0,0 +1 @@ +/target diff --git a/gpclient/Cargo.toml b/gpclient/Cargo.toml new file mode 100644 index 0000000..b999530 --- /dev/null +++ b/gpclient/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "gpclient" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +common = { path = "../common" } +tokio = { version = "1.0", features = ["full"] } diff --git a/gpclient/src/main.rs b/gpclient/src/main.rs new file mode 100644 index 0000000..109ffcc --- /dev/null +++ b/gpclient/src/main.rs @@ -0,0 +1,25 @@ +use common::{Client, SOCKET_PATH}; +use tokio::{io::AsyncReadExt, net::UnixStream, sync::mpsc}; + +#[tokio::main] +async fn main() { + // let mut stream = UnixStream::connect(SOCKET_PATH).await.unwrap(); + + // let mut buf = [0u8; 34]; + // let _ = stream.read(&mut buf).await.unwrap(); + + // // The first two bytes are the port number, the rest is the AES key + // let http_port = u16::from_be_bytes([buf[0], buf[1]]); + // let aes_key = &buf[2..]; + + // println!("http_port: {http_port}"); + // println!("aes_key: {aes_key:?}"); + let (output_tx, mut output_rx) = mpsc::channel::(32); + let client = Client::default(); + + tokio::select! { + _ = client.run() => { + println!("Client finished"); + } + } +} diff --git a/gpgui/.gitignore b/gpgui/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/gpgui/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/gpgui/docs/gateway-login-response.xml b/gpgui/docs/gateway-login-response.xml new file mode 100644 index 0000000..3b25e9f --- /dev/null +++ b/gpgui/docs/gateway-login-response.xml @@ -0,0 +1,27 @@ + + + + (null) + 44d9988f3a3b5a247d359b1d39229add + 1cb389d44ec35e98665211761b65a049ef7ba77e + GP-Gateway-N + user + AD_Authentication + vsys1 + vpn.example.com + (null) + + + + tunnel + -1 + 4100 + + xxxxxxxxxxxxxxxx + xxxxxxxxxxxxxxxx + + 4 + unknown + + + diff --git a/gpgui/docs/portal-config-response.xml b/gpgui/docs/portal-config-response.xml new file mode 100644 index 0000000..0aab3ca --- /dev/null +++ b/gpgui/docs/portal-config-response.xml @@ -0,0 +1,212 @@ + + + vpn.example.com + 4100 + 6.0.1-19 + global-protect-full + **** + + + + -----BEGIN CERTIFICATE----- + -----END CERTIFICATE----- + + yes + + + + -----BEGIN CERTIFICATE----- + -----END CERTIFICATE----- + + yes + + + + -----BEGIN CERTIFICATE----- + -----END CERTIFICATE----- + + no + + + on-demand + yes + yes + 24 + + + + + yes + yes + + 365 + + vpn.example.com + + yes + + + + 5 + + + + + + 1 + + + 1 + vpn_gateway + + + + + + 5 + + + + xxx.xxx.xxx.xxx + + + 1 + + + 1 + + + + + + yes + + + 0 + 0 + + + + no + + + allowed + yes + yes + yes + yes + + no + no + + + 3600 + 20 + yes + + + antivirus + anti-spyware + host-info + data-loss-prevention + patch-management + firewall + anti-malware + disk-backup + disk-encryption + + + + + 1 + no + no + no + no + + allowed + prompt + yes + no + no + yes + yes + no + 30 + 5 + no + no + + + 0 + + 15 + yes + <div style="font-family:'Helvetica + Neue';"><h1 style="color:red;text-align:center; margin: 0; font-size: + 30px;">Notice</h1><p style="margin: 0;font-size: 15px; + line-height: 1.2em;">To access the network, you must first connect to + GlobalProtect.</p></div> + yes + no + <div style="font-family:'Helvetica + Neue';"><h1 style="color:red;text-align:center; margin: 0; font-size: + 30px;">Captive Portal Detected</h1><p style="margin: 0; font-size: + 15px; line-height: 1.2em;">GlobalProtect has temporarily permitted network + access for you to connect to the Internet. Follow instructions from your internet + provider.</p><p style="margin: 0; font-size: 15px; line-height: + 1.2em;">If you let the connection time out, open GlobalProtect and click Connect + to try again.</p></div> + 5 + user-and-machine + 7 + + yes + no + yes + yes + yes + 0 + 0 + 0 + 0 + yes + 0 + 1400 + 0 + no + 30 + 60 + 30 + network-traffic + yes + no + no + + no + yes + yes + no + 4501 + + You have attempted to access a protected resource that requires + additional authentication. Proceed to authenticate at + 0 + yes + + no + no + yes + + not-install + Access to the network from this device has been restricted as per + your organization's security policy. Please contact your IT Administrator. + Access to the network from this device has been restored as per + your organization's security policy. + + + user@example.com + xxxxxx + xxxxxx + 2d8e997765a2f59cbf80284b2f2fbd38 + diff --git a/gpgui/index.html b/gpgui/index.html new file mode 100644 index 0000000..e0d1c84 --- /dev/null +++ b/gpgui/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +

+ + + diff --git a/gpgui/package.json b/gpgui/package.json new file mode 100644 index 0000000..f3dddde --- /dev/null +++ b/gpgui/package.json @@ -0,0 +1,30 @@ +{ + "name": "gpgui", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@emotion/react": "^11.10.6", + "@emotion/styled": "^11.10.6", + "@mui/icons-material": "^5.11.11", + "@mui/lab": "5.0.0-alpha.125", + "@mui/material": "^5.11.11", + "@tauri-apps/api": "^1.2.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "react-spinners": "^0.13.8" + }, + "devDependencies": { + "@tauri-apps/cli": "^1.2.3", + "@types/react": "^18.0.27", + "@types/react-dom": "^18.0.10", + "@vitejs/plugin-react-swc": "^3.0.0", + "typescript": "^4.9.3", + "vite": "^4.1.0" + } +} \ No newline at end of file diff --git a/gpgui/pnpm-lock.yaml b/gpgui/pnpm-lock.yaml new file mode 100644 index 0000000..b3fd641 --- /dev/null +++ b/gpgui/pnpm-lock.yaml @@ -0,0 +1,1391 @@ +lockfileVersion: '6.0' + +dependencies: + '@emotion/react': + specifier: ^11.10.6 + version: 11.10.6(@types/react@18.0.28)(react@18.2.0) + '@emotion/styled': + specifier: ^11.10.6 + version: 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.28)(react@18.2.0) + '@mui/icons-material': + specifier: ^5.11.11 + version: 5.11.11(@mui/material@5.11.11)(@types/react@18.0.28)(react@18.2.0) + '@mui/lab': + specifier: 5.0.0-alpha.125 + version: 5.0.0-alpha.125(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@mui/material@5.11.11)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0) + '@mui/material': + specifier: ^5.11.11 + version: 5.11.11(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0) + '@tauri-apps/api': + specifier: ^1.2.0 + version: 1.2.0 + react: + specifier: ^18.2.0 + version: 18.2.0 + react-dom: + specifier: ^18.2.0 + version: 18.2.0(react@18.2.0) + react-spinners: + specifier: ^0.13.8 + version: 0.13.8(react-dom@18.2.0)(react@18.2.0) + +devDependencies: + '@tauri-apps/cli': + specifier: ^1.2.3 + version: 1.2.3 + '@types/react': + specifier: ^18.0.27 + version: 18.0.28 + '@types/react-dom': + specifier: ^18.0.10 + version: 18.0.11 + '@vitejs/plugin-react-swc': + specifier: ^3.0.0 + version: 3.2.0(vite@4.1.4) + typescript: + specifier: ^4.9.3 + version: 4.9.5 + vite: + specifier: ^4.1.0 + version: 4.1.4 + +packages: + + /@babel/code-frame@7.18.6: + resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.18.6 + dev: false + + /@babel/helper-module-imports@7.18.6: + resolution: {integrity: sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/types': 7.21.2 + dev: false + + /@babel/helper-string-parser@7.19.4: + resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} + engines: {node: '>=6.9.0'} + dev: false + + /@babel/helper-validator-identifier@7.19.1: + resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + engines: {node: '>=6.9.0'} + dev: false + + /@babel/highlight@7.18.6: + resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.19.1 + chalk: 2.4.2 + js-tokens: 4.0.0 + dev: false + + /@babel/runtime@7.21.0: + resolution: {integrity: sha512-xwII0//EObnq89Ji5AKYQaRYiW/nZ3llSv29d49IuxPhKbtJoLP+9QUUZ4nVragQVtaVGeZrpB+ZtG/Pdy/POw==} + engines: {node: '>=6.9.0'} + dependencies: + regenerator-runtime: 0.13.11 + dev: false + + /@babel/types@7.21.2: + resolution: {integrity: sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-string-parser': 7.19.4 + '@babel/helper-validator-identifier': 7.19.1 + to-fast-properties: 2.0.0 + dev: false + + /@emotion/babel-plugin@11.10.6: + resolution: {integrity: sha512-p2dAqtVrkhSa7xz1u/m9eHYdLi+en8NowrmXeF/dKtJpU8lCWli8RUAati7NcSl0afsBott48pdnANuD0wh9QQ==} + dependencies: + '@babel/helper-module-imports': 7.18.6 + '@babel/runtime': 7.21.0 + '@emotion/hash': 0.9.0 + '@emotion/memoize': 0.8.0 + '@emotion/serialize': 1.1.1 + babel-plugin-macros: 3.1.0 + convert-source-map: 1.9.0 + escape-string-regexp: 4.0.0 + find-root: 1.1.0 + source-map: 0.5.7 + stylis: 4.1.3 + dev: false + + /@emotion/cache@11.10.5: + resolution: {integrity: sha512-dGYHWyzTdmK+f2+EnIGBpkz1lKc4Zbj2KHd4cX3Wi8/OWr5pKslNjc3yABKH4adRGCvSX4VDC0i04mrrq0aiRA==} + dependencies: + '@emotion/memoize': 0.8.0 + '@emotion/sheet': 1.2.1 + '@emotion/utils': 1.2.0 + '@emotion/weak-memoize': 0.3.0 + stylis: 4.1.3 + dev: false + + /@emotion/hash@0.9.0: + resolution: {integrity: sha512-14FtKiHhy2QoPIzdTcvh//8OyBlknNs2nXRwIhG904opCby3l+9Xaf/wuPvICBF0rc1ZCNBd3nKe9cd2mecVkQ==} + dev: false + + /@emotion/is-prop-valid@1.2.0: + resolution: {integrity: sha512-3aDpDprjM0AwaxGE09bOPkNxHpBd+kA6jty3RnaEXdweX1DF1U3VQpPYb0g1IStAuK7SVQ1cy+bNBBKp4W3Fjg==} + dependencies: + '@emotion/memoize': 0.8.0 + dev: false + + /@emotion/memoize@0.8.0: + resolution: {integrity: sha512-G/YwXTkv7Den9mXDO7AhLWkE3q+I92B+VqAE+dYG4NGPaHZGvt3G8Q0p9vmE+sq7rTGphUbAvmQ9YpbfMQGGlA==} + dev: false + + /@emotion/react@11.10.6(@types/react@18.0.28)(react@18.2.0): + resolution: {integrity: sha512-6HT8jBmcSkfzO7mc+N1L9uwvOnlcGoix8Zn7srt+9ga0MjREo6lRpuVX0kzo6Jp6oTqDhREOFsygN6Ew4fEQbw==} + peerDependencies: + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@emotion/babel-plugin': 11.10.6 + '@emotion/cache': 11.10.5 + '@emotion/serialize': 1.1.1 + '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0) + '@emotion/utils': 1.2.0 + '@emotion/weak-memoize': 0.3.0 + '@types/react': 18.0.28 + hoist-non-react-statics: 3.3.2 + react: 18.2.0 + dev: false + + /@emotion/serialize@1.1.1: + resolution: {integrity: sha512-Zl/0LFggN7+L1liljxXdsVSVlg6E/Z/olVWpfxUTxOAmi8NU7YoeWeLfi1RmnB2TATHoaWwIBRoL+FvAJiTUQA==} + dependencies: + '@emotion/hash': 0.9.0 + '@emotion/memoize': 0.8.0 + '@emotion/unitless': 0.8.0 + '@emotion/utils': 1.2.0 + csstype: 3.1.1 + dev: false + + /@emotion/sheet@1.2.1: + resolution: {integrity: sha512-zxRBwl93sHMsOj4zs+OslQKg/uhF38MB+OMKoCrVuS0nyTkqnau+BM3WGEoOptg9Oz45T/aIGs1qbVAsEFo3nA==} + dev: false + + /@emotion/styled@11.10.6(@emotion/react@11.10.6)(@types/react@18.0.28)(react@18.2.0): + resolution: {integrity: sha512-OXtBzOmDSJo5Q0AFemHCfl+bUueT8BIcPSxu0EGTpGk6DmI5dnhSzQANm1e1ze0YZL7TDyAyy6s/b/zmGOS3Og==} + peerDependencies: + '@emotion/react': ^11.0.0-rc.0 + '@types/react': '*' + react: '>=16.8.0' + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@emotion/babel-plugin': 11.10.6 + '@emotion/is-prop-valid': 1.2.0 + '@emotion/react': 11.10.6(@types/react@18.0.28)(react@18.2.0) + '@emotion/serialize': 1.1.1 + '@emotion/use-insertion-effect-with-fallbacks': 1.0.0(react@18.2.0) + '@emotion/utils': 1.2.0 + '@types/react': 18.0.28 + react: 18.2.0 + dev: false + + /@emotion/unitless@0.8.0: + resolution: {integrity: sha512-VINS5vEYAscRl2ZUDiT3uMPlrFQupiKgHz5AA4bCH1miKBg4qtwkim1qPmJj/4WG6TreYMY111rEFsjupcOKHw==} + dev: false + + /@emotion/use-insertion-effect-with-fallbacks@1.0.0(react@18.2.0): + resolution: {integrity: sha512-1eEgUGmkaljiBnRMTdksDV1W4kUnmwgp7X9G8B++9GYwl1lUdqSndSriIrTJ0N7LQaoauY9JJ2yhiOYK5+NI4A==} + peerDependencies: + react: '>=16.8.0' + dependencies: + react: 18.2.0 + dev: false + + /@emotion/utils@1.2.0: + resolution: {integrity: sha512-sn3WH53Kzpw8oQ5mgMmIzzyAaH2ZqFEbozVVBSYp538E06OSE6ytOp7pRAjNQR+Q/orwqdQYJSe2m3hCOeznkw==} + dev: false + + /@emotion/weak-memoize@0.3.0: + resolution: {integrity: sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==} + dev: false + + /@esbuild/android-arm64@0.16.17: + resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.16.17: + resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.16.17: + resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.16.17: + resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.16.17: + resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.16.17: + resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.16.17: + resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.16.17: + resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.16.17: + resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.16.17: + resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.16.17: + resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.16.17: + resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.16.17: + resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.16.17: + resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.16.17: + resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.16.17: + resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.16.17: + resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.16.17: + resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.16.17: + resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.16.17: + resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.16.17: + resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.16.17: + resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@mui/base@5.0.0-alpha.119(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-XA5zhlYfXi67u613eIF0xRmktkatx6ERy3h+PwrMN5IcWFbgiL1guz8VpdXON+GWb8+G7B8t5oqTFIaCqaSAeA==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@emotion/is-prop-valid': 1.2.0 + '@mui/types': 7.2.3(@types/react@18.0.28) + '@mui/utils': 5.11.11(react@18.2.0) + '@popperjs/core': 2.11.6 + '@types/react': 18.0.28 + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-is: 18.2.0 + dev: false + + /@mui/base@5.0.0-alpha.124(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-I6M+FrjRCybQCr8I8JTu6L2MkUobSQFgNIpOJyDNKL5zq/73LvZIQXvsKumAzthVGvI1PYaarM9vGDrDYbumKA==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@emotion/is-prop-valid': 1.2.0 + '@mui/types': 7.2.3(@types/react@18.0.28) + '@mui/utils': 5.11.13(react@18.2.0) + '@popperjs/core': 2.11.7 + '@types/react': 18.0.28 + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-is: 18.2.0 + dev: false + + /@mui/core-downloads-tracker@5.11.11: + resolution: {integrity: sha512-0YK0K9GfW1ysw9z4ztWAjLW+bktf+nExMyn2+EQe1Ijb0kF2kz1kIOmb4+di0/PsXG70uCuw4DhEIdNd+JQkRA==} + dev: false + + /@mui/icons-material@5.11.11(@mui/material@5.11.11)(@types/react@18.0.28)(react@18.2.0): + resolution: {integrity: sha512-Eell3ADmQVE8HOpt/LZ3zIma8JSvPh3XgnhwZLT0k5HRqZcd6F/QDHc7xsWtgz09t+UEFvOYJXjtrwKmLdwwpw==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@mui/material': ^5.0.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@mui/material': 5.11.11(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0) + '@types/react': 18.0.28 + react: 18.2.0 + dev: false + + /@mui/lab@5.0.0-alpha.125(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@mui/material@5.11.11)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-lL4heZcBB5HLsLe0ZSC8nXcdWUfz49A2XeZqffxmlPxSFdJHFZujtAZF5AxmesuUSfcsbAWxD7zhtr8a3Yg+6w==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@mui/material': ^5.0.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@emotion/react': 11.10.6(@types/react@18.0.28)(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.28)(react@18.2.0) + '@mui/base': 5.0.0-alpha.124(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0) + '@mui/material': 5.11.11(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0) + '@mui/system': 5.11.16(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.28)(react@18.2.0) + '@mui/types': 7.2.3(@types/react@18.0.28) + '@mui/utils': 5.11.13(react@18.2.0) + '@types/react': 18.0.28 + clsx: 1.2.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-is: 18.2.0 + dev: false + + /@mui/material@5.11.11(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-sSe0dmKjB1IGOYt32Pcha+cXV3IIrX5L5mFAF9LDRssp/x53bluhgLLbkc8eTiJvueVvo6HAyze6EkFEYLQRXQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + react-dom: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@emotion/react': 11.10.6(@types/react@18.0.28)(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.28)(react@18.2.0) + '@mui/base': 5.0.0-alpha.119(@types/react@18.0.28)(react-dom@18.2.0)(react@18.2.0) + '@mui/core-downloads-tracker': 5.11.11 + '@mui/system': 5.11.11(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.28)(react@18.2.0) + '@mui/types': 7.2.3(@types/react@18.0.28) + '@mui/utils': 5.11.11(react@18.2.0) + '@types/react': 18.0.28 + '@types/react-transition-group': 4.4.5 + clsx: 1.2.1 + csstype: 3.1.1 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-is: 18.2.0 + react-transition-group: 4.4.5(react-dom@18.2.0)(react@18.2.0) + dev: false + + /@mui/private-theming@5.11.11(@types/react@18.0.28)(react@18.2.0): + resolution: {integrity: sha512-yLgTkjNC1mpye2SOUkc+zQQczUpg8NvQAETvxwXTMzNgJK1pv4htL7IvBM5vmCKG7IHAB3hX26W2u6i7bxwF3A==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@mui/utils': 5.11.11(react@18.2.0) + '@types/react': 18.0.28 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/private-theming@5.11.13(@types/react@18.0.28)(react@18.2.0): + resolution: {integrity: sha512-PJnYNKzW5LIx3R+Zsp6WZVPs6w5sEKJ7mgLNnUXuYB1zo5aX71FVLtV7geyPXRcaN2tsoRNK7h444ED0t7cIjA==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@mui/utils': 5.11.13(react@18.2.0) + '@types/react': 18.0.28 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/styled-engine@5.11.11(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0): + resolution: {integrity: sha512-wV0UgW4lN5FkDBXefN8eTYeuE9sjyQdg5h94vtwZCUamGQEzmCOtir4AakgmbWMy0x8OLjdEUESn9wnf5J9MOg==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@emotion/cache': 11.10.5 + '@emotion/react': 11.10.6(@types/react@18.0.28)(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.28)(react@18.2.0) + csstype: 3.1.1 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/styled-engine@5.11.16(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0): + resolution: {integrity: sha512-8dJRR/LqtGGaZN21p1vU9euwrKERlgtQIWyuzBKZ8/cuSlW5rIzlp46liP+Uh0+7d9NcHU0H4hBMoPt3ax64PA==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.4.1 + '@emotion/styled': ^11.3.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@emotion/cache': 11.10.5 + '@emotion/react': 11.10.6(@types/react@18.0.28)(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.28)(react@18.2.0) + csstype: 3.1.2 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/system@5.11.11(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.28)(react@18.2.0): + resolution: {integrity: sha512-a9gaOAJBjpzypDfhbGZQ8HzdcxdxsKkFvbp1aAWZhFHBPdehEkARNh7mj851VfEhD/GdffYt85PFKFKdUta5Eg==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@emotion/react': 11.10.6(@types/react@18.0.28)(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.28)(react@18.2.0) + '@mui/private-theming': 5.11.11(@types/react@18.0.28)(react@18.2.0) + '@mui/styled-engine': 5.11.11(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0) + '@mui/types': 7.2.3(@types/react@18.0.28) + '@mui/utils': 5.11.11(react@18.2.0) + '@types/react': 18.0.28 + clsx: 1.2.1 + csstype: 3.1.1 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/system@5.11.16(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(@types/react@18.0.28)(react@18.2.0): + resolution: {integrity: sha512-JY7CNm7ik2Gr4kQpz1+C9N/f4ET3QjVBo/iaHcmlSOgjdxnOzFbv+vCdb1DMzBGew+UbqckppZpZwbgbrBE2Rw==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@emotion/react': ^11.5.0 + '@emotion/styled': ^11.3.0 + '@types/react': ^17.0.0 || ^18.0.0 + react: ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@emotion/react': + optional: true + '@emotion/styled': + optional: true + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.21.0 + '@emotion/react': 11.10.6(@types/react@18.0.28)(react@18.2.0) + '@emotion/styled': 11.10.6(@emotion/react@11.10.6)(@types/react@18.0.28)(react@18.2.0) + '@mui/private-theming': 5.11.13(@types/react@18.0.28)(react@18.2.0) + '@mui/styled-engine': 5.11.16(@emotion/react@11.10.6)(@emotion/styled@11.10.6)(react@18.2.0) + '@mui/types': 7.2.3(@types/react@18.0.28) + '@mui/utils': 5.11.13(react@18.2.0) + '@types/react': 18.0.28 + clsx: 1.2.1 + csstype: 3.1.2 + prop-types: 15.8.1 + react: 18.2.0 + dev: false + + /@mui/types@7.2.3(@types/react@18.0.28): + resolution: {integrity: sha512-tZ+CQggbe9Ol7e/Fs5RcKwg/woU+o8DCtOnccX6KmbBc7YrfqMYEYuaIcXHuhpT880QwNkZZ3wQwvtlDFA2yOw==} + peerDependencies: + '@types/react': '*' + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.0.28 + dev: false + + /@mui/utils@5.11.11(react@18.2.0): + resolution: {integrity: sha512-neMM5rrEXYQrOrlxUfns/TGgX4viS8K2zb9pbQh11/oUUYFlGI32Tn+PHePQx7n6Fy/0zq6WxdBFC9VpnJ5JrQ==} + engines: {node: '>=12.0.0'} + peerDependencies: + react: ^17.0.0 || ^18.0.0 + dependencies: + '@babel/runtime': 7.21.0 + '@types/prop-types': 15.7.5 + '@types/react-is': 17.0.3 + prop-types: 15.8.1 + react: 18.2.0 + react-is: 18.2.0 + dev: false + + /@mui/utils@5.11.13(react@18.2.0): + resolution: {integrity: sha512-5ltA58MM9euOuUcnvwFJqpLdEugc9XFsRR8Gt4zZNb31XzMfSKJPR4eumulyhsOTK1rWf7K4D63NKFPfX0AxqA==} + engines: {node: '>=12.0.0'} + peerDependencies: + react: ^17.0.0 || ^18.0.0 + dependencies: + '@babel/runtime': 7.21.0 + '@types/prop-types': 15.7.5 + '@types/react-is': 17.0.3 + prop-types: 15.8.1 + react: 18.2.0 + react-is: 18.2.0 + dev: false + + /@popperjs/core@2.11.6: + resolution: {integrity: sha512-50/17A98tWUfQ176raKiOGXuYpLyyVMkxxG6oylzL3BPOlA6ADGdK7EYunSa4I064xerltq9TGXs8HmOk5E+vw==} + dev: false + + /@popperjs/core@2.11.7: + resolution: {integrity: sha512-Cr4OjIkipTtcXKjAsm8agyleBuDHvxzeBoa1v543lbv1YaIwQjESsVcmjiWiPEbC1FIeHOG/Op9kdCmAmiS3Kw==} + dev: false + + /@swc/core-darwin-arm64@1.3.36: + resolution: {integrity: sha512-lsP+C8p9cC/Vd9uAbtxpEnM8GoJI/MMnVuXak7OlxOtDH9/oTwmAcAQTfNGNaH19d2FAIRwf+5RbXCPnxa2Zjw==} + engines: {node: '>=10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@swc/core-darwin-x64@1.3.36: + resolution: {integrity: sha512-jaLXsozWN5xachl9fPxDMi5nbWq1rRxPAt6ISeiYB6RJk0MQKH1634pOweBBem2pUDDzwDFXFw6f22LTm/cFvA==} + engines: {node: '>=10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm-gnueabihf@1.3.36: + resolution: {integrity: sha512-vcBdTHjoEpvJDbFlgto+S6VwAHzLA9GyCiuNcTU2v4KNQlFzhbO4A4PMfMCb/Z0RLJEr16tirfHdWIxjU3h8nw==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm64-gnu@1.3.36: + resolution: {integrity: sha512-o7f5OsvwWppJo+qIZmrGO5+XC6DPt6noecSbRHjF6o1YAcR13ETPC14k1eC9H1YbQwpyCFNVAFXyNcUbCeQyrQ==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-arm64-musl@1.3.36: + resolution: {integrity: sha512-FSHPngMi3c0fuGt9yY2Ubn5UcELi3EiPLJxBSC3X8TF9atI/WHZzK9PE9Gtn0C/LyRh4CoyOugDtSOPzGYmLQg==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-x64-gnu@1.3.36: + resolution: {integrity: sha512-PHSsH2rek5pr3e0K09VgWAbrWK2vJhaI7MW9TPoTjyACYjcs3WwjcjQ30MghXUs2Dc/bXjWAOi9KFTjq/uCyFg==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-linux-x64-musl@1.3.36: + resolution: {integrity: sha512-4LfMYQHzozHCKkIcmQy83b+4SpI+mOp6sYNbXqSRz5dYvTVjegKZXe596P1U/87cK2cgR4uYvkgkgBXquaWvwQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-arm64-msvc@1.3.36: + resolution: {integrity: sha512-7y3dDcun79TAjCyk3Iv0eOMw1X/KNQbkVyKOGqnEgq9g22F8F1FoUGKHNTzUqVdzpHeJSsHgW5PlkEkl3c/d9w==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-ia32-msvc@1.3.36: + resolution: {integrity: sha512-zK0VR3B4LX5hzQ+7eD+K+FkxJlJg5Lo36BeahMzQ+/i0IURpnuyFlW88sdkFkMsc2swdU6bpvxLZeIRQ3W4OUg==} + engines: {node: '>=10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core-win32-x64-msvc@1.3.36: + resolution: {integrity: sha512-2bIjr9DhAckGiXZEvj6z2z7ECPcTimG+wD0VuQTvr+wkx46uAJKl5Kq+Zk+dd15ErL7JGUtCet1T7bf1k4FwvQ==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@swc/core@1.3.36: + resolution: {integrity: sha512-Ogrd9uRNIj7nHjXxG66UlKBIcXESUenJ7OD6K2a8p82qlg6ne7Ne5Goiipm/heHYhSfVmjcnRWL9ZJ4gv+YCPA==} + engines: {node: '>=10'} + requiresBuild: true + optionalDependencies: + '@swc/core-darwin-arm64': 1.3.36 + '@swc/core-darwin-x64': 1.3.36 + '@swc/core-linux-arm-gnueabihf': 1.3.36 + '@swc/core-linux-arm64-gnu': 1.3.36 + '@swc/core-linux-arm64-musl': 1.3.36 + '@swc/core-linux-x64-gnu': 1.3.36 + '@swc/core-linux-x64-musl': 1.3.36 + '@swc/core-win32-arm64-msvc': 1.3.36 + '@swc/core-win32-ia32-msvc': 1.3.36 + '@swc/core-win32-x64-msvc': 1.3.36 + dev: true + + /@tauri-apps/api@1.2.0: + resolution: {integrity: sha512-lsI54KI6HGf7VImuf/T9pnoejfgkNoXveP14pVV7XarrQ46rOejIVJLFqHI9sRReJMGdh2YuCoI3cc/yCWCsrw==} + engines: {node: '>= 14.6.0', npm: '>= 6.6.0', yarn: '>= 1.19.1'} + dev: false + + /@tauri-apps/cli-darwin-arm64@1.2.3: + resolution: {integrity: sha512-phJN3fN8FtZZwqXg08bcxfq1+X1JSDglLvRxOxB7VWPq+O5SuB8uLyssjJsu+PIhyZZnIhTGdjhzLSFhSXfLsw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli-darwin-x64@1.2.3: + resolution: {integrity: sha512-jFZ/y6z8z6v4yliIbXKBXA7BJgtZVMsITmEXSuD6s5+eCOpDhQxbRkr6CA+FFfr+/r96rWSDSgDenDQuSvPAKw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli-linux-arm-gnueabihf@1.2.3: + resolution: {integrity: sha512-C7h5vqAwXzY0kRGSU00Fj8PudiDWFCiQqqUNI1N+fhCILrzWZB9TPBwdx33ZfXKt/U4+emdIoo/N34v3TiAOmQ==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli-linux-arm64-gnu@1.2.3: + resolution: {integrity: sha512-buf1c8sdkuUzVDkGPQpyUdAIIdn5r0UgXU6+H5fGPq/Xzt5K69JzXaeo6fHsZEZghbV0hOK+taKV4J0m30UUMQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli-linux-arm64-musl@1.2.3: + resolution: {integrity: sha512-x88wPS9W5xAyk392vc4uNHcKBBvCp0wf4H9JFMF9OBwB7vfd59LbQCFcPSu8f0BI7bPrOsyHqspWHuFL8ojQEA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli-linux-x64-gnu@1.2.3: + resolution: {integrity: sha512-ZMz1jxEVe0B4/7NJnlPHmwmSIuwiD6ViXKs8F+OWWz2Y4jn5TGxWKFg7DLx5OwQTRvEIZxxT7lXHi5CuTNAxKg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli-linux-x64-musl@1.2.3: + resolution: {integrity: sha512-B/az59EjJhdbZDzawEVox0LQu2ZHCZlk8rJf85AMIktIUoAZPFbwyiUv7/zjzA/sY6Nb58OSJgaPL2/IBy7E0A==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli-win32-ia32-msvc@1.2.3: + resolution: {integrity: sha512-ypdO1OdC5ugNJAKO2m3sb1nsd+0TSvMS9Tr5qN/ZSMvtSduaNwrcZ3D7G/iOIanrqu/Nl8t3LYlgPZGBKlw7Ng==} + engines: {node: '>= 10'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli-win32-x64-msvc@1.2.3: + resolution: {integrity: sha512-CsbHQ+XhnV/2csOBBDVfH16cdK00gNyNYUW68isedmqcn8j+s0e9cQ1xXIqi+Hue3awp8g3ImYN5KPepf3UExw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@tauri-apps/cli@1.2.3: + resolution: {integrity: sha512-erxtXuPhMEGJPBtnhPILD4AjuT81GZsraqpFvXAmEJZ2p8P6t7MVBifCL8LznRknznM3jn90D3M8RNBP3wcXTw==} + engines: {node: '>= 10'} + hasBin: true + optionalDependencies: + '@tauri-apps/cli-darwin-arm64': 1.2.3 + '@tauri-apps/cli-darwin-x64': 1.2.3 + '@tauri-apps/cli-linux-arm-gnueabihf': 1.2.3 + '@tauri-apps/cli-linux-arm64-gnu': 1.2.3 + '@tauri-apps/cli-linux-arm64-musl': 1.2.3 + '@tauri-apps/cli-linux-x64-gnu': 1.2.3 + '@tauri-apps/cli-linux-x64-musl': 1.2.3 + '@tauri-apps/cli-win32-ia32-msvc': 1.2.3 + '@tauri-apps/cli-win32-x64-msvc': 1.2.3 + dev: true + + /@types/parse-json@4.0.0: + resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} + dev: false + + /@types/prop-types@15.7.5: + resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + + /@types/react-dom@18.0.11: + resolution: {integrity: sha512-O38bPbI2CWtgw/OoQoY+BRelw7uysmXbWvw3nLWO21H1HSh+GOlqPuXshJfjmpNlKiiSDG9cc1JZAaMmVdcTlw==} + dependencies: + '@types/react': 18.0.28 + dev: true + + /@types/react-is@17.0.3: + resolution: {integrity: sha512-aBTIWg1emtu95bLTLx0cpkxwGW3ueZv71nE2YFBpL8k/z5czEW8yYpOo8Dp+UUAFAtKwNaOsh/ioSeQnWlZcfw==} + dependencies: + '@types/react': 18.0.28 + dev: false + + /@types/react-transition-group@4.4.5: + resolution: {integrity: sha512-juKD/eiSM3/xZYzjuzH6ZwpP+/lejltmiS3QEzV/vmb/Q8+HfDmxu+Baga8UEMGBqV88Nbg4l2hY/K2DkyaLLA==} + dependencies: + '@types/react': 18.0.28 + dev: false + + /@types/react@18.0.28: + resolution: {integrity: sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==} + dependencies: + '@types/prop-types': 15.7.5 + '@types/scheduler': 0.16.2 + csstype: 3.1.1 + + /@types/scheduler@0.16.2: + resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} + + /@vitejs/plugin-react-swc@3.2.0(vite@4.1.4): + resolution: {integrity: sha512-IcBoXL/mcH7JdQr/nfDlDwTdIaH8Rg7LpfQDF4nAht+juHWIuv6WhpKPCSfY4+zztAaB07qdBoFz1XCZsgo3pQ==} + peerDependencies: + vite: ^4 + dependencies: + '@swc/core': 1.3.36 + vite: 4.1.4 + dev: true + + /ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: false + + /babel-plugin-macros@3.1.0: + resolution: {integrity: sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==} + engines: {node: '>=10', npm: '>=6'} + dependencies: + '@babel/runtime': 7.21.0 + cosmiconfig: 7.1.0 + resolve: 1.22.1 + dev: false + + /callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + dev: false + + /chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: false + + /clsx@1.2.1: + resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==} + engines: {node: '>=6'} + dev: false + + /color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: false + + /color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: false + + /convert-source-map@1.9.0: + resolution: {integrity: sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==} + dev: false + + /cosmiconfig@7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + dependencies: + '@types/parse-json': 4.0.0 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + dev: false + + /csstype@3.1.1: + resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} + + /csstype@3.1.2: + resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} + dev: false + + /dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + dependencies: + '@babel/runtime': 7.21.0 + csstype: 3.1.1 + dev: false + + /error-ex@1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + dependencies: + is-arrayish: 0.2.1 + dev: false + + /esbuild@0.16.17: + resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.16.17 + '@esbuild/android-arm64': 0.16.17 + '@esbuild/android-x64': 0.16.17 + '@esbuild/darwin-arm64': 0.16.17 + '@esbuild/darwin-x64': 0.16.17 + '@esbuild/freebsd-arm64': 0.16.17 + '@esbuild/freebsd-x64': 0.16.17 + '@esbuild/linux-arm': 0.16.17 + '@esbuild/linux-arm64': 0.16.17 + '@esbuild/linux-ia32': 0.16.17 + '@esbuild/linux-loong64': 0.16.17 + '@esbuild/linux-mips64el': 0.16.17 + '@esbuild/linux-ppc64': 0.16.17 + '@esbuild/linux-riscv64': 0.16.17 + '@esbuild/linux-s390x': 0.16.17 + '@esbuild/linux-x64': 0.16.17 + '@esbuild/netbsd-x64': 0.16.17 + '@esbuild/openbsd-x64': 0.16.17 + '@esbuild/sunos-x64': 0.16.17 + '@esbuild/win32-arm64': 0.16.17 + '@esbuild/win32-ia32': 0.16.17 + '@esbuild/win32-x64': 0.16.17 + dev: true + + /escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: false + + /escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + dev: false + + /find-root@1.1.0: + resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} + dev: false + + /fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /function-bind@1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + + /has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + dev: false + + /has@1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + + /hoist-non-react-statics@3.3.2: + resolution: {integrity: sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==} + dependencies: + react-is: 16.13.1 + dev: false + + /import-fresh@3.3.0: + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} + engines: {node: '>=6'} + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + dev: false + + /is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: false + + /is-core-module@2.11.0: + resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} + dependencies: + has: 1.0.3 + + /js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: false + + /json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + dev: false + + /lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: false + + /loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + dependencies: + js-tokens: 4.0.0 + dev: false + + /nanoid@3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + + /object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + dev: false + + /parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + dependencies: + callsites: 3.1.0 + dev: false + + /parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + dependencies: + '@babel/code-frame': 7.18.6 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + dev: false + + /path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + /path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + dev: false + + /picocolors@1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: true + + /postcss@8.4.21: + resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + + /prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + dev: false + + /react-dom@18.2.0(react@18.2.0): + resolution: {integrity: sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==} + peerDependencies: + react: ^18.2.0 + dependencies: + loose-envify: 1.4.0 + react: 18.2.0 + scheduler: 0.23.0 + dev: false + + /react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + dev: false + + /react-is@18.2.0: + resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} + dev: false + + /react-spinners@0.13.8(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-3e+k56lUkPj0vb5NDXPVFAOkPC//XyhKPJjvcGjyMNPWsBKpplfeyialP74G7H7+It7KzhtET+MvGqbKgAqpZA==} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 + dependencies: + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /react-transition-group@4.4.5(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + dependencies: + '@babel/runtime': 7.21.0 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + + /react@18.2.0: + resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + dev: false + + /regenerator-runtime@0.13.11: + resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} + dev: false + + /resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + dev: false + + /resolve@1.22.1: + resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} + hasBin: true + dependencies: + is-core-module: 2.11.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + /rollup@3.17.3: + resolution: {integrity: sha512-p5LaCXiiOL/wrOkj8djsIDFmyU9ysUxcyW+EKRLHb6TKldJzXpImjcRSR+vgo09DBdofGcOoLOsRyxxG2n5/qQ==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /scheduler@0.23.0: + resolution: {integrity: sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==} + dependencies: + loose-envify: 1.4.0 + dev: false + + /source-map-js@1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: true + + /source-map@0.5.7: + resolution: {integrity: sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==} + engines: {node: '>=0.10.0'} + dev: false + + /stylis@4.1.3: + resolution: {integrity: sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA==} + dev: false + + /supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: false + + /supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + /to-fast-properties@2.0.0: + resolution: {integrity: sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==} + engines: {node: '>=4'} + dev: false + + /typescript@4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + + /vite@4.1.4: + resolution: {integrity: sha512-3knk/HsbSTKEin43zHu7jTwYWv81f8kgAL99G5NWBcA1LKvtvcVAC4JjBH1arBunO9kQka+1oGbrMKOjk4ZrBg==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.16.17 + postcss: 8.4.21 + resolve: 1.22.1 + rollup: 3.17.3 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: false diff --git a/gpgui/public/vite.svg b/gpgui/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/gpgui/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gpgui/src-tauri/.gitignore b/gpgui/src-tauri/.gitignore new file mode 100644 index 0000000..aba21e2 --- /dev/null +++ b/gpgui/src-tauri/.gitignore @@ -0,0 +1,3 @@ +# Generated by Cargo +# will have compiled files and executables +/target/ diff --git a/gpgui/src-tauri/Cargo.toml b/gpgui/src-tauri/Cargo.toml new file mode 100644 index 0000000..f391281 --- /dev/null +++ b/gpgui/src-tauri/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "app" +version = "0.1.0" +description = "A Tauri App" +authors = ["you"] +license = "" +repository = "" +default-run = "app" +edition = "2021" +rust-version = "1.59" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[build-dependencies] +tauri-build = { version = "1.2.1", features = [] } + +[dependencies] +serde_json = "1.0" +serde = { version = "1.0", features = ["derive"] } +tauri = { version = "1.2.4", features = ["http-all"] } +common = { path = "../../common" } + +[features] +# by default Tauri runs in production mode +# when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL +default = [ "custom-protocol" ] +# this feature is used for production builds where `devPath` points to the filesystem +# DO NOT remove this +custom-protocol = [ "tauri/custom-protocol" ] diff --git a/gpgui/src-tauri/build.rs b/gpgui/src-tauri/build.rs new file mode 100644 index 0000000..795b9b7 --- /dev/null +++ b/gpgui/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/gpgui/src-tauri/icons/128x128.png b/gpgui/src-tauri/icons/128x128.png new file mode 100644 index 0000000..77e7d23 Binary files /dev/null and b/gpgui/src-tauri/icons/128x128.png differ diff --git a/gpgui/src-tauri/icons/128x128@2x.png b/gpgui/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..0f7976f Binary files /dev/null and b/gpgui/src-tauri/icons/128x128@2x.png differ diff --git a/gpgui/src-tauri/icons/32x32.png b/gpgui/src-tauri/icons/32x32.png new file mode 100644 index 0000000..98fda06 Binary files /dev/null and b/gpgui/src-tauri/icons/32x32.png differ diff --git a/gpgui/src-tauri/icons/Square107x107Logo.png b/gpgui/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..f35d84f Binary files /dev/null and b/gpgui/src-tauri/icons/Square107x107Logo.png differ diff --git a/gpgui/src-tauri/icons/Square142x142Logo.png b/gpgui/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..1823bb2 Binary files /dev/null and b/gpgui/src-tauri/icons/Square142x142Logo.png differ diff --git a/gpgui/src-tauri/icons/Square150x150Logo.png b/gpgui/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..dc2b22c Binary files /dev/null and b/gpgui/src-tauri/icons/Square150x150Logo.png differ diff --git a/gpgui/src-tauri/icons/Square284x284Logo.png b/gpgui/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..0ed3984 Binary files /dev/null and b/gpgui/src-tauri/icons/Square284x284Logo.png differ diff --git a/gpgui/src-tauri/icons/Square30x30Logo.png b/gpgui/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..60bf0ea Binary files /dev/null and b/gpgui/src-tauri/icons/Square30x30Logo.png differ diff --git a/gpgui/src-tauri/icons/Square310x310Logo.png b/gpgui/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..c8ca0ad Binary files /dev/null and b/gpgui/src-tauri/icons/Square310x310Logo.png differ diff --git a/gpgui/src-tauri/icons/Square44x44Logo.png b/gpgui/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..8756459 Binary files /dev/null and b/gpgui/src-tauri/icons/Square44x44Logo.png differ diff --git a/gpgui/src-tauri/icons/Square71x71Logo.png b/gpgui/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..2c8023c Binary files /dev/null and b/gpgui/src-tauri/icons/Square71x71Logo.png differ diff --git a/gpgui/src-tauri/icons/Square89x89Logo.png b/gpgui/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..2c5e603 Binary files /dev/null and b/gpgui/src-tauri/icons/Square89x89Logo.png differ diff --git a/gpgui/src-tauri/icons/StoreLogo.png b/gpgui/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..17d142c Binary files /dev/null and b/gpgui/src-tauri/icons/StoreLogo.png differ diff --git a/gpgui/src-tauri/icons/icon.icns b/gpgui/src-tauri/icons/icon.icns new file mode 100644 index 0000000..a2993ad Binary files /dev/null and b/gpgui/src-tauri/icons/icon.icns differ diff --git a/gpgui/src-tauri/icons/icon.ico b/gpgui/src-tauri/icons/icon.ico new file mode 100644 index 0000000..06c23c8 Binary files /dev/null and b/gpgui/src-tauri/icons/icon.ico differ diff --git a/gpgui/src-tauri/icons/icon.png b/gpgui/src-tauri/icons/icon.png new file mode 100644 index 0000000..d1756ce Binary files /dev/null and b/gpgui/src-tauri/icons/icon.png differ diff --git a/gpgui/src-tauri/src/main.rs b/gpgui/src-tauri/src/main.rs new file mode 100644 index 0000000..9fc565d --- /dev/null +++ b/gpgui/src-tauri/src/main.rs @@ -0,0 +1,65 @@ +#![cfg_attr( + all(not(debug_assertions), target_os = "windows"), + windows_subsystem = "windows" +)] + +use common::{Client, ServerApiError, VpnStatus}; +use serde::Serialize; +use std::sync::Arc; +use tauri::{Manager, State}; + +#[tauri::command] +async fn vpn_status<'a>(client: State<'a, Arc>) -> Result { + client.status().await +} + +#[tauri::command] +async fn vpn_connect<'a>( + server: String, + cookie: String, + client: State<'a, Arc>, +) -> Result<(), ServerApiError> { + client.connect(server, cookie).await +} + +#[tauri::command] +async fn vpn_disconnect<'a>(client: State<'a, Arc>) -> Result<(), ServerApiError> { + client.disconnect().await +} + +#[derive(Debug, Clone, Serialize)] +struct StatusPayload { + status: VpnStatus, +} + +fn setup(app: &mut tauri::App) -> Result<(), Box> { + let client = Arc::new(Client::default()); + let client_clone = client.clone(); + let app_handle = app.handle(); + + tauri::async_runtime::spawn(async move { + let _ = client_clone.subscribe_status(move |status| { + let payload = StatusPayload { status }; + if let Err(err) = app_handle.emit_all("vpn-status-received", payload) { + println!("Error emmiting event: {}", err); + } + }); + + let _ = client_clone.run().await; + }); + + app.manage(client); + Ok(()) +} + +fn main() { + tauri::Builder::default() + .setup(setup) + .invoke_handler(tauri::generate_handler![ + vpn_status, + vpn_connect, + vpn_disconnect + ]) + .run(tauri::generate_context!()) + .expect("error while running tauri application"); +} diff --git a/gpgui/src-tauri/tauri.conf.json b/gpgui/src-tauri/tauri.conf.json new file mode 100644 index 0000000..941b835 --- /dev/null +++ b/gpgui/src-tauri/tauri.conf.json @@ -0,0 +1,70 @@ +{ + "$schema": "../node_modules/@tauri-apps/cli/schema.json", + "build": { + "beforeBuildCommand": "pnpm build", + "beforeDevCommand": "pnpm dev", + "devPath": "http://localhost:5173", + "distDir": "../dist" + }, + "package": { + "productName": "gpgui", + "version": "0.1.0" + }, + "tauri": { + "allowlist": { + "http": { + "all": true, + "request": true, + "scope": ["https://**"] + } + }, + "bundle": { + "active": true, + "category": "DeveloperTool", + "copyright": "", + "deb": { + "depends": [] + }, + "externalBin": [], + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "identifier": "com.tauri.dev", + "longDescription": "", + "macOS": { + "entitlements": null, + "exceptionDomain": "", + "frameworks": [], + "providerShortName": null, + "signingIdentity": null + }, + "resources": [], + "shortDescription": "", + "targets": "all", + "windows": { + "certificateThumbprint": null, + "digestAlgorithm": "sha256", + "timestampUrl": "" + } + }, + "security": { + "csp": null + }, + "updater": { + "active": false + }, + "windows": [ + { + "fullscreen": false, + "height": 360, + "resizable": false, + "title": "GlobalProtect", + "width": 260 + } + ] + } +} diff --git a/gpgui/src/App.css b/gpgui/src/App.css new file mode 100644 index 0000000..342fc8b --- /dev/null +++ b/gpgui/src/App.css @@ -0,0 +1,10 @@ +html { + height: 100%; + -webkit-user-select: none; + user-select: none; +} + +body { + height: 100%; + background: #f6f6f6 !important; +} diff --git a/gpgui/src/App.tsx b/gpgui/src/App.tsx new file mode 100644 index 0000000..44146a8 --- /dev/null +++ b/gpgui/src/App.tsx @@ -0,0 +1,202 @@ +import { Box, TextField } from "@mui/material"; +import Button from "@mui/material/Button"; +import { ChangeEvent, useEffect, useState } from "react"; + +import "./App.css"; +import ConnectionStatus, { Status } from "./components/ConnectionStatus"; +import Notification, { NotificationConfig } from "./components/Notification"; +import PasswordAuth, { + Credentials, + PasswordAuthData, +} from "./components/PasswordAuth"; +import gatewayService from "./services/gatewayService"; +import portalService from "./services/portalService"; +import vpnService from "./services/vpnService"; + +export default function App() { + const [portalAddress, setPortalAddress] = useState("220.191.185.154"); + const [status, setStatus] = useState("disconnected"); + const [processing, setProcessing] = useState(false); + const [passwordAuthOpen, setPasswordAuthOpen] = useState(false); + const [passwordAuthenticating, setPasswordAuthenticating] = useState(false); `` + const [passwordAuth, setPasswordAuth] = useState(); + const [notification, setNotification] = useState({ + open: false, + message: "", + }); + + useEffect(() => { + return vpnService.onStatusChanged((latestStatus) => { + console.log("status changed", latestStatus); + setStatus(latestStatus); + if (latestStatus === "connected") { + clearOverlays(); + } + }); + }, []); + + function closeNotification() { + setNotification((notification) => ({ + ...notification, + open: false, + })); + } + + function clearOverlays() { + closeNotification() + setPasswordAuthenticating(false) + setPasswordAuthOpen(false) + } + + function handlePortalChange(e: ChangeEvent) { + const { value } = e.target; + setPortalAddress(value.trim()); + } + + async function handleConnect() { + setProcessing(true); + // setStatus("connecting"); + + try { + const response = await portalService.prelogin(portalAddress); + + if (portalService.isSamlAuth(response)) { + // TODO SAML login + } else if (portalService.isPasswordAuth(response)) { + setPasswordAuthOpen(true); + setPasswordAuth({ + authMessage: response.authMessage, + labelPassword: response.labelPassword, + labelUsername: response.labelUsername, + }); + } else { + throw new Error("Unsupported portal login method"); + } + } catch (e) { + setProcessing(false); + } + } + + function handleCancel() { + // TODO cancel the request first + setProcessing(false) + } + + async function handleDisconnect() { + setProcessing(true); + + try { + await vpnService.disconnect(); + } catch (err: any) { + setNotification({ + open: true, + type: "error", + title: "Failed to disconnect", + message: err.message, + }); + } finally { + setProcessing(false); + } + } + + async function handlePasswordAuth({ username, password }: Credentials) { + try { + setPasswordAuthenticating(true); + const portalConfigResponse = await portalService.fetchConfig({ + portal: portalAddress, + username, + password, + }); + + const { gateways, preferredGateway, userAuthCookie } = + portalConfigResponse; + + if (gateways.length === 0) { + // TODO handle no gateways, treat the portal as a gateway + throw new Error("No gateways found"); + } + + const token = await gatewayService.login({ + gateway: preferredGateway, + username, + password, + userAuthCookie, + }); + + await vpnService.connect(preferredGateway.address!, token); + setProcessing(false); + } catch (err: any) { + console.error(err); + setNotification({ + open: true, + type: "error", + title: "Login failed", + message: err.message, + }); + } finally { + setPasswordAuthenticating(false); + } + } + + function cancelPasswordAuth() { + setPasswordAuthenticating(false); + setPasswordAuthOpen(false); + setProcessing(false); + } + return ( + + + + + + {status === "disconnected" && ( + + )} + {status === "connecting" && ( + + )} + {status === "connected" && ( + + )} + + + + + ); +} diff --git a/gpgui/src/assets/react.svg b/gpgui/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/gpgui/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/gpgui/src/components/ConnectionStatus.tsx b/gpgui/src/components/ConnectionStatus.tsx new file mode 100644 index 0000000..13402ec --- /dev/null +++ b/gpgui/src/components/ConnectionStatus.tsx @@ -0,0 +1,104 @@ +import GppBadIcon from "@mui/icons-material/GppBad"; +import VerifiedIcon from "@mui/icons-material/VerifiedUser"; +import { + Box, + BoxProps, + CircularProgress, + Typography, + useTheme, +} from "@mui/material"; +import { BeatLoader } from "react-spinners"; + +export type Status = + | "processing" + | "disconnected" + | "connecting" + | "connected" + | "disconnecting"; + +export const statusTextMap: Record = { + processing: "Processing...", + connected: "Connected", + disconnected: "Not Connected", + connecting: "Connecting...", + disconnecting: "Disconnecting...", +}; + +export default function ConnectionStatus( + props: BoxProps<"div", { status?: Status }> +) { + const theme = useTheme(); + const { status = "disconnected" } = props; + const { palette } = theme; + const colorsMap: Record = { + processing: palette.info.main, + connected: palette.success.main, + disconnected: palette.action.disabled, + connecting: palette.info.main, + disconnecting: palette.info.main, + }; + + const pending = ["processing", "connecting", "disconnecting"].includes(status); + const connected = status === "connected"; + const disconnected = status === "disconnected"; + + return ( + + + + {pending && } + + {connected && ( + + )} + + {disconnected && ( + + )} + + + + {statusTextMap[status]} + + + ); +} diff --git a/gpgui/src/components/Notification.tsx b/gpgui/src/components/Notification.tsx new file mode 100644 index 0000000..b8768b3 --- /dev/null +++ b/gpgui/src/components/Notification.tsx @@ -0,0 +1,68 @@ +import { + Alert, + AlertColor, + AlertTitle, + Slide, + SlideProps, + Snackbar, + SnackbarCloseReason, +} from "@mui/material"; + +type TransitionProps = Omit; + +function TransitionDown(props: TransitionProps) { + return ; +} + +export type NotificationType = AlertColor; +export type NotificationConfig = { + open: boolean; + message: string; + title?: string; + type?: NotificationType; +}; + +type NotificationProps = { + onClose: () => void; +} & NotificationConfig; + +export default function Notification(props: NotificationProps) { + const { open, message, title, type = "info", onClose } = props; + + function handleClose( + _: React.SyntheticEvent | Event, + reason?: SnackbarCloseReason + ) { + if (reason === "clickaway") { + return; + } + onClose(); + } + + return ( + + + {title && {title}} + {message} + + + ); +} diff --git a/gpgui/src/components/PasswordAuth.tsx b/gpgui/src/components/PasswordAuth.tsx new file mode 100644 index 0000000..6ace68f --- /dev/null +++ b/gpgui/src/components/PasswordAuth.tsx @@ -0,0 +1,120 @@ +import LoadingButton from "@mui/lab/LoadingButton"; +import { Box, Button, Drawer, TextField, Typography } from "@mui/material"; +import { FormEvent, useEffect, useRef, useState } from "react"; +import { Maybe } from "../types"; + +export type PasswordAuthData = { + labelUsername: string; + labelPassword: string; + authMessage: Maybe; +}; + +export type Credentials = { + username: string; + password: string; +}; + +type LoginCallback = (params: Credentials) => void; + +type Props = { + open: boolean; + authData: PasswordAuthData | undefined; + authenticating: boolean; + onCancel: () => void; + onLogin: LoginCallback; +}; + +type AuthFormProps = { + authenticating: boolean; + onCancel: () => void; + onSubmit: LoginCallback; +} & PasswordAuthData; + +function AuthForm(props: AuthFormProps) { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const inputRef = useRef(null); + + useEffect(() => { + inputRef.current?.querySelector("input")?.focus(); + }, []); + + const { + authenticating, + authMessage, + labelUsername, + labelPassword, + onCancel, + onSubmit, + } = props; + + function handleSubmit(e: FormEvent) { + e.preventDefault(); + + if (username.trim() === "" || password === "") { + return; + } + + onSubmit({ username, password }); + } + + return ( +
+ + {authMessage} + setUsername(e.target.value)} + /> + setPassword(e.target.value)} + /> + + + + Login + + + +
+ ); +} + +export default function PasswordAuth(props: Props) { + const { open, authData, authenticating, onCancel, onLogin } = props; + + return ( + + {authData && ( + + )} + + ); +} diff --git a/gpgui/src/main.tsx b/gpgui/src/main.tsx new file mode 100644 index 0000000..cf02056 --- /dev/null +++ b/gpgui/src/main.tsx @@ -0,0 +1,11 @@ +import { CssBaseline } from '@mui/material' +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' + +ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( + + + + , +) diff --git a/gpgui/src/services/gatewayService.ts b/gpgui/src/services/gatewayService.ts new file mode 100644 index 0000000..fd9f3cd --- /dev/null +++ b/gpgui/src/services/gatewayService.ts @@ -0,0 +1,70 @@ +import { Body, ResponseType, fetch } from "@tauri-apps/api/http"; +import { Maybe } from "../types"; +import { parseXml } from "../utils/parseXml"; +import { Gateway } from "./types"; + +type LoginParams = { + gateway: Gateway; + username: string; + password: string; + userAuthCookie: Maybe; +}; + +class GatewayService { + async login(params: LoginParams) { + const { gateway, username, password, userAuthCookie } = params; + if (!gateway.address) { + throw new Error("Gateway address is required"); + } + + const loginUrl = `https://${gateway.address}/ssl-vpn/login.esp`; + + const response = await fetch(loginUrl, { + method: "POST", + headers: { + "User-Agent": "PAN GlobalProtect", + }, + responseType: ResponseType.Text, + body: Body.form({ + prot: "https:", + inputStr: "", + jnlpReady: "jnlpReady", + computer: "Linux", // TODO + ok: "Login", + direct: "yes", + "ipv6-support": "yes", + clientVer: "4100", + clientos: "Linux", + "os-version": "Linux", + server: gateway.address, + user: username, + passwd: password, + "portal-userauthcookie": userAuthCookie ?? "", + "portal-prelogonuserauthcookie": "", + "prelogin-cookie": "", + }), + }); + + if (!response.ok) { + throw new Error("Login failed"); + } + + return this.parseLoginResponse(response.data); + } + + private parseLoginResponse(response: string) { + const result = parseXml(response); + const query = new URLSearchParams(); + + query.append("authcookie", result.text("argument:nth-child(2)")); + query.append("portal", result.text("argument:nth-child(4)")); + query.append("user", result.text("argument:nth-child(5)")); + query.append("domain", result.text("argument:nth-child(8)")); + query.append("preferred-ip", result.text("argument:nth-child(16)")); + query.append("computer", "Linux"); + + return query.toString(); + } +} + +export default new GatewayService(); diff --git a/gpgui/src/services/portalService.ts b/gpgui/src/services/portalService.ts new file mode 100644 index 0000000..077b88f --- /dev/null +++ b/gpgui/src/services/portalService.ts @@ -0,0 +1,158 @@ +import { Body, ResponseType, fetch } from "@tauri-apps/api/http"; +import { Maybe, MaybeProperties } from "../types"; +import { parseXml } from "../utils/parseXml"; +import { Gateway } from "./types"; + +type SamlPreloginResponse = { + samlAuthMethod: string; + samlAuthRequest: string; +}; + +type PasswordPreloginResponse = { + labelUsername: string; + labelPassword: string; + authMessage: Maybe; +}; + +type Region = { + region: string; +}; + +type PreloginResponse = MaybeProperties< + SamlPreloginResponse & PasswordPreloginResponse & Region +>; + +type ConfigResponse = { + userAuthCookie: Maybe; + prelogonUserAuthCookie: Maybe; + preferredGateway: Gateway; + gateways: Gateway[]; +}; + +class PortalService { + async prelogin(portal: string) { + const preloginUrl = `https://${portal}/global-protect/prelogin.esp`; + + const response = await fetch(preloginUrl, { + method: "GET", + responseType: ResponseType.Text, + query: { + tmp: "tmp", + "kerberos-support": "yes", + "ipv6-support": "yes", + clientVer: "4100", + clientos: "Linux", + }, + }); + + if (!response.ok) { + throw new Error(`Failed to connect to portal: ${response.status}`); + } + return this.parsePreloginResponse(response.data); + } + + private parsePreloginResponse(response: string): PreloginResponse { + const doc = parseXml(response); + + return { + samlAuthMethod: doc.text("saml-auth-method"), + samlAuthRequest: doc.text("saml-auth-request"), + labelUsername: doc.text("username-label"), + labelPassword: doc.text("password-label"), + authMessage: doc.text("authentication-message"), + region: doc.text("region"), + }; + } + + isSamlAuth(response: PreloginResponse): response is SamlPreloginResponse { + if (response.samlAuthMethod && response.samlAuthRequest) { + return true; + } + return false; + } + + isPasswordAuth( + response: PreloginResponse + ): response is PasswordPreloginResponse { + if (response.labelUsername && response.labelPassword) { + return true; + } + return false; + } + + async fetchConfig({ + portal, + username, + password, + }: { + portal: string; + username: string; + password: string; + }) { + const configUrl = `https://${portal}/global-protect/getconfig.esp`; + const response = await fetch(configUrl, { + method: "POST", + headers: { + "User-Agent": "PAN GlobalProtect", + }, + responseType: ResponseType.Text, + body: Body.form({ + prot: "https:", + inputStr: "", + jnlpReady: "jnlpReady", + computer: "Linux", // TODO + clientos: "Linux", + ok: "Login", + direct: "yes", + clientVer: "4100", + "os-version": "Linux", + "ipv6-support": "yes", + server: portal, + user: username, + passwd: password, + "portal-userauthcookie": "", + "portal-prelogonuserauthcookie": "", + "prelogin-cookie": "", + }), + }); + + if (!response.ok) { + console.error(response); + throw new Error(`Failed to fetch portal config: ${response.status}`); + } + + return this.parsePortalConfigResponse(response.data); + } + + private parsePortalConfigResponse(response: string): ConfigResponse { + const result = parseXml(response); + const gateways = result.all("gateways list > entry").map((entry) => { + const address = entry.attr("name"); + const name = entry.text("description"); + const priority = entry.text(":scope > priority"); + + return { + name, + address, + priority: priority ? parseInt(priority, 10) : undefined, + priorityRules: entry.all("priority-rule > entry").map((entry) => { + const name = entry.attr("name"); + const priority = entry.text("priority"); + return { + name, + priority: priority ? parseInt(priority, 10) : undefined, + }; + }), + }; + }); + + return { + userAuthCookie: result.text("portal-userauthcookie"), + prelogonUserAuthCookie: result.text("portal-prelogonuserauthcookie"), + preferredGateway: gateways[0], + gateways, + }; + } +} + +export default new PortalService(); diff --git a/gpgui/src/services/types.ts b/gpgui/src/services/types.ts new file mode 100644 index 0000000..5bbc6de --- /dev/null +++ b/gpgui/src/services/types.ts @@ -0,0 +1,13 @@ +import { Maybe } from '../types'; + +type PriorityRule = { + name: Maybe; + priority: Maybe; +}; + +export type Gateway = { + name: Maybe; + address: Maybe; + priorityRules: PriorityRule[]; + priority: Maybe; +}; diff --git a/gpgui/src/services/vpnService.ts b/gpgui/src/services/vpnService.ts new file mode 100644 index 0000000..1f827a4 --- /dev/null +++ b/gpgui/src/services/vpnService.ts @@ -0,0 +1,72 @@ +import { invoke } from "@tauri-apps/api"; +import { listen } from '@tauri-apps/api/event'; + +type Status = 'disconnected' | 'connecting' | 'connected' | 'disconnecting' +type StatusCallback = (status: Status) => void +type StatusEvent = { + payload: { + status: Status + } +} + +class VpnService { + private _status: Status = 'disconnected'; + private statusCallbacks: StatusCallback[] = []; + + constructor() { + this.init(); + } + + private async init() { + const unlisten = await listen('vpn-status-received', (event: StatusEvent) => { + console.log('vpn-status-received', event.payload) + this.setStatus(event.payload.status); + }) + + const status = await this.status(); + this.setStatus(status); + } + + private setStatus(status: Status) { + if (this._status != status) { + this._status = status; + this.fireStatusCallbacks(); + } + } + + private async status(): Promise { + return this.invokeCommand("vpn_status"); + } + + async connect(server: string, cookie: string) { + return this.invokeCommand("vpn_connect", { server, cookie }); + } + + async disconnect() { + return this.invokeCommand("vpn_disconnect"); + } + + onStatusChanged(callback: StatusCallback) { + this.statusCallbacks.push(callback); + callback(this._status); + return () => this.removeStatusCallback(callback); + } + + private fireStatusCallbacks() { + this.statusCallbacks.forEach(cb => cb(this._status)); + } + + private removeStatusCallback(callback: StatusCallback) { + this.statusCallbacks = this.statusCallbacks.filter(cb => cb !== callback); + } + + private async invokeCommand(command: string, args?: any) { + try { + return await invoke(command, args); + } catch (err: any) { + throw new Error(err.message); + } + } +} + +export default new VpnService(); diff --git a/gpgui/src/types.ts b/gpgui/src/types.ts new file mode 100644 index 0000000..254ba00 --- /dev/null +++ b/gpgui/src/types.ts @@ -0,0 +1,5 @@ +export type Maybe = T | null | undefined; + +export type MaybeProperties = { + [P in keyof T]?: Maybe; +}; diff --git a/gpgui/src/utils/parseXml.ts b/gpgui/src/utils/parseXml.ts new file mode 100644 index 0000000..883487d --- /dev/null +++ b/gpgui/src/utils/parseXml.ts @@ -0,0 +1,31 @@ +type ParseResult = { + text: (selector: string) => string; + all: (selector: string) => ParseResult[]; + attr: (selector: string, attr?: string) => string; +}; + +export function parseXml(xml: string): ParseResult { + const parser = new DOMParser(); + const doc = parser.parseFromString(xml, "text/xml"); + + return buildParseResult(doc.documentElement); +} + +function buildParseResult(el: Element): ParseResult { + return { + text(selector) { + return el.querySelector(selector)?.textContent ?? ''; + }, + all(selector) { + return [...el.querySelectorAll(selector)].map((node) => { + return buildParseResult(node); + }); + }, + attr(attr, selector) { + if (selector) { + return el.querySelector(selector)?.getAttribute(attr) ?? ''; + } + return el.getAttribute(attr) ?? ''; + }, + }; +} diff --git a/gpgui/src/vite-env.d.ts b/gpgui/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/gpgui/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/gpgui/tsconfig.json b/gpgui/tsconfig.json new file mode 100644 index 0000000..3d0a51a --- /dev/null +++ b/gpgui/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ESNext", + "useDefineForClassFields": true, + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "allowJs": false, + "skipLibCheck": true, + "esModuleInterop": false, + "allowSyntheticDefaultImports": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "module": "ESNext", + "moduleResolution": "Node", + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx" + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/gpgui/tsconfig.node.json b/gpgui/tsconfig.node.json new file mode 100644 index 0000000..9d31e2a --- /dev/null +++ b/gpgui/tsconfig.node.json @@ -0,0 +1,9 @@ +{ + "compilerOptions": { + "composite": true, + "module": "ESNext", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/gpgui/vite.config.ts b/gpgui/vite.config.ts new file mode 100644 index 0000000..861b04b --- /dev/null +++ b/gpgui/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react-swc' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}) diff --git a/gpservice/.gitignore b/gpservice/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/gpservice/.gitignore @@ -0,0 +1 @@ +/target diff --git a/gpservice/Cargo.toml b/gpservice/Cargo.toml new file mode 100644 index 0000000..2daef7a --- /dev/null +++ b/gpservice/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "gpservice" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +common = { path = "../common" } +tokio = { version = "1", features = ["full"] } +# warp = "0.3" +# aes-gcm = "0.10" +# procfs = "0.15" + +[build-dependencies] +common = { path = "../common" } diff --git a/gpservice/build.rs b/gpservice/build.rs new file mode 100644 index 0000000..e590261 --- /dev/null +++ b/gpservice/build.rs @@ -0,0 +1,25 @@ +use common::sha256_digest; +use std::path::Path; +use std::{env, fs}; + +fn main() { + let gpservice_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let profile = env::var("PROFILE").unwrap(); + let gpclient_path = Path::new(&gpservice_dir) + .join("../target") + .join(profile) + .join("gpclient"); + + if !gpclient_path.exists() { + // error if gpclient doesn't exist + panic!("Please build gpclient first"); + } + + if let Ok(digest) = sha256_digest(gpclient_path) { + let out_dir = env::var("OUT_DIR").unwrap(); + let dest_path = format!("{out_dir}/client_hash.rs"); + fs::write(dest_path, format!("pub const GPCLIENT_HASH: &str = \"{digest}\";")).unwrap(); + } else { + panic!("Error: Unable to get gpclient hash"); + } +} diff --git a/gpservice/src/main.rs b/gpservice/src/main.rs new file mode 100644 index 0000000..004946e --- /dev/null +++ b/gpservice/src/main.rs @@ -0,0 +1,113 @@ +include!(concat!(env!("OUT_DIR"), "/client_hash.rs")); + +// use aes_gcm::{aead::OsRng, Aes256Gcm, KeyInit}; +use common::{server, SOCKET_PATH}; +use tokio::signal; + +// static mut HTTP_PORT: u16 = 0; +// static mut AES_KEY: [u8; 32] = [0; 32]; + +// async fn start_unix_server() { +// println!("Starting unix server..."); +// // remove old socket file +// match std::fs::remove_file(SOCKET_PATH) { +// Ok(_) => println!("Removed old socket file"), +// Err(err) if err.kind() != std::io::ErrorKind::NotFound => { +// println!("Error: {err}"); +// return; +// } +// Err(_) => (), +// } + +// let listener = UnixListener::bind(SOCKET_PATH).unwrap(); + +// // set the socket file permissions to 666 +// let Ok(metadata) = fs::metadata(SOCKET_PATH) else { +// return; +// }; + +// let mut permissions = metadata.permissions(); +// permissions.set_mode(0o666); +// fs::set_permissions(SOCKET_PATH, permissions).unwrap(); + +// loop { +// let (stream, _) = listener.accept().await.unwrap(); +// tokio::spawn(handle_unix_client(stream)); +// } +// } + +// async fn handle_unix_client(mut stream: UnixStream) { +// if !is_validate_client(&mut stream).await { +// println!("Invalid client"); +// stream.shutdown().await.unwrap(); +// return; +// } + +// Read + +// let mut message: [u8; 34] = [0; 34]; +// unsafe { +// message[0..2].copy_from_slice(&HTTP_PORT.to_be_bytes()); +// message[2..34].copy_from_slice(&AES_KEY[..]); +// } +// stream.write_all(&message[..]).await.unwrap(); +// } + +// async fn is_validate_client(stream: &mut UnixStream) -> bool { +// let Ok(ucred) = stream.peer_cred() else { +// return false; +// }; + +// if let Some(pid) = ucred.pid() { +// let Ok(proc) = Process::new(pid) else { +// return false; +// }; + +// let Ok(exe) = proc.exe() else { +// return false; +// }; + +// let Ok(exe_hash) = sha256_digest(exe) else { +// return false; +// }; +// return exe_hash == GPCLIENT_HASH; +// } + +// false +// } + +// async fn start_http_server() { +// println!("Starting http server..."); +// // Match any request and return hello world! +// let routes = warp::any().map(|| "Hello, World!"); +// let (addr, server) = +// warp::serve(routes).bind_with_graceful_shutdown(([127, 0, 0, 1], 0), async { +// tokio::signal::ctrl_c().await.unwrap(); +// }); + +// unsafe { +// HTTP_PORT = addr.port(); +// } + +// println!("Listening on http://{addr}/"); +// tokio::spawn(server).await.unwrap(); +// println!("Shutting down http server"); +// } + +#[tokio::main] +async fn main() -> Result<(), Box> { + // println!("{GPCLIENT_HASH}"); + + // unsafe { + // let aes_key = Aes256Gcm::generate_key(&mut OsRng); + // AES_KEY = aes_key.as_slice().try_into().unwrap(); + // } + // tokio::spawn(start_unix_server()); + // start_http_server().await; + // server::start().await + + if let Err(err) = server::run(SOCKET_PATH, signal::ctrl_c()).await { + println!("Error starting the server: {err}"); + } + Ok(()) +} diff --git a/packaging/aur/PKGBUILD b/packaging/aur/PKGBUILD deleted file mode 100644 index 7b663ee..0000000 --- a/packaging/aur/PKGBUILD +++ /dev/null @@ -1,40 +0,0 @@ -# Maintainer: Keinv Yue - -_pkgver="1.4.9" -_commit="acf184134a2ff19e4a39528bd6a7fbbafa4cf017" -pkgname=globalprotect-openconnect-git -pkgver=${_pkgver} -pkgrel=1 -pkgdesc="A GlobalProtect VPN client (GUI) for Linux based on Openconnect and built with Qt5, supports SAML auth mode. (development version)" -arch=(x86_64 aarch64) -url="https://github.com/yuezk/GlobalProtect-openconnect" -license=('GPL3') -backup=( - etc/gpservice/gp.conf -) -install=gp.install -depends=('openconnect>=8.0.0' qt5-base qt5-webengine qt5-websockets qt5-tools qtkeychain-qt5) -makedepends=(git cmake) -conflicts=('globalprotect-openconnect') -provides=('globalprotect-openconnect' 'gpclient' 'gpservice') - -source=(git+https://github.com/yuezk/GlobalProtect-openconnect#commit=${_commit}) -sha256sums=('SKIP') - -prepare() { - cd GlobalProtect-openconnect - echo "${_pkgver}" > VERSION -} - -build() { - cd GlobalProtect-openconnect - cmake -B build \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_CXX_FLAGS_RELEASE=-s - make -j$(nproc) -C build -} - -package() { - cd GlobalProtect-openconnect - make DESTDIR="$pkgdir/" install -C build -} diff --git a/packaging/aur/PKGBUILD.in b/packaging/aur/PKGBUILD.in deleted file mode 100644 index eb8b59b..0000000 --- a/packaging/aur/PKGBUILD.in +++ /dev/null @@ -1,40 +0,0 @@ -# Maintainer: Keinv Yue - -_pkgver="{VERSION}" -_commit="{COMMIT}" -pkgname=globalprotect-openconnect-git -pkgver=${_pkgver} -pkgrel=1 -pkgdesc="A GlobalProtect VPN client (GUI) for Linux based on Openconnect and built with Qt5, supports SAML auth mode. (development version)" -arch=(x86_64 aarch64) -url="https://github.com/yuezk/GlobalProtect-openconnect" -license=('GPL3') -backup=( - etc/gpservice/gp.conf -) -install=gp.install -depends=('openconnect>=8.0.0' qt5-base qt5-webengine qt5-websockets qt5-tools qtkeychain-qt5) -makedepends=(git cmake) -conflicts=('globalprotect-openconnect') -provides=('globalprotect-openconnect' 'gpclient' 'gpservice') - -source=(git+https://github.com/yuezk/GlobalProtect-openconnect#commit=${_commit}) -sha256sums=('SKIP') - -prepare() { - cd GlobalProtect-openconnect - echo "${_pkgver}" > VERSION -} - -build() { - cd GlobalProtect-openconnect - cmake -B build \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_CXX_FLAGS_RELEASE=-s - make -j$(nproc) -C build -} - -package() { - cd GlobalProtect-openconnect - make DESTDIR="$pkgdir/" install -C build -} diff --git a/packaging/aur/gp.install b/packaging/aur/gp.install deleted file mode 100755 index 063a2a3..0000000 --- a/packaging/aur/gp.install +++ /dev/null @@ -1,8 +0,0 @@ -post_install() { - systemctl enable gpservice.service - systemctl restart gpservice.service -} - -post_upgrade() { - post_install -} diff --git a/packaging/flatpak/com.yuezk.qt.gpclient.yml b/packaging/flatpak/com.yuezk.qt.gpclient.yml deleted file mode 100644 index 5384727..0000000 --- a/packaging/flatpak/com.yuezk.qt.gpclient.yml +++ /dev/null @@ -1,26 +0,0 @@ -app-id: com.yuezk.qt.gpclient -base: io.qt.qtwebengine.BaseApp -base-version: '5.15' -runtime: org.kde.Platform -runtime-version: '5.15' -sdk: org.kde.Sdk -command: gpclient -finish-args: - - --share=network - - --share=ipc - - --socket=x11 - - --socket=wayland - - --filesystem=host - - --device=dri - - --talk-name=org.kde.StatusNotifierWatcher - - --own-name=org.kde.* - - --system-own-name=com.yuezk.qt.GPService -modules: - - name: gpclient - buildsystem: cmake - config-opts: - - -DCMAKE_BUILD_TYPE=Release - - -DCMAKE_CXX_FLAGS_RELEASE=-s - sources: - - type: archive - path: globalprotect-openconnect.tar.gz diff --git a/packaging/obs/globalprotect-openconnect-rpmlintrc b/packaging/obs/globalprotect-openconnect-rpmlintrc deleted file mode 100644 index a43b389..0000000 --- a/packaging/obs/globalprotect-openconnect-rpmlintrc +++ /dev/null @@ -1 +0,0 @@ -setBadness('suse-dbus-unauthorized-service', 0) \ No newline at end of file diff --git a/packaging/obs/globalprotect-openconnect.changes b/packaging/obs/globalprotect-openconnect.changes deleted file mode 100644 index e90b111..0000000 --- a/packaging/obs/globalprotect-openconnect.changes +++ /dev/null @@ -1,154 +0,0 @@ -------------------------------------------------------------------- -Sun Jan 8 12:58:32 UTC 2023 - k3vinyue@gmail.com - 1.4.9 - -- Update to 1.4.9 - * Updated VERSION, Bumped 1.4.8 –> 1.4.9 - * fix: update cmake version - * fix: correct the package name - * fix: use the dev package - * fix: use qtkeychain package - * fix: add qt5-tools - * fix: add libsecret-1-dev - * fix: add pkg-config - * fix: use cmake 3.16 - * fix: add missing build dependency - * ci: fix CI - * Merge branch 'master' into develop - * feat: expose os-version to settings - * Add two missing dependencies for building on debian (#198) - * ci: assert no library missing - * fix: update qtkeychain - * ci: run gpclient after build - * fix: add qtkeychain - * chore: update CMake file - * Added install instructions for MX Linux. (#190) - * Credentials autocompleting (secure version) (#179) - * Read all saved Gateways (for selecting in Systray) (#181) - * copy install script for debian (#180) - * add es and pt support to change status when connected to vpn (#162) - * fix: improve the cli support - * feat: add --reset option to gpclient - -------------------------------------------------------------------- -Sun Jun 12 12:28:58 UTC 2022 - k3vinyue@gmail.com - 1.4.8 - -- Update to 1.4.8 - * Updated VERSION, Bumped 1.4.7 –> 1.4.8 - * fix: fix compile error - * refactor: simplify the code - * chore: use auto to declare variables - * chore: use c++ 17 - * fix: clear cookies when click the Reset button - * fix: refine the authentication workflow - * chore: PLOG -> LOG - -------------------------------------------------------------------- -Tue Jun 7 13:46:04 UTC 2022 - k3vinyue@gmail.com - 1.4.7 - -- Update to 1.4.7 - * Updated VERSION, Bumped 1.4.6 –> 1.4.7 - * fix: release resources when properly - * fix: add support for parsing tokens from HTML - * handle html comment for saml result with okta 2fa (#156) - * chore: use auto to declare variable - * chore: simplify readme - -------------------------------------------------------------------- -Wed Jun 1 15:55:50 UTC 2022 - k3vinyue@gmail.com - 1.4.6 - -- Update to 1.4.6 - * Updated VERSION, Bumped 1.4.5 –> 1.4.6 - * feat: display address in gateway menu item - * fix: fix bug of parsing the portal response - -------------------------------------------------------------------- -Sun May 29 13:15:40 UTC 2022 - k3vinyue@gmail.com - 1.4.5 - -- Update to 1.4.5 - * Updated VERSION, Bumped 1.4.4 –> 1.4.5 - * chore: refine vscode settings - * fix: rollback dbus configuration - * feat: add option to start minimized - * packaging: fix postinst for debian - * packaging: add postinst for debian - * test: test debian packaging - * ci: fix the folder path - * chore: apt -> apt-get - * ci: verify debian package - * Revert "Revert "fix: improve the dbus security"" - * fix: improve the portal config parsing - * Revert "fix: improve the dbus security" - * fix: improve the dbus security - * fix: free resources in slots - * chore: refine cmake files - * fix: support high DPI screen - -------------------------------------------------------------------- -Sat May 14 11:21:14 UTC 2022 - k3vinyue@gmail.com - 1.4.4 - -- Update to 1.4.4 - * Updated VERSION, Bumped 1.4.3 –> 1.4.4 - * fix: support the HighDPI displays - * [misc] update the build script - * [ci] Enable build job for master branch - * [ci] Add ubuntu 22.04 - -------------------------------------------------------------------- -Mon May 9 14:20:54 UTC 2022 - k3vinyue@gmail.com - 1.4.3 - -- Update to 1.4.3 - * Updated VERSION, Bumped 1.4.2 –> 1.4.3 - * refine AUR packaging - * Prepare release 1.4.3 (#149) - -------------------------------------------------------------------- -Fri May 6 14:18:19 UTC 2022 - k3vinyue@gmail.com - 1.4.2 - -- Update to 1.4.2 - * Updated VERSION, Bumped 1.4.1 –> 1.4.2 - * Clear SSL_OP_LEGACY_SERVER_CONNECT (#146) - -------------------------------------------------------------------- -Thu Mar 3 13:58:59 UTC 2022 - k3vinyue@gmail.com - 1.4.1 - -- Update to 1.4.1 - * Updated VERSION, Bumped 1.4.0 –> 1.4.1 - * print the gpservice logs - * update AUR packaging - -------------------------------------------------------------------- -Wed Mar 2 13:34:19 UTC 2022 - k3vinyue@gmail.com - 1.4.0 - -- Update to 1.4.0 - * Updated VERSION, Bumped 1.3.4 –> 1.4.0 - * Fix gpservice after openconnect v8.20 (#124) - * Add 2FA support (#112) - * Add a scripting mode to GPClient (#110) - * Stop saving credentials (#111) - * update CI - * add editorconfig - * Update README.md - * Add a run entry (#108) - * update the installation instruction of Arch Linux - -------------------------------------------------------------------- -Sun Oct 24 04:13:24 UTC 2021 - k3vinyue@gmail.com - 1.3.4 - -- Update to 1.3.4 - * Updated VERSION, Bumped 1.3.3 –> 1.3.4 - * update packaging (#100) - * shorten the sponsor links - * Update README.md - * add sponsor links - * Adding application logs location in the README (#95) - * improve the doc - * Add snap packaging (#93) - * update doc - * Migrate to cmake and refine the code structure (#92) - * QStringView -> QString - -------------------------------------------------------------------- -Thu Jul 9 02:13:46 UTC 2020 - k3vinyue@gmail.com - 1.3.0 - -- Update to 1.3.0 - * Bump version to 1.3.0 diff --git a/packaging/obs/globalprotect-openconnect.spec b/packaging/obs/globalprotect-openconnect.spec deleted file mode 100644 index defac74..0000000 --- a/packaging/obs/globalprotect-openconnect.spec +++ /dev/null @@ -1,98 +0,0 @@ -Name: globalprotect-openconnect -Version: 1.4.9 -Release: 1 -Summary: A GlobalProtect VPN client powered by OpenConnect -Group: Productivity/Networking/PPP -BuildRoot: %{_tmppath}/%{name}-%{version}-build - -License: GPL-3.0 -URL: https://github.com/yuezk/GlobalProtect-openconnect -Source0: %{name}.tar.gz -BuildRequires: cmake cmake(Qt5) cmake(Qt5Gui) cmake(Qt5WebEngine) cmake(Qt5WebSockets) cmake(Qt5DBus) cmake(Qt5Keychain) -BuildRequires: systemd-rpm-macros -Requires: openconnect >= 8.0 -Conflicts: globalprotect-openconnect-snapshot - - -%global debug_package %{nil} - -%description -A GlobalProtect VPN client (GUI) for Linux based on OpenConnect and built with Qt5, supports SAML auth mode. - - -%prep -%autosetup -n "globalprotect-openconnect-%{version}" - - -%pre - -%if 0%{?suse_version} - %service_add_pre gpservice.service -%endif - - -%post - -%if 0%{?suse_version} - %service_add_post gpservice.service -%else - %systemd_post gpservice.service -%endif - - -%preun - -%if 0%{?suse_version} - %service_del_preun gpservice.service -%else - %systemd_preun gpservice.service -%endif - - -%postun - -%if 0%{?suse_version} - %service_del_postun gpservice.service -%else - %systemd_postun_with_restart gpservice.service -%endif - - -%build - -%cmake -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_CXX_FLAGS_RELEASE=-s - -%if 0%{?fedora_version} && 0%{?fedora_version} <= 32 - %make_build -%else - %cmake_build -%endif - - -%install - -%if 0%{?fedora_version} && 0%{?fedora_version} <= 32 - %make_install -%else - %cmake_install -%endif - -%files -%defattr(-,root,root) -%{_unitdir}/gpservice.service -%{_bindir}/gpclient -%{_bindir}/gpservice -%{_datadir}/applications/com.yuezk.qt.gpclient.desktop -%{_datadir}/dbus-1/system-services/com.yuezk.qt.GPService.service -%{_datadir}/dbus-1/system.d/com.yuezk.qt.GPService.conf -%{_datadir}/icons/hicolor/scalable/apps/com.yuezk.qt.gpclient.svg -%{_datadir}/metainfo/com.yuezk.qt.gpclient.metainfo.xml -%config %{_sysconfdir}/gpservice/gp.conf - -%dir %{_sysconfdir}/gpservice -%dir %{_datadir}/icons/hicolor -%dir %{_datadir}/icons/hicolor/scalable -%dir %{_datadir}/icons/hicolor/scalable/apps - -%changelog diff --git a/scripts/_archive-all.sh b/scripts/_archive-all.sh deleted file mode 100755 index ac1efeb..0000000 --- a/scripts/_archive-all.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/bash -e - -VERSION="$(cat VERSION)" - -rm -rf ./artifacts && mkdir -p ./artifacts/{obs,aur,flatpak} - -# Add the version file -echo $VERSION > ./artifacts/VERSION - -# Archive the source code -git-archive-all \ - --force-submodules \ - --prefix=globalprotect-openconnect-${VERSION}/ \ - ./artifacts/globalprotect-openconnect-${VERSION}.tar.gz - -# Prepare the OBS package -cp -r ./packaging/obs ./artifacts -cp ./artifacts/*.tar.gz ./artifacts/obs/globalprotect-openconnect.tar.gz - -# Prepare the AUR package -cp ./packaging/aur/PKGBUILD ./artifacts/aur/PKGBUILD -cp ./packaging/aur/gp.install ./artifacts/aur/gp.install - -# Prepare the flatpak package -cp ./packaging/flatpak/com.yuezk.qt.gpclient.yml ./artifacts/flatpak -cp ./artifacts/*.tar.gz ./artifacts/flatpak/globalprotect-openconnect.tar.gz \ No newline at end of file diff --git a/scripts/build.sh b/scripts/build.sh deleted file mode 100755 index fd67bf8..0000000 --- a/scripts/build.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/bash -e - -./cmakew -B build \ - -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_CXX_FLAGS_RELEASE=-s - -MAKEFLAGS=-j$(nproc) ./cmakew --build build \ No newline at end of file diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh deleted file mode 100755 index ea5cdd5..0000000 --- a/scripts/bump-version.sh +++ /dev/null @@ -1,452 +0,0 @@ -#!/bin/bash -# -# █▄▄ █░█ █▀▄▀█ █▀█ ▄▄ █░█ █▀▀ █▀█ █▀ █ █▀█ █▄░█ -# █▄█ █▄█ █░▀░█ █▀▀ ░░ ▀▄▀ ██▄ █▀▄ ▄█ █ █▄█ █░▀█ -# -# Description: -# - This script automates bumping the git software project's version using automation. - -# - It does several things that are typically required for releasing a Git repository, like git tagging, -# automatic updating of CHANGELOG.md, and incrementing the version number in various JSON files. - -# - Increments / suggests the current software project's version number -# - Adds a Git tag, named after the chosen version number -# - Updates CHANGELOG.md -# - Updates VERSION file -# - Commits files to a new branch -# - Pushes to remote (optionally) -# - Updates "version" : "x.x.x" tag in JSON files if [-v file1 -v file2...] argument is supplied. -# -# Usage: -# ./bump-version.sh [-v ] [-m ] [-j ] [-j ].. [-n] [-p] [-b] [-h] -# -# Options: -# -v Specify a manual version number -# -m Custom release message. -# -f Update version number inside JSON files. -# * For multiple files, add a separate -f option for each one, -# * For example: ./bump-version.sh -f src/plugin/package.json -f composer.json -# -p Push commits to remote repository, eg `-p origin` -# -n Don't perform a commit automatically. -# * You may want to do that yourself, for example. -# -b Don't create automatic `release-` branch -# -h Show help message. - -# -# Detailed notes: -# – The contents of the `VERSION` file which should be a semantic version number such as "1.2.3" -# or even "1.2.3-beta+001.ab" -# -# – It pulls a list of changes from git history & prepends to a file called CHANGELOG.md -# under the title of the new version # number, allows the user to review and update the changelist -# -# – Creates a Git tag with the version number -# -# - Creates automatic `release-` branch -# -# – Commits the new version to the current repository -# -# – Optionally pushes the commit to remote repository -# -# – Make sure to set execute permissions for the script, eg `$ chmod 755 bump-version.sh` -# -# Credits: -# – https://github.com/jv-k/bump-version -# -# - Inspired by the scripts from @pete-otaqui and @mareksuscak -# https://gist.github.com/pete-otaqui/4188238 -# https://gist.github.com/mareksuscak/1f206fbc3bb9d97dec9c -# - -NOW="$(date +'%B %d, %Y')" - -# ANSI/VT100 colours -YELLOW='\033[1;33m' -LIGHTYELLOW='\033[0;33m' -RED='\033[0;31m' -LIGHTRED='\033[1;31m' -GREEN='\033[0;32m' -LIGHTGREEN='\033[1;32m' -BLUE='\033[0;34m' -LIGHTBLUE='\033[1;34m' -PURPLE='\033[0;35m' -LIGHTPURPLE='\033[1;35m' -CYAN='\033[0;36m' -LIGHTCYAN='\033[1;36m' -WHITE='\033[1;37m' -LIGHTGRAY='\033[0;37m' -DARKGRAY='\033[1;30m' -BOLD="\033[1m" -INVERT="\033[7m" -RESET='\033[0m' - -# Default options -FLAG_JSON="false" -FLAG_PUSH="false" - -I_OK="✅"; I_STOP="🚫"; I_ERROR="❌"; I_END="👋🏻" - -S_NORM="${WHITE}" -S_LIGHT="${LIGHTGRAY}" -S_NOTICE="${GREEN}" -S_QUESTION="${YELLOW}" -S_WARN="${LIGHTRED}" -S_ERROR="${RED}" - -V_SUGGEST="0.1.0" # This is suggested in case VERSION file or user supplied version via -v is missing -GIT_MSG="" -REL_NOTE="" -REL_PREFIX="release-" -PUSH_DEST="origin" - -# Show credits & help -usage() { - echo -e "$GREEN"\ - "\n █▄▄ █░█ █▀▄▀█ █▀█ ▄▄ █░█ █▀▀ █▀█ █▀ █ █▀█ █▄░█ "\ - "\n █▄█ █▄█ █░▀░█ █▀▀ ░░ ▀▄▀ ██▄ █▀▄ ▄█ █ █▄█ █░▀█ "\ - "\n\t\t\t\t\t$LIGHTGRAY v${SCRIPT_VER}"\ - - echo -e " ${S_NORM}${BOLD}Usage:${RESET}"\ - "\n $0 [-v ] [-m ] [-j ] [-j ].. [-n] [-p] [-h]" 1>&2; - - echo -e "\n ${S_NORM}${BOLD}Options:${RESET}" - echo -e " $S_WARN-v$S_NORM \tSpecify a manual version number" - echo -e " $S_WARN-m$S_NORM \tCustom release message." - echo -e " $S_WARN-f$S_NORM \tUpdate version number inside JSON files."\ - "\n\t\t\t* For multiple files, add a separate -f option for each one,"\ - "\n\t\t\t* For example: ./bump-version.sh -f src/plugin/package.json -f composer.json" - echo -e " $S_WARN-p$S_NORM \t\t\tPush commits to ORIGIN. " - echo -e " $S_WARN-n$S_NORM \t\t\tDon't perform a commit automatically. "\ - "\n\t\t\t* You may want to do that manually after checking everything, for example." - echo -e " $S_WARN-b$S_NORM \t\t\tDon't create automatic \`release-\` branch" - echo -e " $S_WARN-h$S_NORM \t\t\tShow this help message. " - echo -e "\n ${S_NORM}${BOLD}Author:$S_LIGHT https://github.com/jv-t/bump-version $RESET\n" - -} - -# If there are no commits in repo, quit, because you can't tag with zero commits. -check-commits-exist() { - git rev-parse HEAD &> /dev/null - if [ ! "$?" -eq 0 ]; then - echo -e "\n${I_STOP} ${S_ERROR}Your current branch doesn't have any commits yet. Can't tag without at least one commit." >&2 - echo - exit 1 - fi -} - -get-commit-msg() { - echo Bumped $([ -n "${V_PREV}" ] && echo "${V_PREV} –>" || echo "to ") "$V_USR_INPUT" -} - -exit_abnormal() { - echo -e " ${S_LIGHT}––––––" - usage # Show help - exit 1 -} - -# Process script options -process-arguments() { - local OPTIONS OPTIND OPTARG - - # Get positional parameters - JSON_FILES=( ) - while getopts ":v:p:m:f:hbn" OPTIONS; do # Note: Adding the first : before the flags takes control of flags and prevents default error msgs. - case "$OPTIONS" in - h ) - # Show help - exit_abnormal - ;; - v ) - # User has supplied a version number - V_USR_SUPPLIED=$OPTARG - ;; - m ) - REL_NOTE=$OPTARG - # Custom release note - echo -e "\n${S_LIGHT}Option set: ${S_NOTICE}Release note:" ${S_NORM}"'"$REL_NOTE"'" - ;; - f ) - FLAG_JSON=true - echo -e "\n${S_LIGHT}Option set: ${S_NOTICE}JSON file via [-f]: <${S_NORM}${OPTARG}${S_LIGHT}>" - # Store JSON filenames(s) - JSON_FILES+=($OPTARG) - ;; - p ) - FLAG_PUSH=true - PUSH_DEST=${OPTARG} # Replace default with user input - echo -e "\n${S_LIGHT}Option set: ${S_NOTICE}Pushing to <${S_NORM}${PUSH_DEST}${S_LIGHT}>, as the last action in this script." - ;; - n ) - FLAG_NOCOMMIT=true - echo -e "\n${S_LIGHT}Option set: ${S_NOTICE}Disable commit after tagging." - ;; - b ) - FLAG_NOBRANCH=true - echo -e "\n${S_LIGHT}Option set: ${S_NOTICE}Disable committing to new branch." - ;; - \? ) - echo -e "\n${I_ERROR}${S_ERROR} Invalid option: ${S_WARN}-$OPTARG" >&2 - echo - exit_abnormal - ;; - : ) - echo -e "\n${I_ERROR}${S_ERROR} Option ${S_WARN}-$OPTARG ${S_ERROR}requires an argument." >&2 - echo - exit_abnormal - ;; - esac - done -} - -# Suggests version from VERSION file, or grabs from user supplied -v . -# If none is set, suggest default from options. -process-version() { - if [ -f VERSION ] && [ -s VERSION ]; then - V_PREV=`cat VERSION` - - echo -e "\n${S_NOTICE}Current version from <${S_NORM}VERSION${S_NOTICE}> file: ${S_NORM}$V_PREV" - - # Suggest incremented value from VERSION file - V_PREV_LIST=(`echo $V_PREV | tr '.' ' '`) - V_MAJOR=${V_PREV_LIST[0]}; V_MINOR=${V_PREV_LIST[1]}; V_PATCH=${V_PREV_LIST[2]}; - - # Test if V_PATCH is a number, then increment it. Otherwise, do nothing - if [ "$V_PATCH" -eq "$V_PATCH" ] 2>/dev/null; then # discard stderr (2) output to black hole (suppress it) - V_PATCH=$((V_PATCH + 1)) # Increment - fi - - V_SUGGEST="$V_MAJOR.$V_MINOR.$V_PATCH" - else - echo -ne "\n${S_WARN}The [${S_NORM}VERSION${S_WARN}] " - if [ ! -f VERSION ]; then - echo "file was not found."; - elif [ ! -s VERSION ]; then - echo "file is empty."; - fi - fi - - # If a version number is supplied by the user with [-v ], then use it - if [ -n "$V_USR_SUPPLIED" ]; then - echo -e "\n${S_NOTICE}You selected version using [-v]:" "${S_WARN}${V_USR_SUPPLIED}" - V_USR_INPUT="${V_USR_SUPPLIED}" - else - echo -ne "\n${S_QUESTION}Enter a new version number [${S_NORM}$V_SUGGEST${S_QUESTION}]: " - echo -ne "$S_WARN" - read V_USR_INPUT - - if [ "$V_USR_INPUT" = "" ]; then - V_USR_INPUT="${V_SUGGEST}" - fi - fi - - # echo -e "${S_NOTICE}Setting version to [${S_NORM}${V_USR_INPUT}${S_NOTICE}] ...." -} - -# Only tag if tag doesn't already exist -check-tag-exists() { - TAG_CHECK_EXISTS=`git tag -l v"$V_USR_INPUT"` - if [ -n "$TAG_CHECK_EXISTS" ]; then - echo -e "\n${I_STOP} ${S_ERROR}Error: A release with that tag version number already exists!\n" - exit 0 - fi -} - -# $1 : version -# $2 : release note -tag() { - if [ -z "$2" ]; then - # Default release note - git tag -a "v$1" -m "Tag version $1." - else - # Custom release note - git tag -a "v$1" -m "$2" - fi - echo -e "\n${I_OK} ${S_NOTICE}Added GIT tag" -} - -# Change `version:` value in JSON files, like packager.json, composer.json, etc -bump-json-files() { - if [ "$FLAG_JSON" != true ]; then return; fi - - JSON_PROCESSED=( ) # holds filenames after they've been changed - - for FILE in "${JSON_FILES[@]}"; do - if [ -f $FILE ]; then - # Get the existing version number - V_OLD=$( sed -n 's/.*"version": "\(.*\)",/\1/p' $FILE ) - - if [ "$V_OLD" = "$V_USR_INPUT" ]; then - echo -e "\n${S_WARN}File <${S_NORM}$FILE${S_WARN}> already contains version: ${S_NORM}$V_OLD" - else - # Write to output file - FILE_MSG=`sed -i .temp "s/\"version\": \"$V_OLD\"/\"version\": \"$V_USR_INPUT\"/g" $FILE 2>&1` - if [ "$?" -eq 0 ]; then - echo -e "\n${I_OK} ${S_NOTICE}Updated file: <${S_NOTICE}$FILE${S_LIGHT}> from ${S_NORM}$V_OLD -> $V_USR_INPUT" - rm -f ${FILE}.temp - # Add file change to commit message: - GIT_MSG+="${GIT_MSG}Updated $FILE, " - else - echo -e "\n${I_STOP} ${S_ERROR}Error\n$PUSH_MSG\n" - fi - fi - - JSON_PROCESSED+=($FILE) - else - echo -e "\n${S_WARN}File <${S_NORM}$FILE${S_WARN}> not found." - fi - done - # Stage files that were changed: - [ -n "${JSON_PROCESSED}" ] && git add "${JSON_PROCESSED[@]}" -} - -# Handle VERSION file -do-versionfile() { - [ -f VERSION ] && ACTION_MSG="Updated" || ACTION_MSG="Created" - - GIT_MSG+="${ACTION_MSG} VERSION, " - echo $V_USR_INPUT | tr -d "\n" > VERSION # Create file - echo -e "\n${I_OK} ${S_NOTICE}${ACTION_MSG} [${S_NORM}VERSION${S_NOTICE}] file" - - # Stage file for commit - git add VERSION -} - -# Dump git log history to CHANGELOG.md -do-changelog() { - - # Log latest commits to CHANGELOG.md: - # Get latest commits - LOG_MSG=`git log --pretty=format:"- %s" $([ -n "$V_PREV" ] && echo "v${V_PREV}...HEAD") 2>&1` - if [ ! "$?" -eq 0 ]; then - echo -e "\n${I_STOP} ${S_ERROR}Error getting commit history for logging to CHANGELOG.\n$LOG_MSG\n" - exit 1 - fi - - [ -f CHANGELOG.md ] && ACTION_MSG="Updated" || ACTION_MSG="Created" - # Add info to commit message for later: - GIT_MSG+="${ACTION_MSG} CHANGELOG.md, " - - # Add heading - echo "## $V_USR_INPUT ($NOW)" > tmpfile - - # Log the bumping commit: - # - The final commit is done after do-changelog(), so we need to create the log entry for it manually: - echo "- ${GIT_MSG}$(get-commit-msg)" >> tmpfile - # Add previous commits - [ -n "$LOG_MSG" ] && echo "$LOG_MSG" >> tmpfile - - echo -en "\n" >> tmpfile - - if [ -f CHANGELOG.md ]; then - # Append existing log - cat CHANGELOG.md >> tmpfile - else - echo -e "\n${S_WARN}A [${S_NORM}CHANGELOG.md${S_WARN}] file was not found." - fi - - mv tmpfile CHANGELOG.md - - # User prompts - echo -e "\n${I_OK} ${S_NOTICE}${ACTION_MSG} [${S_NORM}CHANGELOG.md${S_NOTICE}] file" - # Pause & allow user to open and edit the file: - echo -en "\n${S_QUESTION}Make adjustments to [${S_NORM}CHANGELOG.md${S_QUESTION}] if required now. Press to continue." - read - - # Stage log file, to commit later - git add CHANGELOG.md -} - -# -check-branch-exist() { - [ "$FLAG_NOBRANCH" = true ] && return - - BRANCH_MSG=`git rev-parse --verify "${REL_PREFIX}${V_USR_INPUT}" 2>&1` - if [ "$?" -eq 0 ]; then - echo -e "\n${I_STOP} ${S_ERROR}Error: Branch <${S_NORM}${REL_PREFIX}${V_USR_INPUT}${S_ERROR}> already exists!\n" - exit 1 - fi -} - -# -do-branch() { - [ "$FLAG_NOBRANCH" = true ] && return - - echo -e "\n${S_NOTICE}Creating new release branch..." - - BRANCH_MSG=`git branch "${REL_PREFIX}${V_USR_INPUT}" 2>&1` - if [ ! "$?" -eq 0 ]; then - echo -e "\n${I_STOP} ${S_ERROR}Error\n$BRANCH_MSG\n" - exit 1 - else - BRANCH_MSG=`git checkout "${REL_PREFIX}${V_USR_INPUT}" 2>&1` - echo -e "\n${I_OK} ${S_NOTICE}${BRANCH_MSG}" - fi - - # REL_PREFIX -} - -# Stage & commit all files modified by this script -do-commit() { - [ "$FLAG_NOCOMMIT" = true ] && return - - GIT_MSG+="$(get-commit-msg)" - echo -e "\n${S_NOTICE}Committing..." - COMMIT_MSG=`git commit -m "${GIT_MSG}" 2>&1` - if [ ! "$?" -eq 0 ]; then - echo -e "\n${I_STOP} ${S_ERROR}Error\n$COMMIT_MSG\n" - exit 1 - else - echo -e "\n${I_OK} ${S_NOTICE}$COMMIT_MSG" - fi -} - -# Pushes files + tags to remote repo. Changes are staged by earlier functions -do-push() { - [ "$FLAG_NOCOMMIT" = true ] && return - - if [ "$FLAG_PUSH" = true ]; then - CONFIRM="Y" - else - echo -ne "\n${S_QUESTION}Push tags to <${S_NORM}${PUSH_DEST}${S_QUESTION}>? [${S_NORM}N/y${S_QUESTION}]: " - read CONFIRM - fi - - case "$CONFIRM" in - [yY][eE][sS]|[yY] ) - echo -e "\n${S_NOTICE}Pushing files + tags to <${S_NORM}${PUSH_DEST}${S_NOTICE}>..." - PUSH_MSG=`git push "${PUSH_DEST}" v"$V_USR_INPUT" 2>&1` # Push new tag - if [ ! "$?" -eq 0 ]; then - echo -e "\n${I_STOP} ${S_WARN}Warning\n$PUSH_MSG" - # exit 1 - else - echo -e "\n${I_OK} ${S_NOTICE}$PUSH_MSG" - fi - ;; - esac -} - -#### Initiate Script ########################### - -check-commits-exist - -# Process and prepare -process-arguments "$@" -process-version - -check-branch-exist -check-tag-exists - -echo -e "\n${S_LIGHT}––––––" - -# Update files -bump-json-files -do-versionfile -# do-changelog -# do-branch -do-commit -# tag "${V_USR_INPUT}" "${REL_NOTE}" -do-push - -echo -e "\n${S_LIGHT}––––––" -echo -e "\n${I_OK} ${S_NOTICE}"Bumped $([ -n "${V_PREV}" ] && echo "${V_PREV} –>" || echo "to ") "$V_USR_INPUT" -echo -e "\n${GREEN}Done ${I_END}\n" \ No newline at end of file diff --git a/scripts/install-debian.sh b/scripts/install-debian.sh deleted file mode 100755 index f6159be..0000000 --- a/scripts/install-debian.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -e - -sudo apt-get update -sudo apt-get install -y \ - build-essential \ - qtbase5-dev \ - libqt5websockets5-dev \ - qtwebengine5-dev \ - qttools5-dev \ - qt5keychain-dev \ - openconnect \ - -./scripts/install.sh diff --git a/scripts/install-fedora.sh b/scripts/install-fedora.sh deleted file mode 100755 index 469f554..0000000 --- a/scripts/install-fedora.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -e - -sudo dnf install -y \ - qt5-qtbase-devel \ - qt5-qtwebengine-devel \ - qt5-qtwebsockets-devel \ - qtkeychain-qt5-devel \ - openconnect - -./scripts/install.sh diff --git a/scripts/install-opensuse.sh b/scripts/install-opensuse.sh deleted file mode 100755 index 0e8bd91..0000000 --- a/scripts/install-opensuse.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -e - -sudo zypper install -y \ - libqt5-qtbase-devel \ - libqt5-qtwebsockets-devel \ - libqt5-qtwebengine-devel \ - qtkeychain-qt5-devel \ - openconnect - -./scripts/install.sh diff --git a/scripts/install-ubuntu.sh b/scripts/install-ubuntu.sh deleted file mode 100755 index 4e5e0df..0000000 --- a/scripts/install-ubuntu.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -e - -sudo apt-get update -sudo apt-get install -y \ - build-essential \ - qtbase5-dev \ - libqt5websockets5-dev \ - qtwebengine5-dev \ - qttools5-dev \ - qt5keychain-dev \ - openconnect - -./scripts/install.sh diff --git a/scripts/install.sh b/scripts/install.sh deleted file mode 100755 index 0a6543d..0000000 --- a/scripts/install.sh +++ /dev/null @@ -1,11 +0,0 @@ -#!/bin/bash -e - -./scripts/build.sh - -sudo ./cmakew --install build - -sudo systemctl enable gpservice.service -sudo systemctl daemon-reload -sudo systemctl restart gpservice.service - -echo -e "\nSuccess. You can launch the GlobalProtect VPN client from the application dashboard.\n" \ No newline at end of file diff --git a/scripts/prepare-packaging.sh b/scripts/prepare-packaging.sh deleted file mode 100755 index 55d6a49..0000000 --- a/scripts/prepare-packaging.sh +++ /dev/null @@ -1,46 +0,0 @@ -#!/bin/bash -e - -OLD_VERSION=$(git tag --sort=-v:refname --list "v[0-9]*" | head -n 1 | cut -c 2-) -NEW_VERSION="$(cat VERSION)" -HISTORY_ENTRIES=$(git log --format=" * %s" v${OLD_VERSION}.. | cat -n | sort -uk2 | sort -n | cut -f2-) - -function update_debian_changelog() { - local OLD_CHANGELOG=$(cat debian/changelog) - - cat > debian/changelog <<-EOF - globalprotect-openconnect (${NEW_VERSION}-1) unstable; urgency=medium - - ${HISTORY_ENTRIES} - - -- Kevin Yue $(date -R) - - ${OLD_CHANGELOG} - EOF -} - -function update_rpm_changelog() { - local OLD_CHANGELOG=$(cat packaging/obs/globalprotect-openconnect.changes) - - cat > packaging/obs/globalprotect-openconnect.changes <<-EOF - ------------------------------------------------------------------- - $(LC_ALL=en.US date -u "+%a %b %e %T %Z %Y") - k3vinyue@gmail.com - ${NEW_VERSION} - - - Update to ${NEW_VERSION} - ${HISTORY_ENTRIES} - - ${OLD_CHANGELOG} - EOF -} - -function generate_pkgbuild() { - local commit_id="$(git rev-parse HEAD)" - local version="$(cat VERSION)" - sed -e "s/{COMMIT}/${commit_id}/" -e "s/{VERSION}/${version}/" packaging/aur/PKGBUILD.in > packaging/aur/PKGBUILD -} - -# Update rpm version -sed -i"" -re "s/(Version:\s+).+/\1${NEW_VERSION}/" packaging/obs/globalprotect-openconnect.spec - -update_rpm_changelog -update_debian_changelog -generate_pkgbuild diff --git a/scripts/release-archive-all.sh b/scripts/release-archive-all.sh deleted file mode 100755 index 396e6f7..0000000 --- a/scripts/release-archive-all.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -e - -./scripts/_archive-all.sh \ No newline at end of file diff --git a/scripts/release.sh b/scripts/release.sh deleted file mode 100755 index 59fd11e..0000000 --- a/scripts/release.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -e - -VERSION=$(cat VERSION) - -# Update packaging, e.g., version, changelog, etc. -./scripts/prepare-packaging.sh - -# Commit the changes -git commit -m "Release ${VERSION}" . -git tag v$VERSION -a -m "Release ${VERSION}" \ No newline at end of file diff --git a/scripts/snapshot-archive-all.sh b/scripts/snapshot-archive-all.sh deleted file mode 100755 index 71b192c..0000000 --- a/scripts/snapshot-archive-all.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash -e - -./scripts/snapshot-version.sh -./scripts/prepare-packaging.sh -./scripts/_archive-all.sh - -# Update the OBS packaging -mv ./artifacts/obs/globalprotect-openconnect.tar.gz ./artifacts/obs/globalprotect-openconnect-snapshot.tar.gz -mv ./artifacts/obs/globalprotect-openconnect.spec ./artifacts/obs/globalprotect-openconnect-snapshot.spec -mv ./artifacts/obs/globalprotect-openconnect.changes ./artifacts/obs/globalprotect-openconnect-snapshot.changes -mv ./artifacts/obs/globalprotect-openconnect-rpmlintrc ./artifacts/obs/globalprotect-openconnect-snapshot-rpmlintrc -sed -i"" -re "s/(Name:\s+).+/\1globalprotect-openconnect-snapshot/" \ - -re "s/(Conflicts:\s+).+/\1globalprotect-openconnect/" \ - ./artifacts/obs/globalprotect-openconnect-snapshot.spec diff --git a/scripts/snapshot-version.sh b/scripts/snapshot-version.sh deleted file mode 100755 index 99b60b8..0000000 --- a/scripts/snapshot-version.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/bash -e - -VERSION="v$(cat VERSION)" -git describe --tags --match "${VERSION}" | sed -re 's/^v([^-]+)-([^-]+)-(.+)/\1+\2snapshot.\3/' > VERSION \ No newline at end of file diff --git a/scripts/verify-debian-package.sh b/scripts/verify-debian-package.sh deleted file mode 100755 index 9369618..0000000 --- a/scripts/verify-debian-package.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/bash -e - -sudo apt-get update -sudo apt-get install -y \ - build-essential \ - qtbase5-dev \ - libqt5websockets5-dev \ - qtwebengine5-dev \ - qt5keychain-dev \ - cmake \ - qttools5-dev \ - debhelper - -mkdir -p build - -cp ./artifacts/*.tar.gz build/ && cd build -tar -xzf *.tar.gz && cd globalprotect-openconnect-*/ - -dpkg-buildpackage -us -uc diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml deleted file mode 100644 index 4025185..0000000 --- a/snap/snapcraft.yaml +++ /dev/null @@ -1,92 +0,0 @@ -name: globalprotect-openconnect -base: core18 -confinement: strict -compression: lzo - -license: GPL-3.0 - -adopt-info: application - -package-repositories: - - type: apt - ppa: dwmw2/openconnect - -layout: - /usr/local/sbin: - bind: $SNAP/usr/sbin - /usr/share/vpnc-scripts: - bind: $SNAP/usr/share/vpnc-scripts - /usr/share/locale: - bind: $SNAP/usr/share/locale - -slots: - gpservice-slot: - interface: dbus - bus: system - name: com.yuezk.qt.GPService - -plugs: - gpservice-plug: - interface: dbus - bus: system - name: com.yuezk.qt.GPService - -apps: - gpservice: - common-id: com.yuezk.qt.gpservice - daemon: simple - command: usr/bin/gpservice - command-chain: - - snap/command-chain/desktop-launch - environment: - LC_ALL: en_US.UTF-8 - LANG: en_US.UTF-8 - plugs: - - network - slots: - - gpservice-slot - - gpclient: - common-id: com.yuezk.qt.gpclient - command: usr/bin/gpclient - desktop: usr/share/applications/com.yuezk.qt.gpclient.desktop - extensions: - - kde-neon - plugs: - - desktop - - desktop-legacy - - wayland - - unity7 - - x11 - - network - - gpservice-plug - -parts: - application: - override-pull: | - snapcraftctl pull - - VERSION=$(cat VERSION) - GRADE="stable" - - if echo "$VERSION" | grep -q "snapshot" - then - GRADE="devel" - fi - - snapcraftctl set-version "$VERSION" - snapcraftctl set-grade "$GRADE" - parse-info: - - usr/share/metainfo/com.yuezk.qt.gpclient.metainfo.xml - plugin: cmake - source: . - build-packages: - - libglu1-mesa-dev - build-snaps: - - kde-frameworks-5-core18-sdk - stage-packages: - - openconnect - - libatm1 - configflags: - - -DCMAKE_INSTALL_PREFIX=/usr - - -DCMAKE_BUILD_TYPE=Release \ No newline at end of file diff --git a/version.h.in b/version.h.in deleted file mode 100644 index eaab018..0000000 --- a/version.h.in +++ /dev/null @@ -1 +0,0 @@ -#define VERSION "@version@"