diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e6bb0e..a0ee8e7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,5 @@ name: CI +# See https://help.github.com/en/actions/reference/events-that-trigger-workflows on: [push, pull_request] jobs: clippy_check: @@ -30,7 +31,7 @@ jobs: - name: Install node uses: actions/setup-node@v3 with: - node-version: 14 + node-version: 18 - name: Install node packages working-directory: ./web_frontend run: npm i @@ -40,11 +41,10 @@ jobs: - name: Build web frontend run: trunk build working-directory: ./web_frontend + - name: Build backend + run: cargo build + working-directory: . - - uses: actions-rs/clippy-check@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features rustfmt: name: Check style @@ -68,7 +68,7 @@ jobs: args: --all -- --check ci: - name: CI + name: Build runs-on: ${{ matrix.os }} strategy: matrix: @@ -81,18 +81,6 @@ jobs: os: ubuntu-latest rust: stable target: aarch64-unknown-linux-gnu - # - build: macos - # os: macos-latest - # rust: stable - # target: x86_64-apple-darwin - # - buid: macos - # os: macos-latest - # rust: stable - # target: aarch64-apple-darwin - - build: windows - os: windows-latest - rust: stable - target: x86_64-pc-windows-msvc steps: - name: Checkout uses: actions/checkout@v2 @@ -100,7 +88,7 @@ jobs: - name: Cache build artifacts uses: actions/cache@v3 with: - key: no_gui-${{ matrix.os }}-${{ matrix.target }}-artifacts + key: ${{ matrix.os }}-${{ matrix.target }}-artifacts path: | ./target ~/.cargo @@ -114,122 +102,33 @@ jobs: - name: Install rust target run: rustup target add ${{ matrix.target }} - - name: Install cross build dependencies if: matrix.target == 'aarch64-unknown-linux-gnu' run: sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross - + - name: Install node + uses: actions/setup-node@v3 + with: + node-version: 18 + - name: Install node packages + working-directory: ./web_frontend + run: npm i + - name: Install trunk + uses: actions-rs/cargo@v1 + with: + command: install + args: --locked --debug trunk + - name: Build web frontend + run: rustup target add wasm32-unknown-unknown && trunk build --release + working-directory: ./web_frontend - name: Build server uses: actions-rs/cargo@v1 with: command: build + working-directory: . args: --release --target ${{ matrix.target }} --bin privaxy --target-dir target - - uses: actions/upload-artifact@v3 if: matrix.os != 'windows-latest' with: - name: privaxy_nogui-${{ matrix.target }} + name: privaxy-${{ matrix.target }} path: | target/${{ matrix.target }}/release/privaxy - - - uses: actions/upload-artifact@v3 - if: matrix.os == 'windows-latest' - with: - name: privaxy_nogui-${{ matrix.target }} - path: | - target/${{ matrix.target }}/release/privaxy.exe - - ci_desktop: - name: CI Desktop - runs-on: ${{ matrix.os }} - strategy: - matrix: - include: - - build: linux - os: ubuntu-latest - rust: stable - # - build: macos - # os: macos-latest - # rust: stable - - build: windows - os: windows-latest - rust: stable - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Cache build artifacts - uses: actions/cache@v3 - with: - key: gui-${{ matrix.os }}-artifacts - path: | - ./target - ~/.cargo - - - name: Install rust - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.rust }} - profile: minimal - override: true - - - name: Install trunk - uses: actions-rs/cargo@v1 - with: - command: install - args: --locked --debug trunk - - - name: Install webassembly rust target - run: rustup target add wasm32-unknown-unknown - - # Required for tailwindcss - - name: Install node - uses: actions/setup-node@v3 - with: - node-version: 16 - - - if: startsWith(matrix.os, 'ubuntu') == true - name: Install gui library packages - run: sudo apt-get update && sudo apt-get install -y libwebkit2gtk-4.0-dev build-essential libayatana-appindicator3-dev librsvg2-dev libgtk-3-dev libsoup2.4-dev libjavascriptcoregtk-4.0-dev - - - if: startsWith(matrix.os, 'macos') == true - name: Install rust apple arm target - run: rustup target add aarch64-apple-darwin - - - if: startsWith(matrix.os, 'macos') == true - name: Install apple api key private key - run: | - mkdir -p ~/private_keys - echo "$API_KEY" >> ~/private_keys/AuthKey_"$API_KEY_ID".p8 - shell: bash - env: - API_KEY: ${{secrets.APPLE_API_KEY_CONTENTS}} - API_KEY_ID: ${{secrets.APPLE_API_KEY}} - - - name: Install node packages - working-directory: ./web_frontend - run: npm i - # It is required to first build the frontend as the server won't - # build if it has no access to frontend's dist directory. - - name: Build web frontend - run: trunk build --release - working-directory: ./web_frontend - - - name: Build desktop app - id: desktop_app - uses: tauri-apps/tauri-action@5a6072a9edbbf71718caee364b5b96731d7580fc #v0 does not handle universal builds - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }} - APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} - APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} - APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} - APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} - APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} - with: - tagName: privaxy-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version - releaseName: 'Privaxy v__VERSION__' - releaseBody: 'See the assets to download this version and install.' - releaseDraft: true - prerelease: false - args: ${{ startsWith(matrix.os, 'macos') == true && '--target universal-apple-darwin' || '' }} diff --git a/.gitignore b/.gitignore index 9c37d64..e6b67b9 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ web_frontend/dist filters/__pycache__ node_modules .vscode +**/target/* \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index c7d1d14..799d41a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -189,6 +189,23 @@ dependencies = [ "tokio", ] +[[package]] +name = "async-trait" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + [[package]] name = "autocfg" version = "1.3.0" @@ -598,6 +615,19 @@ version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +[[package]] +name = "filterlists-api" +version = "0.1.0" +dependencies = [ + "async-trait", + "readonly", + "reqwasm", + "reqwest 0.12.4", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "flate2" version = "1.0.30" @@ -925,6 +955,25 @@ dependencies = [ "tracing", ] +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.1.0", + "indexmap 2.2.6", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1019,6 +1068,29 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http 1.1.0", + "http-body 1.0.0", + "pin-project-lite", +] + [[package]] name = "httparse" version = "1.8.0" @@ -1047,9 +1119,9 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", - "http-body", + "http-body 0.4.6", "httparse", "httpdate", "itoa 1.0.11", @@ -1061,6 +1133,26 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.0", + "httparse", + "itoa 1.0.11", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + [[package]] name = "hyper-rustls" version = "0.23.2" @@ -1068,7 +1160,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1788965e61b367cd03a62950836d5cd41560c3577d90e40e0819373194d1661c" dependencies = [ "http 0.2.12", - "hyper", + "hyper 0.14.28", "log", "rustls 0.20.9", "rustls-native-certs", @@ -1084,7 +1176,7 @@ checksum = "ec3efd23720e2049821a693cbc7e65ea87c72f1c58ff2f9522ff332b1491e590" dependencies = [ "futures-util", "http 0.2.12", - "hyper", + "hyper 0.14.28", "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", @@ -1097,12 +1189,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.28", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper 1.3.1", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.0", + "hyper 1.3.1", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + [[package]] name = "iana-time-zone" version = "0.1.60" @@ -1666,7 +1794,7 @@ dependencies = [ "futures-util", "hex", "http 0.2.12", - "hyper", + "hyper 0.14.28", "hyper-rustls 0.23.2", "include_dir", "lazy_static", @@ -1676,7 +1804,7 @@ dependencies = [ "once_cell", "openssl", "regex", - "reqwest", + "reqwest 0.11.27", "rustls 0.20.9", "serde", "serde-tuple-vec-map", @@ -1838,6 +1966,17 @@ dependencies = [ "rand_core 0.5.1", ] +[[package]] +name = "readonly" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a25d631e41bfb5fdcde1d4e2215f62f7f0afa3ff11e26563765bd6ea1d229aeb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.66", +] + [[package]] name = "redox_syscall" version = "0.5.1" @@ -1908,12 +2047,12 @@ dependencies = [ "encoding_rs", "futures-core", "futures-util", - "h2", + "h2 0.3.26", "http 0.2.12", - "http-body", - "hyper", + "http-body 0.4.6", + "hyper 0.14.28", "hyper-rustls 0.24.2", - "hyper-tls", + "hyper-tls 0.5.0", "ipnet", "js-sys", "log", @@ -1923,7 +2062,7 @@ dependencies = [ "percent-encoding", "pin-project-lite", "rustls 0.21.12", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "serde", "serde_json", "serde_urlencoded", @@ -1940,7 +2079,49 @@ dependencies = [ "wasm-streams", "web-sys", "webpki-roots", - "winreg", + "winreg 0.50.0", +] + +[[package]] +name = "reqwest" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +dependencies = [ + "base64 0.22.1", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2 0.4.5", + "http 1.1.0", + "http-body 1.0.0", + "http-body-util", + "hyper 1.3.1", + "hyper-tls 0.6.0", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile 2.1.2", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg 0.52.0", ] [[package]] @@ -2060,7 +2241,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9aace74cb666635c918e9c12bc0d348266037aa8eb599b5cba565709a8dff00" dependencies = [ "openssl-probe", - "rustls-pemfile", + "rustls-pemfile 1.0.4", "schannel", "security-framework", ] @@ -2074,6 +2255,22 @@ dependencies = [ "base64 0.21.7", ] +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64 0.22.1", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + [[package]] name = "rustls-webpki" version = "0.101.7" @@ -2610,6 +2807,27 @@ dependencies = [ "serde", ] +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + [[package]] name = "tower-service" version = "0.3.2" @@ -2773,7 +2991,7 @@ dependencies = [ "futures-util", "headers", "http 0.2.12", - "hyper", + "hyper 0.14.28", "log", "mime", "mime_guess", @@ -2909,6 +3127,7 @@ dependencies = [ name = "web_frontend" version = "0.1.0" dependencies = [ + "filterlists-api", "futures", "gloo-timers", "gloo-utils", @@ -3130,6 +3349,16 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] + [[package]] name = "yew" version = "0.19.3" diff --git a/README.md b/README.md index bf5d12d..a7369e4 100644 --- a/README.md +++ b/README.md @@ -8,11 +8,19 @@

+**Forked from the [app version](https://github.com/Barre/privaxy/tree/v0.5.2)** + +This reverts it back to [v0.3.1](https://github.com/Barre/privaxy/tree/v0.3.1), but with +newer updates, an improved UI, and server-friendly configuration. To skip to the differences, +[see here](#differences) + +**TODO: more screencaps** +
-dashboard -requests -filters -exclusions + +filters +filterlists custom_fiters taskbar
@@ -49,14 +57,16 @@ Privaxy is also way more capable than DNS-based blockers as it is able to operat ### Using a pre-built binary -Pre-built binaries for major operating systems and platforms are provided at [github releases](https://github.com/Barre/privaxy/releases). +**TODO** + +## Differences + +- You can now specify the address to bind to in the toml config +- You can use environment variables to specify the folder to store the config +- **Adding of custom filters from external sources.** I've added an integration + to https://filterlists.com in the web gui to search and add filters from there. +- **NotValidBefore** patched properly, slight time differences *will not* produce + invalid cert messages. -### Local system configuration -1. Go to the GUI, click on "Save CA certificate". -2. Install the downloaded certificate locally. - - Macos: - - Linux: `cp privaxy_ca_cert.pem /usr/local/share/ca-certificates/` -3. Configure your local system to pass http traffic through privaxy which listens on localhost:8100. - - Macos: - - Ubuntu (gnome): +**TODO** more info, screenshots \ No newline at end of file diff --git a/debian/postinst b/debian/postinst new file mode 100644 index 0000000..4783b43 --- /dev/null +++ b/debian/postinst @@ -0,0 +1,13 @@ +#!/bin/bash +set -e + +if ! id -u privaxy >/dev/null 2>&1; then + useradd -r -d /etc/privaxy -s /usr/sbin/nologin privaxy + mkdir -p /etc/privaxy + chown privaxy:privaxy /etc/privaxy +fi + +mkdir -p /var/log/privaxy +chown privaxy:privaxy /var/log/privaxy + +exit 0 diff --git a/debian/postrm b/debian/postrm new file mode 100644 index 0000000..ecea914 --- /dev/null +++ b/debian/postrm @@ -0,0 +1,26 @@ +#!/bin/bash +set -e + +if [ -x /etc/init.d/privaxy ]; then + update-rc.d privaxy remove + rm -f /etc/init.d/privaxy +fi + +if systemctl is-enabled --quiet privaxy; then + systemctl disable privaxy +fi + +if systemctl status privaxy >/dev/null 2>&1; then + systemctl stop privaxy + systemctl disable privaxy + rm -f /lib/systemd/system/privaxy.service + systemctl daemon-reload + systemctl reset-failed +fi + +if id -u privaxy >/dev/null 2>&1; then + userdel privaxy + rm -rf /etc/privaxy +fi + +exit 0 diff --git a/debian/prerm b/debian/prerm new file mode 100644 index 0000000..21074c1 --- /dev/null +++ b/debian/prerm @@ -0,0 +1,12 @@ +#!/bin/bash +set -e + +if [ -x /etc/init.d/privaxy ]; then + /etc/init.d/privaxy stop || true +fi + +if systemctl is-active --quiet privaxy; then + systemctl stop privaxy || true +fi + +exit 0 diff --git a/debian/privaxy.service b/debian/privaxy.service new file mode 100644 index 0000000..16188e5 --- /dev/null +++ b/debian/privaxy.service @@ -0,0 +1,15 @@ +[Unit] +Description=Privaxy Service +After=network.target + +[Service] +ExecStart=/usr/local/bin/privaxy +Restart=always +User=privaxy +Group=privaxy +Environment=RUST_LOG=info +KillSignal=SIGTERM +ExecReload=/bin/kill -HUP $MAINPID + +[Install] +WantedBy=multi-user.target diff --git a/filterlists-api/Cargo.lock b/filterlists-api/Cargo.lock new file mode 100644 index 0000000..e0360a3 --- /dev/null +++ b/filterlists-api/Cargo.lock @@ -0,0 +1,1274 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "async-trait" +version = "0.1.80" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6fa2087f2753a7da8cc1c0dbfcf89579dd57458e36769de5ac750b4671737ca" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "backtrace" +version = "0.3.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17c6a35df3749d2e8bb1b7b21a976d82b15548788d2735b9d82f329268f71a11" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "bytes" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "514de17de45fdb8dc022b1a7975556c53c86f9f0aa5f534b98977b171857c2c9" + +[[package]] +name = "cc" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41c270e7540d725e65ac7f1b212ac8ce349719624d7bcff99f8e2e488e8cf03f" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" + +[[package]] +name = "encoding_rs" +version = "0.8.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "filterlists-api" +version = "0.1.0" +dependencies = [ + "async-trait", + "readonly", + "reqwasm", + "reqwest", + "serde", + "serde_json", + "thiserror", +] + +[[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.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +dependencies = [ + "futures-core", +] + +[[package]] +name = "futures-core" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" + +[[package]] +name = "futures-sink" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" + +[[package]] +name = "futures-task" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" + +[[package]] +name = "futures-util" +version = "0.3.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "pin-utils", +] + +[[package]] +name = "gimli" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" + +[[package]] +name = "gloo-net" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2899cb1a13be9020b010967adc6b2a8a343b6f1428b90238c9d53ca24decc6db" +dependencies = [ + "futures-channel", + "futures-core", + "futures-sink", + "gloo-utils", + "js-sys", + "pin-project", + "serde", + "serde_json", + "thiserror", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "gloo-utils" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037fcb07216cb3a30f7292bd0176b050b7b9a052ba830ef7d5d65f6dc64ba58e" +dependencies = [ + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "h2" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" + +[[package]] +name = "hyper" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe575dd17d0862a9a33781c8c4696a55c320909004a67a00fb286ba8b1bc496d" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b875924a60b96e5d7b9ae7b066540b1dd1cbd90d1828f54c92e02a283351c56" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "pin-project-lite", + "socket2", + "tokio", + "tower", + "tower-service", + "tracing", +] + +[[package]] +name = "idna" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "ipnet" +version = "2.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" + +[[package]] +name = "memchr" +version = "2.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87dfd01fe195c66b572b37921ad8803d010623c0aca821bea2302239d155cdae" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.48.0", +] + +[[package]] +name = "native-tls" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "object" +version = "0.35.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" + +[[package]] +name = "openssl" +version = "0.10.64" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95a0481286a310808298130d22dd1fef0fa571e05a8f44ec801801e84b216b1f" +dependencies = [ + "bitflags 2.5.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +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.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c597637d56fbc83893a35eb0dd04b2b8e7a50c91e64e9493e398b5df4fb45fa2" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" + +[[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.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" + +[[package]] +name = "proc-macro2" +version = "1.0.85" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22244ce15aa966053a896d1accb3a6e68469b97c7f33f284b99f0d576879fc23" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "readonly" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a25d631e41bfb5fdcde1d4e2215f62f7f0afa3ff11e26563765bd6ea1d229aeb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "reqwasm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05b89870d729c501fa7a68c43bf4d938bbb3a8c156d333d90faa0e8b3e3212fb" +dependencies = [ + "gloo-net", +] + +[[package]] +name = "reqwest" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "566cafdd92868e0939d3fb961bd0dc25fcfaaed179291093b3d43e6b3150ea10" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "winreg", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls-pemfile" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29993a25686778eb88d4189742cd713c9bce943bc54251a33509dc63cbacf73d" +dependencies = [ + "base64", + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "schannel" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +dependencies = [ + "windows-sys 0.52.0", +] + +[[package]] +name = "security-framework" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c627723fd09706bacdb5cf41499e95098555af3c3c29d014dc3c458ef6be11c0" +dependencies = [ + "bitflags 2.5.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "317936bbbd05227752583946b9e66d7ce3b489f84e11a94a510b4437fef407d7" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.203" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "455182ea6142b14f93f4bc5320a2b31c1f266b66a4a5c858b013302a5d8cbfc3" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" + +[[package]] +name = "socket2" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "syn" +version = "2.0.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c42f3f41a2de00b01c0aaad383c5a45241efc8b2d1eda5661812fda5f3cdcff5" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" + +[[package]] +name = "system-configuration" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +dependencies = [ + "bitflags 1.3.2", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys 0.52.0", +] + +[[package]] +name = "thiserror" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[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.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "tower" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "pin-project", + "pin-project-lite", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-layer" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" + +[[package]] +name = "tower-service" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" + +[[package]] +name = "tracing" +version = "0.1.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +dependencies = [ + "once_cell", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "unicode-bidi" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "unicode-normalization" +version = "0.1.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "url" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6302e3bb753d46e83516cae55ae196fc0c309407cf11ab35cc51a4c2a4633" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[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.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +dependencies = [ + "cfg-if", + "serde", + "serde_json", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" +dependencies = [ + "cfg-if", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" + +[[package]] +name = "web-sys" +version = "0.3.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.5", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +dependencies = [ + "windows_aarch64_gnullvm 0.52.5", + "windows_aarch64_msvc 0.52.5", + "windows_i686_gnu 0.52.5", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.5", + "windows_x86_64_gnu 0.52.5", + "windows_x86_64_gnullvm 0.52.5", + "windows_x86_64_msvc 0.52.5", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" + +[[package]] +name = "winreg" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" +dependencies = [ + "cfg-if", + "windows-sys 0.48.0", +] diff --git a/filterlists-api/Cargo.toml b/filterlists-api/Cargo.toml new file mode 100644 index 0000000..9297ef6 --- /dev/null +++ b/filterlists-api/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "filterlists-api" +version = "0.1.0" +edition = "2021" + +[lib] +name = "filterlists_api" +path = "src/lib.rs" + +[dependencies] +readonly = "0.2.12" +serde = { version = "1.0.203", features = ["derive"] } +serde_json = "1.0.117" +reqwest = { version = "0.12.4", features = ["json"], optional = true } +reqwasm = { version = "0.5.0", optional = true } +async-trait = "0.1.80" +thiserror = "1.0.61" + +[features] +default = ["reqwasm"] +reqwest = ["dep:reqwest"] +reqwasm = ["dep:reqwasm"] diff --git a/filterlists-api/src/dtypes.rs b/filterlists-api/src/dtypes.rs new file mode 100644 index 0000000..c93c8b4 --- /dev/null +++ b/filterlists-api/src/dtypes.rs @@ -0,0 +1,166 @@ +use readonly; +use serde::{Deserialize, Serialize}; +use thiserror::Error; + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +#[serde(rename_all = "camelCase")] +#[readonly::make] +pub struct Filter { + pub id: u64, + pub name: String, + #[serde(default = "default_description")] + pub description: String, + pub license_id: u64, + pub syntax_ids: Vec, + pub language_ids: Vec, + pub tag_ids: Vec, + #[serde(default)] + pub primary_view_url: Option, + pub maintainer_ids: Vec, +} + +fn default_description() -> String { + "No description".to_string() +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +#[serde(rename_all = "camelCase")] +#[readonly::make] +pub struct FilterViewURL { + pub segment_number: u32, + pub primariness: u32, + pub url: String, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +#[readonly::make] +pub struct FilterLanguage { + pub id: u64, + pub iso6391: String, + pub name: String, + pub filter_list_ids: Vec, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +#[serde(rename_all = "camelCase")] +#[readonly::make] +pub struct FilterDetails { + pub id: u64, + pub name: String, + pub description: String, + pub license_id: u64, + pub syntax_ids: Vec, + pub language_ids: Vec, + pub tag_ids: Vec, + pub primary_view_url: String, + pub maintainer_ids: Vec, + pub view_urls: Vec, + pub home_url: String, + pub onion_url: String, + pub policy_url: String, + pub submission_url: String, + pub issues_url: String, + pub forum_url: String, + pub chat_url: String, + pub email_address: String, + pub donate_url: String, + pub upstream_filter_list_ids: Vec, + pub include_in_filter_list_ids: Vec, + pub includes_filter_list_ids: Vec, + pub dependency_filter_list_ids: Vec, + pub dependent_filter_list_ids: Vec, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +#[readonly::make] +pub struct FilterSoftware { + pub id: u64, + pub name: String, + #[serde(default)] + pub home_url: Option, + #[serde(default)] + pub download_url: Option, + pub supports_abp_url_scheme: bool, + pub syntax_ids: Vec, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +#[readonly::make] +pub struct FilterListSyntax { + pub id: u64, + pub name: String, + pub url: String, + pub filter_list_ids: Vec, + pub software_ids: Vec, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +#[readonly::make] +pub struct FilterLicense { + pub id: u64, + pub name: String, + #[serde(default)] + pub url: Option, + #[serde(default)] + pub permit_modifications: Option, + #[serde(default)] + pub permit_distribution: Option, + #[serde(default)] + pub permit_commercial_use: Option, + pub filter_list_ids: Vec, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +#[readonly::make] +pub struct FilterTag { + pub id: u64, + pub name: String, + pub filter_list_ids: Vec, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Error)] +#[error("{r#type} error ({status}): {title} {trace_id}")] +#[serde(rename_all = "camelCase")] +#[readonly::make] +pub struct FilterListAPIError { + pub r#type: String, + pub title: String, + pub status: u16, + pub trace_id: String, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone)] +#[serde(rename_all = "camelCase")] +#[readonly::make] +pub struct FilterMaintainer { + pub id: u64, + pub name: String, + pub url: String, + pub filter_list_ids: Vec, +} + +pub enum FilterArgs { + U64(u64), + Filter(Filter), +} + +#[derive(Debug, Error)] +pub enum FilterListError { + #[error("API error: {0}")] + APIError(#[from] FilterListAPIError), + #[cfg(feature = "reqwest")] + #[error("Request error: {0}")] + RequestError(#[from] reqwest::Error), + #[cfg(feature = "reqwasm")] + #[error("Request error: {0}")] + RequestError(#[from] reqwasm::Error), + #[error("Serialization error: {0}")] + SerializationError(#[from] serde_json::Error), + #[error("Unknown error: {0}")] + GenericError(#[from] Box), +} diff --git a/filterlists-api/src/get.rs b/filterlists-api/src/get.rs new file mode 100644 index 0000000..713edb2 --- /dev/null +++ b/filterlists-api/src/get.rs @@ -0,0 +1,64 @@ +#[cfg(all(feature = "reqwasm", feature = "reqwest"))] +compile_error!("feature \"reqwasm\" and \"reqwest\" cannot be enabled at the same time"); + +use crate::{FilterListAPIError, FilterListError}; +macro_rules! if_wasm { + ($($item:item)*) => { + $( + #[cfg(feature = "reqwasm")] + $item + )* + }; +} + +macro_rules! if_reqwest { + ($($item:item)*) => { + $( + #[cfg(feature = "reqwest")] + $item + )* + }; +} +if_wasm! { + use reqwasm::http::Request; + use serde::de::DeserializeOwned; + + pub(crate) async fn _get(url: &str) -> Result + where + T: DeserializeOwned, + { + let response = Request::get(url).send().await.map_err(|e| FilterListError::RequestError(e))?; + let status = response.status(); + let body = response.text().await.map_err(|e| FilterListError::RequestError(e))?; + + if status == 200 { + let data: T = serde_json::from_str(&body)?; + Ok(data) + } else { + let error: FilterListAPIError = serde_json::from_str(&body)?; + Err(FilterListError::APIError(error)) + } + } +} + +if_reqwest! { + use reqwest; + use serde::de::DeserializeOwned; + + pub(crate) async fn _get(url: &str) -> Result + where + T: DeserializeOwned, + { + let response = reqwest::get(url).await?; + let status = response.status(); + let body = response.text().await?; + + if status.is_success() { + let data: T = serde_json::from_str(&body)?; + Ok(data) + } else { + let error: FilterListAPIError = serde_json::from_str(&body)?; + Err(FilterListError::APIError(error)) + } + } +} diff --git a/filterlists-api/src/lib.rs b/filterlists-api/src/lib.rs new file mode 100644 index 0000000..5f9c6df --- /dev/null +++ b/filterlists-api/src/lib.rs @@ -0,0 +1,45 @@ +#[cfg(all(feature = "reqwasm", feature = "reqwest"))] +compile_error!("feature \"reqwasm\" and \"reqwest\" cannot be enabled at the same time"); + +mod dtypes; +mod get; +pub use self::dtypes::*; +use crate::get::_get; + +pub const FILTERLISTS_API_URL: &str = "https://filterlists.com/api/directory/"; + +pub async fn get_filters() -> Result, FilterListError> { + _get::>(&format!("{FILTERLISTS_API_URL}/lists")).await +} + +pub async fn get_filter_information(filter: FilterArgs) -> Result { + let id = match filter { + FilterArgs::U64(id) => id, + FilterArgs::Filter(filter) => filter.id.clone(), + }; + _get::(&format!("{FILTERLISTS_API_URL}/lists/{id}")).await +} + +pub async fn get_syntaxes() -> Result, FilterListError> { + _get::>(&format!("{FILTERLISTS_API_URL}/syntaxes")).await +} + +pub async fn get_licenses() -> Result, FilterListError> { + _get::>(&format!("{FILTERLISTS_API_URL}/licenses")).await +} + +pub async fn get_software_list() -> Result, FilterListError> { + _get::>(&format!("{FILTERLISTS_API_URL}/software")).await +} + +pub async fn get_languages() -> Result, FilterListError> { + _get::>(&format!("{FILTERLISTS_API_URL}/languages")).await +} + +pub async fn get_tags() -> Result, FilterListError> { + _get::>(&format!("{FILTERLISTS_API_URL}/tags")).await +} + +pub async fn get_maintainers() -> Result, FilterListError> { + _get::>(&format!("{FILTERLISTS_API_URL}/maintainers")).await +} diff --git a/filterlists-api/src/main.rs b/filterlists-api/src/main.rs new file mode 100644 index 0000000..e7a11a9 --- /dev/null +++ b/filterlists-api/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + println!("Hello, world!"); +} diff --git a/filterlists-api/src/mod.rs b/filterlists-api/src/mod.rs new file mode 100644 index 0000000..0d6f2ae --- /dev/null +++ b/filterlists-api/src/mod.rs @@ -0,0 +1 @@ +pub(crate) mod dtypes; \ No newline at end of file diff --git a/images/filterlist.png b/images/filterlist.png new file mode 100755 index 0000000..430fe7d Binary files /dev/null and b/images/filterlist.png differ diff --git a/images/filters.png b/images/filters.png new file mode 100755 index 0000000..7037644 Binary files /dev/null and b/images/filters.png differ diff --git a/privaxy/Cargo.toml b/privaxy/Cargo.toml index 66f1ef3..cd7f74c 100644 --- a/privaxy/Cargo.toml +++ b/privaxy/Cargo.toml @@ -1,7 +1,21 @@ [package] name = "privaxy" -version = "0.3.1" +description = "Next generation tracker and advertisement blocker" +version = "0.6.0" edition = "2021" +authors = [ + "Pierre Barre ", + "Josh McDaniel <80354972+joshrmcdaniel@users.noreply.github.com>" +] + +[package.metadata.deb] +maintainer = "Josh McDaniel <80354972+joshrmcdaniel@users.noreply.github.com>" +license-file = ["../LICENSE.txt", "4"] +depends = "$auto" +assets = [ + ["../debian/privaxy.service", "/lib/systemd/system/privaxy.service", "644"], + ["target/release/privaxy", "/usr/local/bin/", "755"], +] [[bin]] name = "privaxy" diff --git a/privaxy/src/resources/vendor/ublock/web_accessible_resources/noop.css b/privaxy/src/resources/vendor/ublock/web_accessible_resources/noop.css new file mode 100644 index 0000000..c84ecef --- /dev/null +++ b/privaxy/src/resources/vendor/ublock/web_accessible_resources/noop.css @@ -0,0 +1 @@ +/* */ \ No newline at end of file diff --git a/privaxy/src/server/cert.rs b/privaxy/src/server/cert.rs index 1a116eb..45b942e 100644 --- a/privaxy/src/server/cert.rs +++ b/privaxy/src/server/cert.rs @@ -14,10 +14,10 @@ use openssl::{ }, }; use rustls::{Certificate, PrivateKey, ServerConfig}; +use std::time::{SystemTime, UNIX_EPOCH}; use std::{str::FromStr, sync::Arc}; use tokio::sync::Mutex; use uluru::LRUCache; -use std::time::{SystemTime, UNIX_EPOCH}; const MAX_CACHED_CERTIFICATES: usize = 1_000; @@ -111,7 +111,9 @@ impl SignedWithCaCert { let not_before = { let current_time = SystemTime::now(); - let since_epoch = current_time.duration_since(UNIX_EPOCH).expect("Time went backwards"); + let since_epoch = current_time + .duration_since(UNIX_EPOCH) + .expect("Time went backwards"); // patch NotValidBefore Asn1Time::from_unix(since_epoch.as_secs() as i64 - 60).unwrap() }; diff --git a/privaxy/src/server/configuration.rs b/privaxy/src/server/configuration.rs index 26fa70b..096f3d7 100644 --- a/privaxy/src/server/configuration.rs +++ b/privaxy/src/server/configuration.rs @@ -3,20 +3,20 @@ use crate::{ }; use dirs::home_dir; use futures::future::{try_join_all, AbortHandle, Abortable}; +use hex; use openssl::{ pkey::{PKey, Private}, x509::X509, }; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr}; +use sha2::{Digest, Sha256}; use std::env; use std::path::{Path, PathBuf}; use std::{collections::BTreeSet, time::Duration}; use thiserror::Error; use tokio::sync::{self, mpsc::Sender}; use tokio::{fs, sync::mpsc::Receiver}; -use sha2::{Digest, Sha256}; -use hex; use url::Url; const CONFIGURATION_DIRECTORY_NAME: &str = ".privaxy"; @@ -59,7 +59,7 @@ pub struct DefaultFilter { group: String, title: String, #[serde_as(as = "DisplayFromStr")] - url: Url + url: Url, } #[serde_as] @@ -70,7 +70,7 @@ pub struct Filter { pub group: FilterGroup, pub file_name: String, #[serde_as(as = "DisplayFromStr")] - pub url: Url + pub url: Url, } #[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] @@ -97,7 +97,12 @@ impl DefaultFilters { DefaultFilters(filters) } - fn parse_filter(url: &'static str, title: &'static str, group: FilterGroup, enabled_by_default: bool) -> Option { + fn parse_filter( + url: &'static str, + title: &'static str, + group: FilterGroup, + enabled_by_default: bool, + ) -> Option { match Url::parse(url) { Ok(parsed_url) => { let file_name = calculate_sha256_hex(url); @@ -108,7 +113,7 @@ impl DefaultFilters { title: title.to_string(), url: parsed_url, }) - }, + } Err(e) => { log::warn!("Failed to parse URL {}: {}", url, e); None @@ -130,11 +135,29 @@ impl DefaultFilters { fn get_ads_filters() -> Vec { vec![ - ("https://filters.adtidy.org/extension/ublock/filters/2_without_easylist.txt", "AdGuard Base", FilterGroup::Ads, false), - ("https://filters.adtidy.org/extension/ublock/filters/11.txt", "AdGuard Mobile Ads", FilterGroup::Ads, false), - ("https://easylist.to/easylist/easylist.txt", "EasyList", FilterGroup::Ads, true), - ].into_iter() - .filter_map(|(url, title, group, enabled_by_default)| Self::parse_filter(url, title, group, enabled_by_default)) + ( + "https://filters.adtidy.org/extension/ublock/filters/2_without_easylist.txt", + "AdGuard Base", + FilterGroup::Ads, + false, + ), + ( + "https://filters.adtidy.org/extension/ublock/filters/11.txt", + "AdGuard Mobile Ads", + FilterGroup::Ads, + false, + ), + ( + "https://easylist.to/easylist/easylist.txt", + "EasyList", + FilterGroup::Ads, + true, + ), + ] + .into_iter() + .filter_map(|(url, title, group, enabled_by_default)| { + Self::parse_filter(url, title, group, enabled_by_default) + }) .collect() } @@ -151,10 +174,23 @@ impl DefaultFilters { fn get_malware_filters() -> Vec { vec![ - ("https://curben.gitlab.io/malware-filter/phishing-filter.txt", "Phishing URL Blocklist", FilterGroup::Malware, false), - ("https://curben.gitlab.io/malware-filter/pup-filter.txt", "PUP Domains Blocklist", FilterGroup::Malware, false), - ].into_iter() - .filter_map(|(url, title, group, enabled_by_default)| Self::parse_filter(url, title, group, enabled_by_default)) + ( + "https://curben.gitlab.io/malware-filter/phishing-filter.txt", + "Phishing URL Blocklist", + FilterGroup::Malware, + false, + ), + ( + "https://curben.gitlab.io/malware-filter/pup-filter.txt", + "PUP Domains Blocklist", + FilterGroup::Malware, + false, + ), + ] + .into_iter() + .filter_map(|(url, title, group, enabled_by_default)| { + Self::parse_filter(url, title, group, enabled_by_default) + }) .collect() } @@ -213,14 +249,12 @@ impl DefaultFilters { } } - -pub (crate) fn calculate_sha256_hex(input: &str) -> String { +pub(crate) fn calculate_sha256_hex(input: &str) -> String { let mut hasher = Sha256::new(); hasher.update(input); hex::encode(hasher.finalize()) } - impl Filter { async fn update(&mut self, http_client: &reqwest::Client) -> ConfigurationResult { log::debug!("Updating filter: {}", self.title); @@ -236,7 +270,10 @@ impl Filter { Ok(filter) } - pub async fn get_contents(&mut self, http_client: &reqwest::Client) -> ConfigurationResult { + pub async fn get_contents( + &mut self, + http_client: &reqwest::Client, + ) -> ConfigurationResult { let filter_path = get_filter_directory().join(&self.file_name); match fs::read(filter_path).await { Err(err) => { @@ -266,7 +303,7 @@ impl From for Filter { _ => unreachable!(), }, file_name: default_filter.file_name, - url: default_filter.url + url: default_filter.url, } } } @@ -307,7 +344,7 @@ pub enum ConfigurationError { } impl Configuration { - pub async fn read_from_home(http_client: reqwest::Client) -> ConfigurationResult { + pub async fn read_from_home() -> ConfigurationResult { let configuration_directory = get_config_directory(); let configuration_file_path = configuration_directory.join(CONFIGURATION_FILE_NAME); @@ -317,7 +354,7 @@ impl Configuration { fs::create_dir(&configuration_directory).await?; - let configuration = Self::new_default(http_client).await?; + let configuration = Self::new_default().await?; configuration.save().await?; return Ok(configuration); @@ -332,7 +369,7 @@ impl Configuration { log::debug!("Configuration file not found, creating one"); if err.kind() == std::io::ErrorKind::NotFound { - let configuration = Self::new_default(http_client).await?; + let configuration = Self::new_default().await?; configuration.save().await?; Ok(configuration) @@ -418,7 +455,10 @@ impl Configuration { self.filters.iter_mut().filter(|f| f.enabled) } - pub async fn update_filters(&mut self, http_client: reqwest::Client) -> ConfigurationResult<()> { + pub async fn update_filters( + &mut self, + http_client: reqwest::Client, + ) -> ConfigurationResult<()> { log::debug!("Updating filters"); let futures = self.filters.iter_mut().filter_map(|filter| { @@ -434,16 +474,22 @@ impl Configuration { Ok(()) } - pub async fn add_filter(&mut self, filter: &mut Filter, http_client: &reqwest::Client) -> ConfigurationResult<()> { + pub async fn add_filter( + &mut self, + filter: &mut Filter, + http_client: &reqwest::Client, + ) -> ConfigurationResult<()> { match filter.update(http_client).await { - Ok(_) => { + Ok(_) => { self.filters.push(filter.clone()); Ok(()) - }, + } Err(err) => { log::error!("Failed to add filter: {err}"); filter.enabled = false; - Err(ConfigurationError::FilterError("Unable to add filter".to_string())) + Err(ConfigurationError::FilterError( + "Unable to add filter".to_string(), + )) } } } @@ -488,8 +534,7 @@ impl Configuration { } } - async fn new_default(http_client: reqwest::Client) -> ConfigurationResult { - + async fn new_default() -> ConfigurationResult { let (x509, private_key) = make_ca_certificate(); let x509_pem = std::str::from_utf8(&x509.to_pem().unwrap()) @@ -502,7 +547,8 @@ impl Configuration { let default_filters = DefaultFilters::new(); Ok(Configuration { - filters: default_filters.0 + filters: default_filters + .0 .into_iter() .map(|filter| filter.into()) .collect(), @@ -576,19 +622,26 @@ async fn get_filter( Ok(content) => content, Err(err) => { log::error!("Failed to read filter content: {err}"); - return Err(ConfigurationError::FilterError(format!("Failed to read filter content: {}", err))) + return Err(ConfigurationError::FilterError(format!( + "Failed to read filter content: {}", + err + ))); } } } else { log::error!("Failed to fetch filter content: {}", response.status()); - return Err(ConfigurationError::FilterError(format!("Failed to fetch filter content: {}", response.status()))); + return Err(ConfigurationError::FilterError(format!( + "Failed to fetch filter content: {}", + response.status() + ))); } - }, + } Err(err) => { log::error!("Failed to fetch filter URL: {err}"); return Err(ConfigurationError::FilterError(format!( "Failed to fetch filter URL: {}", - err))); + err + ))); } }; Ok(response) diff --git a/privaxy/src/server/lib.rs b/privaxy/src/server/lib.rs index f4d6a40..413f9f1 100644 --- a/privaxy/src/server/lib.rs +++ b/privaxy/src/server/lib.rs @@ -61,7 +61,7 @@ pub async fn start_privaxy() -> PrivaxyServer { .build() .unwrap(); - let configuration = match configuration::Configuration::read_from_home(client.clone()).await { + let configuration = match configuration::Configuration::read_from_home().await { Ok(configuration) => configuration, Err(err) => { println!( diff --git a/privaxy/src/server/proxy/serve.rs b/privaxy/src/server/proxy/serve.rs index 4d4f50c..8e4f5a1 100644 --- a/privaxy/src/server/proxy/serve.rs +++ b/privaxy/src/server/proxy/serve.rs @@ -94,7 +94,6 @@ pub(crate) async fn serve( let mut request_headers = req.headers().clone(); request_headers.remove(http::header::CONNECTION); request_headers.remove(http::header::HOST); - let mut response = match client .request(req.method().clone(), req.uri().to_string()) .headers(request_headers) @@ -103,7 +102,10 @@ pub(crate) async fn serve( .await { Ok(response) => response, - Err(err) => return Ok(get_informative_error_response(&err.to_string())), + Err(err) => { + log::error!("Failed to send request: {}", err.to_string()); + return Ok(get_informative_error_response(&err.to_string())); + } }; statistics.increment_proxied_requests(); diff --git a/privaxy/src/server/web_gui/custom_filters.rs b/privaxy/src/server/web_gui/custom_filters.rs index 55e0552..9fe5c97 100644 --- a/privaxy/src/server/web_gui/custom_filters.rs +++ b/privaxy/src/server/web_gui/custom_filters.rs @@ -4,10 +4,8 @@ use std::{convert::Infallible, sync::Arc}; use tokio::sync::mpsc::Sender; use warp::http::StatusCode; -pub async fn get_custom_filters( - http_client: reqwest::Client, -) -> Result, Infallible> { - let configuration = match Configuration::read_from_home(http_client).await { +pub async fn get_custom_filters() -> Result, Infallible> { + let configuration = match Configuration::read_from_home().await { Ok(configuration) => configuration, Err(err) => { log::error!("Failed to get customs filters: {err}"); @@ -22,13 +20,12 @@ pub async fn get_custom_filters( pub async fn put_custom_filters( custom_filters: String, - http_client: reqwest::Client, configuration_updater_sender: Sender, configuration_save_lock: Arc>, ) -> Result, Infallible> { let _guard = configuration_save_lock.lock().await; - let mut configuration = match Configuration::read_from_home(http_client).await { + let mut configuration = match Configuration::read_from_home().await { Ok(configuration) => configuration, Err(err) => { log::error!("Failed to put customs filters: {err}"); diff --git a/privaxy/src/server/web_gui/events.rs b/privaxy/src/server/web_gui/events.rs index 9899d73..377f069 100644 --- a/privaxy/src/server/web_gui/events.rs +++ b/privaxy/src/server/web_gui/events.rs @@ -5,7 +5,7 @@ use tokio::sync::broadcast; use warp::ws::{Message, WebSocket}; #[derive(Debug, Serialize, Clone)] -pub(crate) struct Event { +pub struct Event { pub now: DateTime, pub method: String, pub url: String, diff --git a/privaxy/src/server/web_gui/exclusions.rs b/privaxy/src/server/web_gui/exclusions.rs index be89384..8933ea5 100644 --- a/privaxy/src/server/web_gui/exclusions.rs +++ b/privaxy/src/server/web_gui/exclusions.rs @@ -4,10 +4,8 @@ use std::{convert::Infallible, sync::Arc}; use tokio::sync::mpsc::Sender; use warp::http::StatusCode; -pub async fn get_exclusions( - http_client: reqwest::Client, -) -> Result, Infallible> { - let configuration = match Configuration::read_from_home(http_client).await { +pub async fn get_exclusions() -> Result, Infallible> { + let configuration = match Configuration::read_from_home().await { Ok(configuration) => configuration, Err(err) => { log::error!("Failed to get exclusions: {err}"); @@ -22,14 +20,13 @@ pub async fn get_exclusions( pub async fn put_exclusions( exclusions: String, - http_client: reqwest::Client, configuration_updater_sender: Sender, configuration_save_lock: Arc>, local_exclusions_store: LocalExclusionStore, ) -> Result, Infallible> { let _guard = configuration_save_lock.lock().await; - let mut configuration = match Configuration::read_from_home(http_client).await { + let mut configuration = match Configuration::read_from_home().await { Ok(configuration) => configuration, Err(err) => { log::error!("Failed to put exclusions: {err}"); diff --git a/privaxy/src/server/web_gui/filters.rs b/privaxy/src/server/web_gui/filters.rs index 2d7fa72..dd5673e 100644 --- a/privaxy/src/server/web_gui/filters.rs +++ b/privaxy/src/server/web_gui/filters.rs @@ -1,12 +1,13 @@ use super::get_error_response; -use crate::configuration::{Configuration, Filter, FilterGroup, calculate_sha256_hex}; +use crate::configuration::{calculate_sha256_hex, Configuration, Filter, FilterGroup}; +use crate::web_gui::ApiError; use serde::Deserialize; use serde_with::{serde_as, DisplayFromStr}; use std::{convert::Infallible, sync::Arc}; use tokio::sync::mpsc::Sender; -use warp::http::Response; use url::Url; +use warp::http::Response; #[derive(Debug, Deserialize)] pub struct FilterStatusChangeRequest { @@ -16,7 +17,7 @@ pub struct FilterStatusChangeRequest { #[serde_as] #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] -pub struct AddFilterRequest { +pub struct FilterRequest { pub enabled: bool, pub title: String, pub group: FilterGroup, @@ -26,13 +27,12 @@ pub struct AddFilterRequest { pub async fn change_filter_status( filter_status_change_request: Vec, - http_client: reqwest::Client, configuration_updater_sender: Sender, configuration_save_lock: Arc>, ) -> Result { let _guard = configuration_save_lock.lock().await; - let mut configuration = match Configuration::read_from_home(http_client).await { + let mut configuration = match Configuration::read_from_home().await { Ok(configuration) => configuration, Err(err) => { log::error!("Failed to change filter status: {err}"); @@ -61,10 +61,8 @@ pub async fn change_filter_status( .unwrap()) } -pub async fn get_filters_configuration( - http_client: reqwest::Client, -) -> Result { - let configuration = match Configuration::read_from_home(http_client).await { +pub async fn get_filters_configuration() -> Result { + let configuration = match Configuration::read_from_home().await { Ok(configuration) => configuration, Err(err) => { log::error!("Failed to get filters configuration: {err}"); @@ -81,7 +79,7 @@ pub async fn get_filters_configuration( } pub async fn add_filter( - add_filter_request: AddFilterRequest, + filter_request: FilterRequest, http_client: reqwest::Client, configuration_updater_sender: Sender, configuration_save_lock: Arc>, @@ -89,7 +87,7 @@ pub async fn add_filter( let _guard = configuration_save_lock.lock().await; // Read the current configuration - let mut configuration = match Configuration::read_from_home(http_client.clone()).await { + let mut configuration = match Configuration::read_from_home().await { Ok(configuration) => configuration, Err(err) => { log::error!("Failed to read configuration: {err}"); @@ -98,54 +96,103 @@ pub async fn add_filter( }; // Clone the URL to avoid moving the original value - let filter_url = add_filter_request.url.clone(); - if configuration.filters.iter().any(|filter| filter.url == add_filter_request.url) { - log::warn!("Filter with URL {} already exists", add_filter_request.url); + let filter_url = filter_request.url.clone(); + if configuration + .filters + .iter() + .any(|filter| filter.url == filter_request.url) + { + log::warn!("Filter with URL {} already exists", filter_request.url); return Ok(Response::builder() .status(http::StatusCode::CONFLICT) - .body("Filter already exists".to_string()) + .body( + serde_json::to_string(&ApiError { + error: format!("Filter with URL {} already exists", filter_request.url), + }) + .unwrap(), + ) .unwrap()); } // Add the new filter to the configuration let mut new_filter = Filter { - enabled: add_filter_request.enabled, + enabled: filter_request.enabled, url: filter_url, - title: add_filter_request.title.clone(), - group: add_filter_request.group, - file_name: calculate_sha256_hex(&add_filter_request.url.to_string()), // Generate a unique file name + title: filter_request.title.clone(), + group: filter_request.group, + file_name: calculate_sha256_hex(&filter_request.url.to_string()), // Generate a unique file name }; - - match configuration.add_filter(&mut new_filter, &http_client).await { - Ok(_) => {}, + + match configuration + .add_filter(&mut new_filter, &http_client) + .await + { + Ok(_) => {} Err(err) => { log::error!("Failed to add filter: {err}"); return Ok(get_error_response(err)); } - } - configuration_updater_sender.send(configuration.clone()).await.unwrap(); + configuration_updater_sender + .send(configuration.clone()) + .await + .unwrap(); // Save the updated configuration if let Err(err) = configuration.save().await { log::error!("Failed to save configuration: {err}"); - return Ok(Response::builder() - .status(http::StatusCode::INTERNAL_SERVER_ERROR) - .body(format!("Failed to save configuration: {err}")) - .unwrap()); + return Ok(get_error_response(err)); } // Send the updated configuration to the updater - if let Err(err) = configuration_updater_sender.send(configuration.clone()).await { + if let Err(err) = configuration_updater_sender + .send(configuration.clone()) + .await + { log::error!("Failed to send updated configuration: {err}"); - return Ok(Response::builder() - .status(http::StatusCode::INTERNAL_SERVER_ERROR) - .body(format!("Failed to send updated configuration: {err}")) - .unwrap()); + return Ok(get_error_response(err)); } Ok(Response::builder() .status(http::StatusCode::CREATED) - .body("Filter added successfully".to_string()) + .body("".to_string()) + .unwrap()) +} + +pub async fn delete_filter( + filter_request: FilterRequest, + configuration_updater_sender: Sender, + configuration_save_lock: Arc>, +) -> Result { + let _guard = configuration_save_lock.lock().await; + + let configuration = match Configuration::read_from_home().await { + Ok(configuration) => configuration, + Err(err) => { + log::error!("Failed to read configuration: {err}"); + return Ok(get_error_response(err)); + } + }; + + let mut new_configuration = configuration.clone(); + new_configuration + .filters + .retain(|filter| filter.url != filter_request.url); + + if let Err(err) = new_configuration.save().await { + log::error!("Failed to save configuration: {err}"); + return Ok(get_error_response(err)); + } + + if let Err(err) = configuration_updater_sender + .send(new_configuration.clone()) + .await + { + log::error!("Failed to send updated configuration: {err}"); + return Ok(get_error_response(err)); + } + Ok(Response::builder() + .status(http::StatusCode::NO_CONTENT) + .body("".to_string()) .unwrap()) } diff --git a/privaxy/src/server/web_gui/mod.rs b/privaxy/src/server/web_gui/mod.rs index 17d4679..e9e8075 100644 --- a/privaxy/src/server/web_gui/mod.rs +++ b/privaxy/src/server/web_gui/mod.rs @@ -6,9 +6,10 @@ use serde::Serialize; use std::net::SocketAddr; use std::sync::Arc; use tokio::sync::{broadcast, mpsc::Sender}; +use warp::filters::BoxedFilter; use warp::http::Response; use warp::path::Tail; -use warp::Filter; +use warp::{http, Filter, Reply}; pub(crate) mod blocking_enabled; pub(crate) mod custom_filters; @@ -60,6 +61,127 @@ pub(crate) fn start_web_gui_static_files_server(bind: SocketAddr, api_addr: Sock }); } +fn create_routes( + events_sender: broadcast::Sender, + statistics: Statistics, + blocking_disabled_store: BlockingDisabledStore, + configuration_updater_sender: Sender, + ca_certificate_pem: String, + configuration_save_lock: Arc>, + local_exclusions_store: LocalExclusionStore, + http_client: reqwest::Client, +) -> BoxedFilter<(impl Reply,)> { + let events_route = warp::path("events") + .and(warp::ws()) + .map(move |ws: warp::ws::Ws| { + let events_sender = events_sender.clone(); + ws.on_upgrade(move |websocket| events::events(websocket, events_sender)) + }); + + let statistics_route = warp::path("statistics") + .and(warp::ws()) + .map(move |ws: warp::ws::Ws| { + let statistics = statistics.clone(); + ws.on_upgrade(move |websocket| statistics::statistics(websocket, statistics)) + }); + + let filters_route = warp::path("filters").and( + warp::get() + .and_then(filters::get_filters_configuration) + .or(warp::put() + .and(warp::body::json()) + .and(with_configuration_updater_sender( + configuration_updater_sender.clone(), + )) + .and(with_configuration_save_lock( + configuration_save_lock.clone(), + )) + .and_then(filters::change_filter_status)) + .or(warp::post() + .and(warp::body::json()) + .and(with_http_client(http_client.clone())) + .and(with_configuration_updater_sender( + configuration_updater_sender.clone(), + )) + .and(with_configuration_save_lock( + configuration_save_lock.clone(), + )) + .and_then(filters::add_filter)) + .or(warp::delete() + .and(warp::body::json()) + .and(with_configuration_updater_sender( + configuration_updater_sender.clone(), + )) + .and(with_configuration_save_lock( + configuration_save_lock.clone(), + )) + .and_then(filters::delete_filter)), + ); + + let custom_filters_route = warp::path("custom-filters").and( + warp::get() + .and_then(custom_filters::get_custom_filters) + .or(warp::put() + .and(warp::body::json()) + .and(with_configuration_updater_sender( + configuration_updater_sender.clone(), + )) + .and(with_configuration_save_lock( + configuration_save_lock.clone(), + )) + .and_then(custom_filters::put_custom_filters)), + ); + + let exclusions_route = warp::path("exclusions").and( + warp::get() + .and_then(exclusions::get_exclusions) + .or(warp::put() + .and(warp::body::json()) + .and(with_configuration_updater_sender( + configuration_updater_sender.clone(), + )) + .and(with_configuration_save_lock( + configuration_save_lock.clone(), + )) + .and(with_local_exclusions_store(local_exclusions_store)) + .and_then(exclusions::put_exclusions)), + ); + + let blocking_enabled_route = warp::path("blocking-enabled").and( + warp::get() + .and(with_blocking_disabled_store( + blocking_disabled_store.clone(), + )) + .and_then(blocking_enabled::get_blocking_enabled) + .or(warp::put() + .and(warp::body::json()) + .and(with_blocking_disabled_store(blocking_disabled_store)) + .and_then(blocking_enabled::put_blocking_enabled)), + ); + + let ca_certificate_route = + warp::path("privaxy_ca_certificate.pem").and(warp::get().map(move || { + Response::builder() + .header( + http::header::CONTENT_DISPOSITION, + "attachment; filename=privaxy_ca_certificate.pem;", + ) + .body(ca_certificate_pem.clone()) + })); + + let options_route = warp::options().map(|| ""); + + events_route + .or(statistics_route) + .or(filters_route) + .or(custom_filters_route) + .or(exclusions_route) + .or(blocking_enabled_route) + .or(ca_certificate_route) + .or(options_route) + .boxed() +} + #[allow(clippy::too_many_arguments)] pub(crate) fn start_web_gui_server( events_sender: broadcast::Sender, @@ -75,125 +197,26 @@ pub(crate) fn start_web_gui_server( let cors = warp::cors() .allow_any_origin() - .allow_methods(vec!["GET", "PUT", "POST"]) + .allow_methods(vec!["GET", "PUT", "POST", "DELETE"]) .allow_headers(vec![ http::header::CONTENT_TYPE, http::header::CONTENT_LENGTH, http::header::DATE, ]); - tokio::spawn(async move { - let routes = warp::get() - .and( - warp::path("events") - .and(warp::ws()) - .map(move |ws: warp::ws::Ws| { - let events_sender = events_sender.clone(); - - ws.on_upgrade(move |websocket| events::events(websocket, events_sender)) - }) - .or(warp::path("statistics") - .and(warp::ws()) - .map(move |ws: warp::ws::Ws| { - let statistics = statistics.clone(); - - ws.on_upgrade(move |websocket| { - statistics::statistics(websocket, statistics) - }) - })), - ) - .or(warp::path("filters") - .and( - warp::get() - .and(with_http_client(http_client.clone())) - .and_then(filters::get_filters_configuration), - ) - .or(warp::put() - .and(warp::path("filters")) - .and(warp::body::json()) - .and(with_http_client(http_client.clone())) - .and(with_configuration_updater_sender( - configuration_updater_sender.clone(), - )) - .and(with_configuration_save_lock( - configuration_save_lock.clone(), - )) - .and_then(filters::change_filter_status)) - .or(warp::post() - .and(warp::path("filters")) - .and(warp::body::json()) - .and(with_http_client(http_client.clone())) - .and(with_configuration_updater_sender( - configuration_updater_sender.clone(), - )) - .and(with_configuration_save_lock( - configuration_save_lock.clone(), - )) - .and_then(filters::add_filter)), - ) - .or(warp::path("custom-filters") - .and( - warp::get() - .and(with_http_client(http_client.clone())) - .and_then(custom_filters::get_custom_filters), - ) - .or(warp::put().and( - warp::path("custom-filters") - .and(warp::body::json()) - .and(with_http_client(http_client.clone())) - .and(with_configuration_updater_sender( - configuration_updater_sender.clone(), - )) - .and(with_configuration_save_lock( - configuration_save_lock.clone(), - )) - .and_then(custom_filters::put_custom_filters), - ))) - .or(warp::path("exclusions") - .and( - warp::get() - .and(with_http_client(http_client.clone())) - .and_then(exclusions::get_exclusions), - ) - .or(warp::put().and( - warp::path("exclusions") - .and(warp::body::json()) - .and(with_http_client(http_client.clone())) - .and(with_configuration_updater_sender( - configuration_updater_sender.clone(), - )) - .and(with_configuration_save_lock(configuration_save_lock)) - .and(with_local_exclusions_store(local_exclusions_store)) - .and_then(exclusions::put_exclusions), - ))) - .or(warp::path("blocking-enabled") - .and( - warp::get() - .and(with_blocking_disabled_store( - blocking_disabled_store.clone(), - )) - .and_then(blocking_enabled::get_blocking_enabled), - ) - .or(warp::put() - .and(warp::path("blocking-enabled")) - .and(warp::body::json()) - .and(with_blocking_disabled_store(blocking_disabled_store).clone()) - .and_then(blocking_enabled::put_blocking_enabled))) - .or( - warp::path("privaxy_ca_certificate.pem").and(warp::get().map(move || { - Response::builder() - .header( - http::header::CONTENT_DISPOSITION, - "attachment; filename=privaxy_ca_certificate.pem;", - ) - .body(ca_certificate_pem.clone()) - })), - ) - .or(warp::options().map(|| "")) - .with(cors); - - warp::serve(routes).run(bind).await - }); + let routes = create_routes( + events_sender, + statistics, + blocking_disabled_store, + configuration_updater_sender, + ca_certificate_pem, + configuration_save_lock, + local_exclusions_store, + http_client, + ) + .with(cors); + + tokio::spawn(async move { warp::serve(routes).run(bind).await }); } fn with_local_exclusions_store( diff --git a/web_frontend/Cargo.toml b/web_frontend/Cargo.toml index 2563cee..1485d2d 100644 --- a/web_frontend/Cargo.toml +++ b/web_frontend/Cargo.toml @@ -5,6 +5,7 @@ edition = "2021" [dependencies] +filterlists-api = { path = "../filterlists-api", features = ["reqwasm"] } yew = "0.19.3" yew-router = "0.16.0" serde_json = "1.0.81" diff --git a/web_frontend/src/filterlists.rs b/web_frontend/src/filterlists.rs index 12a56fb..61caa28 100644 --- a/web_frontend/src/filterlists.rs +++ b/web_frontend/src/filterlists.rs @@ -1,269 +1,233 @@ - -use crate::{save_button, submit_banner, get_api_host}; +use crate::filters::{AddFilterRequest, Filter, FilterConfiguration, FilterGroup}; use crate::save_button::BASE_BUTTON_CSS; +use crate::{get_api_host, save_button, submit_banner}; use reqwasm::http::Request; -use crate::filters::{AddFilterRequest,FilterGroup}; -use serde::{Deserialize, Serialize}; -use std::fmt::Debug; +use url::Url; use wasm_bindgen_futures::spawn_local; -use yew::{html, Component, Context, Html}; -use yew::prelude::*; use web_sys::HtmlInputElement; +use yew::prelude::*; use yew::InputEvent; -use yew::html::Scope; -use url::Url; - - -#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] -#[serde(rename_all = "camelCase")] -pub struct FilterEntry { - id: u64, - name: String, - #[serde(default = "default_description")] - description: String, - license_id: u64, - syntax_ids: Vec, - language_ids: Vec, - tag_ids: Vec, - #[serde(default)] - primary_view_url: Option, - maintainer_ids: Vec, -} -fn default_description() -> String { - "No description".to_string() -} - -type FilterLists = Vec; - -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct FilterViewURL { - segment_number: u32, - primariness: u32, - url: String, -} - -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct FilterListDetail { - id: u64, - name: String, - description: String, - license_id: u64, - syntax_ids: Vec, - language_ids: Vec, - tag_ids: Vec, - primary_view_url: String, - maintainer_ids: Vec, - view_urls: Vec, - home_url: String, - onion_url: String, - policy_url: String, - submission_url: String, - issues_url: String, - forum_url: String, - chat_url: String, - email_address: String, - donate_url: String, - upstream_filter_list_ids: Vec, - include_in_filter_list_ids: Vec, - includes_filter_list_ids: Vec, - dependency_filter_list_ids: Vec, - dependent_filter_list_ids: Vec, -} - -#[derive(Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct FilterSoftware { - id: u64, - name: String, - #[serde(default)] - home_url: Option, - #[serde(default)] - download_url: Option, - supports_abp_url_scheme: bool, - syntax_ids: Vec, -} - -type FilterSoftwareList = Vec; - -#[derive(Serialize, Deserialize, PartialEq, Clone)] -#[serde(rename_all = "camelCase")] -pub struct FilterListSyntax { - id: u64, - name: String, - url: String, - filter_list_ids: Vec, - software_ids: Vec, -} - -type FilterListSyntaxes = Vec; - -#[derive(Serialize, Deserialize, PartialEq, Clone)] -#[serde(rename_all = "camelCase")] -pub struct FilterLicense { - id: u64, - name: String, - #[serde(default)] - url: Option, - #[serde(default)] - permit_modifications: Option, - #[serde(default)] - permit_distribution: Option, - #[serde(default)] - permit_commercial_use: Option, - filter_list_ids: Vec, -} - -type FilterLicenses = Vec; +use yew::{html, Component, Context, Html}; pub enum SearchFilterMessage { Open, Close, - Save, FilterChanged(String), - SelectFilter(Option), + AddFilter(filterlists_api::Filter), + RemoveFilter(filterlists_api::Filter), LoadFilters, - FiltersLoaded(FilterEntry), + FiltersLoaded(Vec), Error(String), NextPage, PreviousPage, - LanguagesLoaded(FilterLanguages), - LicensesLoaded(FilterLicenses), + LanguagesLoaded(Vec), + LicensesLoaded(Vec), + TagsLoaded(Vec), } pub struct SearchFilterList { link: yew::html::Scope, is_open: bool, - filters: FilterLists, - selected_filter: Option, + filters: Vec, filter_query: String, loading: bool, - languages: FilterLanguages, - licenses: FilterLicenses, + languages: Vec, + licenses: Vec, + tags: Vec, current_page: usize, results_per_page: usize, + active_filters: FilterConfiguration, } -#[derive(Serialize, Deserialize, PartialEq, Clone)] -#[serde(rename_all = "camelCase")] -pub struct FilterLanguage { - id: u64, - iso6391: String, - name: String, - filter_list_ids: Vec -} - -type FilterLanguages = Vec; +use filterlists_api; +const FILTER_TAG_GROUPS: [&'static str; 4] = ["ads", "privacy", "malware", "social"]; +#[derive(Properties, PartialEq)] +pub struct Props { + pub filter_configuration: FilterConfiguration, +} impl Component for SearchFilterList { type Message = SearchFilterMessage; - type Properties = (); + type Properties = Props; fn create(_ctx: &Context) -> Self { Self { link: _ctx.link().clone(), is_open: false, - filters: FilterLists::new(), - languages: FilterLanguages::new(), - licenses: FilterLicenses::new(), - selected_filter: None, + filters: Vec::::new(), + languages: Vec::::new(), + licenses: Vec::::new(), + tags: Vec::::new(), filter_query: String::new(), loading: true, current_page: 1, results_per_page: 10, + active_filters: _ctx.props().filter_configuration.clone(), } } - fn update(&mut self, _ctx: &Context, msg: SearchFilterMessage) -> bool { match msg { SearchFilterMessage::Open => { self.is_open = true; self.link.send_message(SearchFilterMessage::LoadFilters); - }, + } SearchFilterMessage::Close => self.is_open = false, SearchFilterMessage::FilterChanged(query) => self.filter_query = query, - SearchFilterMessage::SelectFilter(filter) => self.selected_filter = filter, + SearchFilterMessage::AddFilter(filter) => { + let parsed_url = match Url::parse(&filter.primary_view_url.clone().unwrap()) { + Ok(url) => url, + Err(err) => { + log::error!("Failed to parse URL: {}", err); + return false; + } + }; + let group: FilterGroup = self + .tags + .clone() + .into_iter() + .filter(|tag| { + filter.tag_ids.contains(&tag.id) + && FILTER_TAG_GROUPS.contains(&tag.name.as_str()) + }) + .map(|tag| match tag.name.as_str() { + "ads" => FilterGroup::Ads, + "privacy" => FilterGroup::Privacy, + "malware" => FilterGroup::Malware, + "social" => FilterGroup::Social, + _ => FilterGroup::Regional, + }) + .next() + .unwrap_or(FilterGroup::Regional); + + let request_body: AddFilterRequest = + AddFilterRequest::new(filter.name.clone(), group, parsed_url); + let request = Request::post(&format!("http://{}/filters", get_api_host())) + .header("Content-Type", "application/json") + .body(serde_json::to_string(&request_body).unwrap()); + self.active_filters.push(Filter::new( + filter.name.clone(), + FilterGroup::Malware, + "".to_string(), + )); + spawn_local(async move { + match request.send().await { + Ok(response) => { + if response.ok() { + log::info!("Filter added successfully"); + } else { + log::error!("Failed to add filter: {:?}", response.status()); + } + } + Err(err) => { + log::error!("Request error: {:?}", err); + } + } + }) + } + SearchFilterMessage::RemoveFilter(filter) => { + let parsed_url = match Url::parse(&filter.primary_view_url.clone().unwrap()) { + Ok(url) => url, + Err(err) => { + log::error!("Failed to parse URL: {}", err); + return false; + } + }; + let request_body: AddFilterRequest = + AddFilterRequest::new(filter.name.clone(), FilterGroup::Malware, parsed_url); + let request = Request::delete(&format!("http://{}/filters", get_api_host())) + .header("Content-Type", "application/json") + .body(serde_json::to_string(&request_body).unwrap()); + spawn_local(async move { + match request.send().await { + Ok(response) => { + if response.ok() { + log::info!("Filter removed successfully"); + } else { + log::error!("Failed to remove filter: {:?}", response.status()); + } + } + Err(err) => { + log::error!("Request error: {:?}", err); + } + } + }) + } SearchFilterMessage::LoadFilters => { if self.loading { let link = self.link.clone(); - spawn_local(async move { - match fetch_languages(link.clone()).await { - Ok(_) => log::info!("Languages loaded successfully"), - Err(err) => link.send_message(SearchFilterMessage::Error(err)), - }; - match fetch_filter_lists(link.clone()).await { - Ok(_) => log::info!("Filters loaded successfully"), - Err(err) => link.send_message(SearchFilterMessage::Error(err)), - }; - match fetch_licenses(link.clone()).await { - Ok(_) => log::info!("Licenses loaded successfully"), - Err(err) => link.send_message(SearchFilterMessage::Error(err)), - }; - }); + spawn_local(async move { + match filterlists_api::get_languages().await { + Ok(langs) => { + link.send_message(SearchFilterMessage::LanguagesLoaded(langs)) + } + Err(err) => { + link.send_message(SearchFilterMessage::Error(err.to_string())) + } + }; + match filterlists_api::get_licenses().await { + Ok(licenses) => { + link.send_message(SearchFilterMessage::LicensesLoaded(licenses)) + } + Err(err) => { + link.send_message(SearchFilterMessage::Error(err.to_string())) + } + }; + match filterlists_api::get_tags().await { + Ok(tags) => link.send_message(SearchFilterMessage::TagsLoaded(tags)), + Err(err) => { + link.send_message(SearchFilterMessage::Error(err.to_string())) + } + }; + match filterlists_api::get_filters().await { + Ok(filters) => { + link.send_message(SearchFilterMessage::FiltersLoaded(filters)) + } + Err(err) => { + link.send_message(SearchFilterMessage::Error(err.to_string())) + } + }; + }); + } } - }, - SearchFilterMessage::FiltersLoaded(filter) => { - self.filters.push(filter); + SearchFilterMessage::FiltersLoaded(filters) => { + log::info!("Filters loaded successfully"); + self.filters = filters.clone(); self.loading = false; - }, - SearchFilterMessage::LanguagesLoaded(langs ) => { + } + SearchFilterMessage::LanguagesLoaded(langs) => { + log::info!("Languages loaded successfully"); self.languages = langs.clone(); - }, + } SearchFilterMessage::LicensesLoaded(licenses) => { + log::info!("Licenses loaded successfully"); self.licenses = licenses.clone(); - }, + } + SearchFilterMessage::TagsLoaded(tags) => { + log::info!("Tags loaded successfully"); + self.tags = tags.clone(); + } SearchFilterMessage::Error(error) => { log::error!("Error loading filters: {}", error); self.loading = false; - }, - SearchFilterMessage::Save => { - if let Some(filter) = &self.selected_filter { - let parsed_url = match Url::parse(&filter.primary_view_url.clone().unwrap()) { - Ok(url) => url, - Err(err) => { - log::error!("Failed to parse URL: {}", err); - return false; - } - }; - let request_body: AddFilterRequest = AddFilterRequest::new(self.selected_filter.clone().unwrap().name, FilterGroup::Malware, parsed_url); - let request = Request::post(&format!("http://{}/filters", get_api_host())) - .header("Content-Type", "application/json") - .body(serde_json::to_string(&request_body).unwrap()); - spawn_local(async move { - match request.send().await { - Ok(response) => { - if response.ok() { - log::info!("Filter added successfully"); - } else { - log::error!("Failed to add filter: {:?}", response.status()); - } - } - Err(err) => { - log::error!("Request error: {:?}", err); - } - } - }) - } - self.is_open = false; - }, + } SearchFilterMessage::NextPage => { - if self.current_page < (self.filters.len() as f64 / self.results_per_page as f64).ceil() as usize { + if self.current_page + < (self.filters.len() as f64 / self.results_per_page as f64).ceil() as usize + { self.current_page += 1; } - }, + } SearchFilterMessage::PreviousPage => { if self.current_page > 1 { self.current_page -= 1; } - }, + } } true } - fn view(&self, _ctx: &Context) -> Html { let save_button_classes = classes!( @@ -277,14 +241,24 @@ impl Component for SearchFilterList { "bg-blue-600", "hover:bg-blue-700", ); - - - let filtered_filters: Vec<&FilterEntry> = self.filters.iter() - .filter(|filter| filter.name.to_lowercase().contains(&self.filter_query.to_lowercase())) - .collect(); - let total_pages = (filtered_filters.len() as f64 / self.results_per_page as f64).ceil() as usize; + + let filtered_filters: Vec<&filterlists_api::Filter> = self + .filters + .iter() + .filter(|filter| { + filter + .name + .to_lowercase() + .contains(&self.filter_query.to_lowercase()) + }) + .collect(); + let total_pages = + (filtered_filters.len() as f64 / self.results_per_page as f64).ceil() as usize; let start_index = (self.current_page - 1) * self.results_per_page; - let paginated_filters = filtered_filters.into_iter().skip(start_index).take(self.results_per_page); + let paginated_filters = filtered_filters + .into_iter() + .skip(start_index) + .take(self.results_per_page); let cancel_button_classes = classes!( BASE_BUTTON_CSS.clone().to_vec(), "focus:ring-red-500", @@ -299,9 +273,13 @@ impl Component for SearchFilterList { "py-2", "px-4", "rounded", - if self.current_page == 1 { "opacity-50 cursor-not-allowed" } else { "" }, + if self.current_page == 1 { + "opacity-50 cursor-not-allowed" + } else { + "" + }, ); - + let next_button_classes = classes!( "bg-gray-500", "hover:bg-gray-700", @@ -310,20 +288,24 @@ impl Component for SearchFilterList { "py-2", "px-4", "rounded", - if self.current_page == total_pages { "opacity-50 cursor-not-allowed" } else { "" }, + if self.current_page == total_pages { + "opacity-50 cursor-not-allowed" + } else { + "" + }, ); let document = gloo_utils::document(); if let Some(body) = document.body() { body.set_class_name(if self.is_open { "modal-open" } else { "" }); } - + html! { <> { if self.is_open { html! { @@ -349,31 +331,28 @@ impl Component for SearchFilterList { - { for paginated_filters.map(|filter| self.view_filter_row(filter)) } + { for paginated_filters.map(|filter| self.view_filter_row(filter, _ctx)) }
- {"Page "} {self.current_page} {" of "} {total_pages} -
-
@@ -386,16 +365,28 @@ impl Component for SearchFilterList { } } - - } - impl SearchFilterList { - fn view_filter_row(&self, filter: &FilterEntry) -> Html { - let selected = self.selected_filter.as_ref().map(|f| f.id) == Some(filter.id); + fn view_filter_row(&self, filter: &filterlists_api::Filter, ctx: &Context) -> Html { let filter_clone = filter.clone(); - + let cancel_button_class = classes!( + BASE_BUTTON_CSS.clone().to_vec(), + "focus:ring-red-500", + "bg-red-600", + "hover:bg-red-700", + ); + let save_button_classes = classes!( + BASE_BUTTON_CSS.clone().to_vec(), + "focus:ring-green-500", + "bg-green-600", + "hover:bg-green-700", + ); + let existing_filter = self + .active_filters + .clone() + .into_iter() + .any(|f| f.title == filter.name); html! { @@ -415,14 +406,24 @@ impl SearchFilterList { { self.get_license_name(filter.license_id) } - + } } fn get_language_name(&self, language_ids: Vec) -> String { - self.languages.iter() + self.languages + .iter() .filter(|lang| language_ids.contains(&lang.id)) .map(|lang| lang.name.clone()) .collect::>() @@ -430,82 +431,11 @@ impl SearchFilterList { } fn get_license_name(&self, license_id: u64) -> String { - // Retrieve the license(s) name from the licenses list, given the license ID - // and join them into a single string separated by commas - self.licenses.iter() + self.licenses + .iter() .filter(|license| license.id == license_id) .map(|license| license.name.clone()) .collect::>() .join(", ") } } - - -async fn fetch_filter_lists(link: Scope) -> Result<(), String> { - // dont know what else is supported - let software_list = fetch_software().await?; - let ublock_origin = software_list.iter().find(|software| software.name == "uBlock Origin") - .ok_or("no clue")?; - match _fetch::("https://filterlists.com/api/directory/lists").await { - Ok(filter_lists) => { - for filter in filter_lists.iter().filter(|filter| { - !filter.syntax_ids.is_empty() && filter.syntax_ids.iter().any(|id| ublock_origin.syntax_ids.contains(id)) - }) { - link.send_message(SearchFilterMessage::FiltersLoaded(filter.clone())); - } - }, - Err(err) => return Err(err), - }; - - Ok(()) - - } - -async fn fetch_licenses(link: Scope) -> Result<(), String> { - match _fetch::("https://filterlists.com/api/directory/licenses").await { - Ok(licenses) => { - link.send_message(SearchFilterMessage::LicensesLoaded(licenses)); - Ok(()) - }, - Err(err) => Err(err), - - } -} - -async fn fetch_software() -> Result { - match _fetch::("https://filterlists.com/api/directory/software").await { - Ok(software_list) => Ok(software_list), - Err(err) => Err(err), - - } -} - -async fn fetch_languages(link: Scope) -> Result<(), String> { - match _fetch::("https://filterlists.com/api/directory/languages").await { - Ok(languages) => { - link.send_message(SearchFilterMessage::LanguagesLoaded(languages)); - Ok(()) - }, - Err(err) => return Err(err), - } -} - -async fn _fetch(url: &str) -> Result -where - T: for<'de> Deserialize<'de>, -{ - let response = Request::get(url) - .send() - .await - .map_err(|err| err.to_string())?; - - if response.ok() { - let body = response.text().await.map_err(|err| err.to_string())?; - log::debug!("Raw response body: {}", body); - - let syntaxes: T = serde_json::from_str(&body).map_err(|err| err.to_string())?; - return Ok(syntaxes) - } else { - Err(format!("Failed to fetch from {}: {}", url, response.status_text())) - } -} \ No newline at end of file diff --git a/web_frontend/src/filters.rs b/web_frontend/src/filters.rs index 67c1c3c..eb3e55d 100644 --- a/web_frontend/src/filters.rs +++ b/web_frontend/src/filters.rs @@ -1,5 +1,5 @@ -use crate::{save_button, submit_banner, get_api_host}; use crate::filterlists::SearchFilterList; +use crate::{get_api_host, save_button, submit_banner}; use reqwasm::http::Request; use serde::{Deserialize, Serialize}; use serde_json::de::IoRead; @@ -7,12 +7,12 @@ use serde_json::StreamDeserializer; use serde_with::{serde_as, DisplayFromStr}; use std::fmt::Debug; use std::io::Cursor; +use url::Url; use wasm_bindgen_futures::spawn_local; -use yew::{html, Callback, Component, Context, Html}; -use yew::prelude::*; use web_sys::{HtmlInputElement, HtmlSelectElement}; +use yew::prelude::*; use yew::InputEvent; -use url::Url; +use yew::{html, Callback, Component, Context, Html}; #[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq)] pub enum FilterGroup { @@ -63,7 +63,7 @@ pub enum AddFilterMessage { } #[serde_as] -#[derive(Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] pub struct AddFilterRequest { enabled: bool, title: String, @@ -83,7 +83,6 @@ impl AddFilterRequest { } } - pub struct AddFilterComponent { link: yew::html::Scope, is_open: bool, @@ -102,7 +101,7 @@ impl Component for AddFilterComponent { is_open: false, category: FilterGroup::Default, url: String::new(), - title: String::new() + title: String::new(), } } @@ -114,7 +113,11 @@ impl Component for AddFilterComponent { if let Ok(parsed_url) = Url::parse(&url) { let request_body = AddFilterRequest { enabled: true, - title: if title.is_empty() { self.url.clone() } else { title }, + title: if title.is_empty() { + self.url.clone() + } else { + title + }, group: category, url: parsed_url, }; @@ -192,11 +195,14 @@ impl Component for AddFilterComponent { "Add filter" }; - let options: Html = FilterGroup::values().into_iter().map(|group| { - html! { - - } - }).collect(); + let options: Html = FilterGroup::values() + .into_iter() + .map(|group| { + html! { + + } + }) + .collect(); let url = self.url.clone(); let category = self.category.clone(); @@ -212,7 +218,7 @@ impl Component for AddFilterComponent { {if self.is_open { html! { -
+
@@ -273,17 +279,27 @@ impl Component for AddFilterComponent { } } - #[derive(Deserialize, Clone, PartialEq, Eq)] pub struct Filter { enabled: bool, - title: String, + pub title: String, group: FilterGroup, file_name: String, } +impl Filter { + pub fn new(title: String, group: FilterGroup, file_name: String) -> Self { + Self { + enabled: true, + title, + group, + file_name, + } + } +} + impl std::fmt::Display for Filter { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!( f, "Filter(Enabled={}, Title={}, Group={:?}, File_name={})", @@ -299,8 +315,7 @@ pub struct FilterStatusChangeRequest { file_name: String, } -#[derive(Deserialize, Clone, PartialEq, Eq)] -pub struct FilterConfiguration(Vec); +pub type FilterConfiguration = Vec; pub enum Message { Load, @@ -320,6 +335,17 @@ impl Filters { fn configuration_has_changed(&self) -> bool { self.filter_configuration != self.filter_configuration_before_changes } + pub fn get_filters(&self) -> Vec { + self.filter_configuration.as_ref().unwrap().clone() + } + + pub fn has_filter(&self, filter: &Filter) -> bool { + self.filter_configuration + .as_ref() + .unwrap() + .into_iter() + .any(|f| f.file_name == filter.file_name) + } } impl Component for Filters { @@ -378,7 +404,6 @@ impl Component for Filters { .filter_configuration .as_ref() .unwrap() - .0 .iter() .map(|filter| FilterStatusChangeRequest { enabled: filter.enabled, @@ -413,7 +438,6 @@ impl Component for Filters { self.filter_configuration .as_mut() .unwrap() - .0 .iter_mut() .find(|filter| filter.file_name == filter_name) .and_then(|filter| { @@ -473,7 +497,6 @@ impl Component for Filters { let category_name = format!("{:?}", category); log::debug!("Category filter: {category_name}"); let filters = filters - .0 .iter() .filter(|filter| filter.group == category) .collect::>(); @@ -519,23 +542,25 @@ impl Component for Filters { }; match &self.filter_configuration { - Some(filter_configuration) => html! { - <> - { title } - {success_banner} -
- - - -
- { render_category(FilterGroup::Default, filter_configuration) } - { render_category(FilterGroup::Ads, filter_configuration) } - { render_category(FilterGroup::Privacy, filter_configuration) } - { render_category(FilterGroup::Malware, filter_configuration) } - { render_category(FilterGroup::Social, filter_configuration) } - { render_category(FilterGroup::Regional, filter_configuration) } - - }, + Some(filter_configuration) => { + html! { + <> + { title } + {success_banner} +
+ + + +
+ { render_category(FilterGroup::Default, filter_configuration) } + { render_category(FilterGroup::Ads, filter_configuration) } + { render_category(FilterGroup::Privacy, filter_configuration) } + { render_category(FilterGroup::Malware, filter_configuration) } + { render_category(FilterGroup::Social, filter_configuration) } + { render_category(FilterGroup::Regional, filter_configuration) } + + } + } // This realistically loads way too fast for a loader to be useful. Adding one would just add // unwanted flickering. None => html! {{ title }}, diff --git a/web_frontend/src/main.rs b/web_frontend/src/main.rs index 3b204cb..3377306 100644 --- a/web_frontend/src/main.rs +++ b/web_frontend/src/main.rs @@ -4,8 +4,8 @@ use yew_router::prelude::*; mod blocking_enabled; mod dashboard; -mod filters; mod filterlists; +mod filters; mod requests; mod save_button; mod settings; diff --git a/web_frontend/src/save_button.rs b/web_frontend/src/save_button.rs index 9e26634..81fde99 100644 --- a/web_frontend/src/save_button.rs +++ b/web_frontend/src/save_button.rs @@ -1,7 +1,7 @@ use web_sys::MouseEvent; use yew::{classes, html, Callback, Component, Context, Html, Properties}; -pub const BASE_BUTTON_CSS: [&'static str; 20]= [ +pub const BASE_BUTTON_CSS: [&'static str; 20] = [ "inline-flex", "items-center", "justify-center",