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**
+
@@ -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! {
<>
- {"Search filterlist.com"}
+ {"Search filterlists.com"}
{ 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)) }
-
{"Previous"}
{"Page "} {self.current_page} {" of "} {total_pages}
-
{"Next"}
-
- {"Save"}
-
- {"Cancel"}
+ {"close"}
@@ -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) }
-
+
+ { if existing_filter { "Remove" } else { "Add" } }
+
}
}
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! {
- {group.as_str()}
- }
- }).collect();
+ let options: Html = FilterGroup::values()
+ .into_iter()
+ .map(|group| {
+ html! {
+ {group.as_str()}
+ }
+ })
+ .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",