├── .cargo └── config.toml ├── .github └── workflows │ ├── ci.yml │ └── update-filters.yaml ├── .gitignore ├── CHANGELOG.md ├── Cargo.lock ├── Cargo.toml ├── LICENSE.txt ├── README.md ├── filters ├── __init__.py ├── main.py ├── registry.py ├── registry │ └── .gitkeep └── requirements.txt ├── privaxy ├── Cargo.toml └── src │ ├── resources │ ├── blocked_by_privaxy.html │ ├── error.html │ ├── head.html │ └── vendor │ │ └── ublock │ │ ├── redirect-resources.js │ │ ├── scriptlets.js │ │ └── web_accessible_resources │ │ ├── 1x1.gif │ │ ├── 2x2.png │ │ ├── 32x32.png │ │ ├── 3x2.png │ │ ├── README.txt │ │ ├── addthis_widget.js │ │ ├── amazon_ads.js │ │ ├── amazon_apstag.js │ │ ├── ampproject_v0.js │ │ ├── chartbeat.js │ │ ├── click2load.html │ │ ├── doubleclick_instream_ad_status.js │ │ ├── empty │ │ ├── epicker-ui.html │ │ ├── fingerprint2.js │ │ ├── fingerprint3.js │ │ ├── google-analytics_analytics.js │ │ ├── google-analytics_cx_api.js │ │ ├── google-analytics_ga.js │ │ ├── google-analytics_inpage_linkid.js │ │ ├── google-ima.js │ │ ├── googlesyndication_adsbygoogle.js │ │ ├── googletagmanager_gtm.js │ │ ├── googletagservices_gpt.js │ │ ├── hd-main.js │ │ ├── ligatus_angular-tag.js │ │ ├── monkeybroker.js │ │ ├── mxpnl_mixpanel.js │ │ ├── nobab.js │ │ ├── nobab2.js │ │ ├── noeval-silent.js │ │ ├── noeval.js │ │ ├── nofab.js │ │ ├── noop-0.1s.mp3 │ │ ├── noop-0.5s.mp3 │ │ ├── noop-1s.mp4 │ │ ├── noop-vmap1.0.xml │ │ ├── noop.html │ │ ├── noop.js │ │ ├── noop.txt │ │ ├── outbrain-widget.js │ │ ├── popads-dummy.js │ │ ├── popads.js │ │ ├── prebid-ads.js │ │ ├── scorecardresearch_beacon.js │ │ └── window.open-defuser.js │ └── server │ ├── blocker.rs │ ├── blocker_utils.rs │ ├── ca.rs │ ├── cert.rs │ ├── configuration.rs │ ├── events.rs │ ├── lib.rs │ ├── main.rs │ ├── proxy │ ├── exclusions.rs │ ├── html_rewriter.rs │ ├── mitm.rs │ ├── mod.rs │ └── serve.rs │ └── statistics.rs ├── src-tauri ├── .gitignore ├── Cargo.toml ├── build.rs ├── icons │ ├── 128x128.png │ ├── 128x128@2x.png │ ├── 32x32.png │ ├── Square107x107Logo.png │ ├── Square142x142Logo.png │ ├── Square150x150Logo.png │ ├── Square284x284Logo.png │ ├── Square30x30Logo.png │ ├── Square310x310Logo.png │ ├── Square44x44Logo.png │ ├── Square71x71Logo.png │ ├── Square89x89Logo.png │ ├── StoreLogo.png │ ├── icon.icns │ ├── icon.ico │ ├── icon.png │ └── tray.png ├── src │ ├── commands.rs │ └── main.rs └── tauri.conf.json └── web_frontend ├── Cargo.toml ├── Trunk.toml ├── index.html ├── package-lock.json ├── package.json ├── src ├── blocking_enabled.rs ├── dashboard.rs ├── filters.rs ├── main.rs ├── requests.rs ├── resources │ └── logo.svg ├── save_button.rs ├── save_ca_certificate.rs ├── settings.rs ├── settings_textarea.rs ├── submit_banner.rs └── tailwind.css └── tailwind.config.js /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.aarch64-unknown-linux-gnu] 2 | linker = "aarch64-linux-gnu-gcc" 3 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: [push, pull_request] 3 | jobs: 4 | clippy_check: 5 | name: Run clippy check 6 | runs-on: ubuntu-latest 7 | steps: 8 | - name: Checkout 9 | uses: actions/checkout@v3 10 | 11 | - uses: actions-rs/toolchain@v1 12 | with: 13 | toolchain: nightly 14 | components: clippy 15 | override: true 16 | 17 | - name: Install gui library packages 18 | 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 19 | 20 | - name: Install trunk 21 | uses: actions-rs/cargo@v1 22 | with: 23 | command: install 24 | args: --locked --debug trunk 25 | 26 | - name: Install webassembly rust target 27 | run: rustup target add wasm32-unknown-unknown 28 | 29 | # Required for tailwindcss 30 | - name: Install node 31 | uses: actions/setup-node@v3 32 | with: 33 | node-version: 14 34 | - name: Install node packages 35 | working-directory: ./web_frontend 36 | run: npm i 37 | 38 | # We need to build the web frontend otherwise, we will not be able to compile 39 | # the server and checks will fail. 40 | - name: Build web frontend 41 | run: trunk build 42 | working-directory: ./web_frontend 43 | 44 | - uses: actions-rs/clippy-check@v1 45 | with: 46 | token: ${{ secrets.GITHUB_TOKEN }} 47 | args: --all-features 48 | 49 | rustfmt: 50 | name: Check style 51 | runs-on: ubuntu-latest 52 | steps: 53 | - name: Checkout 54 | uses: actions/checkout@v1 55 | 56 | - name: Install rust 57 | uses: actions-rs/toolchain@v1 58 | with: 59 | toolchain: stable 60 | components: rustfmt 61 | profile: minimal 62 | override: true 63 | 64 | - name: Run cargo fmt 65 | uses: actions-rs/cargo@v1 66 | with: 67 | command: fmt 68 | args: --all -- --check 69 | 70 | ci: 71 | name: CI 72 | runs-on: ${{ matrix.os }} 73 | strategy: 74 | matrix: 75 | include: 76 | - build: linux 77 | os: ubuntu-latest 78 | rust: stable 79 | target: x86_64-unknown-linux-gnu 80 | - build: linux 81 | os: ubuntu-latest 82 | rust: stable 83 | target: aarch64-unknown-linux-gnu 84 | - build: macos 85 | os: macos-latest 86 | rust: stable 87 | target: x86_64-apple-darwin 88 | - buid: macos 89 | os: macos-latest 90 | rust: stable 91 | target: aarch64-apple-darwin 92 | - build: windows 93 | os: windows-latest 94 | rust: stable 95 | target: x86_64-pc-windows-msvc 96 | steps: 97 | - name: Checkout 98 | uses: actions/checkout@v2 99 | 100 | - name: Cache build artifacts 101 | uses: actions/cache@v3 102 | with: 103 | key: no_gui-${{ matrix.os }}-${{ matrix.target }}-artifacts 104 | path: | 105 | ./target 106 | ~/.cargo 107 | 108 | - name: Install rust 109 | uses: actions-rs/toolchain@v1 110 | with: 111 | toolchain: ${{ matrix.rust }} 112 | profile: minimal 113 | override: true 114 | 115 | - name: Install rust target 116 | run: rustup target add ${{ matrix.target }} 117 | 118 | - name: Install cross build dependencies 119 | if: matrix.target == 'aarch64-unknown-linux-gnu' 120 | run: sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu libc6-dev-arm64-cross 121 | 122 | - name: Build server 123 | uses: actions-rs/cargo@v1 124 | with: 125 | command: build 126 | args: --release --target ${{ matrix.target }} --bin privaxy --target-dir target 127 | 128 | - uses: actions/upload-artifact@v3 129 | if: matrix.os != 'windows-latest' 130 | with: 131 | name: privaxy_nogui-${{ matrix.target }} 132 | path: | 133 | target/${{ matrix.target }}/release/privaxy 134 | 135 | - uses: actions/upload-artifact@v3 136 | if: matrix.os == 'windows-latest' 137 | with: 138 | name: privaxy_nogui-${{ matrix.target }} 139 | path: | 140 | target/${{ matrix.target }}/release/privaxy.exe 141 | 142 | ci_desktop: 143 | name: CI Desktop 144 | runs-on: ${{ matrix.os }} 145 | strategy: 146 | matrix: 147 | include: 148 | - build: linux 149 | os: ubuntu-latest 150 | rust: stable 151 | - build: macos 152 | os: macos-latest 153 | rust: stable 154 | - build: windows 155 | os: windows-latest 156 | rust: stable 157 | steps: 158 | - name: Checkout 159 | uses: actions/checkout@v2 160 | 161 | - name: Cache build artifacts 162 | uses: actions/cache@v3 163 | with: 164 | key: gui-${{ matrix.os }}-artifacts 165 | path: | 166 | ./target 167 | ~/.cargo 168 | 169 | - name: Install rust 170 | uses: actions-rs/toolchain@v1 171 | with: 172 | toolchain: ${{ matrix.rust }} 173 | profile: minimal 174 | override: true 175 | 176 | - name: Install trunk 177 | uses: actions-rs/cargo@v1 178 | with: 179 | command: install 180 | args: --locked --debug trunk 181 | 182 | - name: Install webassembly rust target 183 | run: rustup target add wasm32-unknown-unknown 184 | 185 | # Required for tailwindcss 186 | - name: Install node 187 | uses: actions/setup-node@v3 188 | with: 189 | node-version: 16 190 | 191 | - if: startsWith(matrix.os, 'ubuntu') == true 192 | name: Install gui library packages 193 | 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 194 | 195 | - if: startsWith(matrix.os, 'macos') == true 196 | name: Install rust apple arm target 197 | run: rustup target add aarch64-apple-darwin 198 | 199 | - if: startsWith(matrix.os, 'macos') == true 200 | name: Install apple api key private key 201 | run: | 202 | mkdir -p ~/private_keys 203 | echo "$API_KEY" >> ~/private_keys/AuthKey_"$API_KEY_ID".p8 204 | shell: bash 205 | env: 206 | API_KEY: ${{secrets.APPLE_API_KEY_CONTENTS}} 207 | API_KEY_ID: ${{secrets.APPLE_API_KEY}} 208 | 209 | - name: Install node packages 210 | working-directory: ./web_frontend 211 | run: npm i 212 | # It is required to first build the frontend as the server won't 213 | # build if it has no access to frontend's dist directory. 214 | - name: Build web frontend 215 | run: trunk build --release 216 | working-directory: ./web_frontend 217 | 218 | - name: Build desktop app 219 | id: desktop_app 220 | uses: tauri-apps/tauri-action@5a6072a9edbbf71718caee364b5b96731d7580fc #v0 does not handle universal builds 221 | env: 222 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 223 | ENABLE_CODE_SIGNING: ${{ secrets.APPLE_CERTIFICATE }} 224 | APPLE_CERTIFICATE: ${{ secrets.APPLE_CERTIFICATE }} 225 | APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} 226 | APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }} 227 | APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }} 228 | APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }} 229 | with: 230 | tagName: privaxy-v__VERSION__ # the action automatically replaces \_\_VERSION\_\_ with the app version 231 | releaseName: 'Privaxy v__VERSION__' 232 | releaseBody: 'See the assets to download this version and install.' 233 | releaseDraft: true 234 | prerelease: false 235 | args: ${{ startsWith(matrix.os, 'macos') == true && '--target universal-apple-darwin' || '' }} 236 | -------------------------------------------------------------------------------- /.github/workflows/update-filters.yaml: -------------------------------------------------------------------------------- 1 | name: Update filters 2 | 3 | on: 4 | schedule: 5 | - cron: "0 * * * *" 6 | workflow_dispatch: 7 | 8 | jobs: 9 | update-filters: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: "Checkout code" 13 | uses: actions/checkout@v3 14 | 15 | - name: "Setup python" 16 | uses: actions/setup-python@v4 17 | with: 18 | python-version: "3.10" 19 | cache: "pip" 20 | 21 | - name: "Install dependencies" 22 | working-directory: "./filters" 23 | run: python3 -m pip install -r requirements.txt 24 | 25 | - name: Update filters 26 | working-directory: "./filters" 27 | run: rm registry/.gitkeep && python3 main.py 28 | 29 | - name: Publish on cloudflare pages 30 | uses: cloudflare/wrangler-action@2.0.0 31 | with: 32 | apiToken: ${{ secrets.CF_API_TOKEN }} 33 | accountId: ${{ secrets.CF_ACCOUNT_ID }} 34 | command: pages publish filters/registry --project-name=privaxy --branch=production --commit-dirty=true 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /data 3 | web_frontend/dist 4 | .DS_Store 5 | filters/__pycache__ 6 | node_modules 7 | .vscode 8 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## v0.5.2 4 | 5 | - Wildcards are allowed in configurable exclusions. 6 | 7 | ## v0.5.1 8 | 9 | - Apple build of desktop app is now notarized. 10 | 11 | ## v0.5.0 12 | 13 | - Add builds for apple silicon 14 | 15 | ## v0.4.0 16 | 17 | - Now ships as a desktop gui app. 18 | - A new "nogui" binary is shipped alongside the gui version. 19 | - Fixes an issue where cosmetic filtering may not have worked anymore when faulty rules existed in filter lists. 20 | 21 | ## v0.3.1 (December 4, 2022) 22 | 23 | - Update ublock resources. 24 | - Bump dependencies. 25 | 26 | ## v0.3.0 (June 21, 2022) 27 | 28 | - Make use of system resolver. 29 | - Fix windows build (). 30 | 31 | ## v0.2.0 (June 20, 2022) 32 | 33 | - Inject styles and scripts before the `` and `` tags. 34 | - Windows build 35 | 36 | ## v0.1.0 (May 10, 2022) 37 | 38 | - Initial release 39 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["web_frontend", "privaxy", "src-tauri"] 3 | 4 | [profile.release] 5 | lto = true 6 | 7 | [profile.release.package.privaxy] 8 | codegen-units = 1 9 | opt-level = 3 10 | 11 | [profile.release.package.privaxy_app] 12 | codegen-units = 1 13 | opt-level = 3 14 | 15 | [profile.release.package.web_frontend] 16 | codegen-units = 1 17 | opt-level = 's' 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 | 4 |

Privaxy

5 | 6 |

7 | Next generation tracker and advertisement blocker 8 |

9 |
10 | 11 |
12 | dashboard 13 | requests 14 | filters 15 | exclusions 16 | custom_fiters 17 | taskbar 18 |
19 | 20 | ## About 21 | 22 | Privaxy is a MITM HTTP(s) proxy that sits in between HTTP(s) talking applications, such as a web browser and HTTP servers, such as those serving websites. 23 | 24 | By establishing a two-way tunnel between both ends, Privaxy is able to block network requests based on URL patterns and to inject scripts as well as styles into HTML documents. 25 | 26 | Operating at a lower level, Privaxy is both more efficient as well as more streamlined than browser add-on-based blockers. A single instance of Privaxy on a small virtual machine, server or even, on the same computer as the traffic is originating from, can filter thousands of requests per second while requiring a very small amount of memory. 27 | 28 | Privaxy is not limited by the browser’s APIs and can operate with any HTTP traffic, not only the traffic flowing from web browsers. 29 | 30 | Privaxy is also way more capable than DNS-based blockers as it is able to operate directly on URLs and to inject resources into web pages. 31 | 32 | ## Features 33 | 34 | - Suppport for [Adblock Plus filters](https://adblockplus.org/filter-cheatsheet), such as [easylist](https://easylist.to/). 35 | - Web graphical user interface with a statistics display as well as a live request explorer. 36 | - Support for uBlock origin's `js` syntax. 37 | - Support for uBlock origin's `redirect` syntax. 38 | - Support for uBlock origin's scriptlets. 39 | - Browser and HTTP client agnostic. 40 | - Support for custom filters. 41 | - Support for excluding hosts from the MITM pipeline. 42 | - Support for protocol upgrades, such as with websockets. 43 | - Automatic filter lists updates. 44 | - Very low resource usage. 45 | - Around 50MB of memory with approximately 320 000 filters enabled. 46 | - Able to filter thousands of requests per second on a small machine. 47 | 48 | ## Installation 49 | 50 | ### Using a pre-built binary 51 | 52 | Pre-built binaries for major operating systems and platforms are provided at [github releases](https://github.com/Barre/privaxy/releases). 53 | 54 | ### Local system configuration 55 | 56 | 1. Go to the GUI, click on "Save CA certificate". 57 | 2. Install the downloaded certificate locally. 58 | - Macos: 59 | - Linux: `cp privaxy_ca_cert.pem /usr/local/share/ca-certificates/` 60 | 3. Configure your local system to pass http traffic through privaxy which listens on localhost:8100. 61 | - Macos: 62 | - Ubuntu (gnome): 63 | -------------------------------------------------------------------------------- /filters/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/filters/__init__.py -------------------------------------------------------------------------------- /filters/main.py: -------------------------------------------------------------------------------- 1 | from registry import get_filters, FilterException 2 | import sys 3 | import json 4 | 5 | 6 | def eprint(*args, **kwargs): 7 | print(*args, file=sys.stderr, **kwargs) 8 | 9 | 10 | def main(): 11 | filters = get_filters() 12 | 13 | meta_data = [] 14 | 15 | for filter in filters: 16 | print(f"Processing filter: {filter.title}") 17 | 18 | try: 19 | filter.save_to_registry() 20 | except FilterException as e: 21 | eprint(f"Failed to fetch filter: {e}") 22 | 23 | continue 24 | 25 | meta_data.append(filter.to_dict()) 26 | 27 | with open("registry/metadata.json", "w") as f: 28 | f.write(json.dumps(meta_data, indent=4, sort_keys=True, ensure_ascii=False)) 29 | 30 | 31 | if __name__ == "__main__": 32 | main() 33 | -------------------------------------------------------------------------------- /filters/registry/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/filters/registry/.gitkeep -------------------------------------------------------------------------------- /filters/requirements.txt: -------------------------------------------------------------------------------- 1 | requests==2.26 2 | -------------------------------------------------------------------------------- /privaxy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "privaxy" 3 | version = "0.3.1" 4 | edition = "2021" 5 | 6 | [[bin]] 7 | name = "privaxy" 8 | path = "src/server/main.rs" 9 | 10 | [lib] 11 | name = "privaxy" 12 | path = "src/server/lib.rs" 13 | 14 | [dependencies] 15 | hyper = { version = "0.14.23", features = ["full"] } 16 | tokio = { version = "1.22.0", features = ["full"] } 17 | serde_json = "1.0.89" 18 | toml = "0.5.9" 19 | serde = { version = "1.0.148", features = ["derive"] } 20 | tokio-util = { version = "0.7.4", features = ["full"] } 21 | adblock = { version = "0.6.0" } 22 | openssl = { version = "0.10.43", features = ["vendored"] } 23 | include_dir = "0.7.3" 24 | chrono = { version = "0.4.23", features = ["serde"] } 25 | rustls = { version = "0.20.7" } 26 | futures-util = "0.3.25" 27 | wildmatch = "2.1.1" 28 | http = "0.2.8" 29 | mime_guess = "2.0.4" 30 | tokio-rustls = "0.23.4" 31 | hyper-rustls = { version = "0.23.1", features = ["http1", "http2"] } 32 | log = "0.4.17" 33 | env_logger = "0.10.0" 34 | uluru = "3.0.0" 35 | regex = "1.7.0" 36 | lazy_static = "1.4.0" 37 | lol_html = "0.3.1" 38 | crossbeam-channel = "0.5.6" 39 | thiserror = "1.0.37" 40 | url = "2.3.1" 41 | futures = "0.3.25" 42 | dirs = "4.0.0" 43 | async-compression = { version = "0.3.15", features = ["futures-io", "gzip"] } 44 | reqwest = { version = "0.11.13", features = [ 45 | "stream", 46 | "rustls-tls", 47 | "gzip", 48 | "deflate", 49 | "json", 50 | "brotli", 51 | ] } 52 | once_cell = "1.16.0" 53 | serde-tuple-vec-map = "1.0.1" 54 | base64 = "0.13.1" 55 | -------------------------------------------------------------------------------- /privaxy/src/resources/blocked_by_privaxy.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |

403

6 |
7 |
8 |

Privaxy Error. 9 |

10 |

To access this page, disable blocking, update filters or 11 | exclude this host from blocking. 12 |

13 |

14 | Filter that matched this request: #{matching_filter}# 16 |

17 |
18 |
19 |
20 |
21 |
22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /privaxy/src/resources/error.html: -------------------------------------------------------------------------------- 1 | 2 |
3 |
4 |
5 |

502

6 |
7 |
8 |

Bad Gateway. 9 |

10 |

Unable to process your request. 11 |

12 |

13 | Reason: 14 |

#{request_error_reson}#
15 |

16 |
17 |
18 |
19 |
20 |
21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/redirect-resources.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2015-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | 'use strict'; 23 | 24 | /******************************************************************************/ 25 | 26 | // The resources referenced below are found in ./web_accessible_resources/ 27 | // 28 | // The content of the resources which declare a `data` property will be loaded 29 | // in memory, and converted to a suitable internal format depending on the 30 | // type of the loaded data. The `data` property allows for manual injection 31 | // through `+js(...)`, or for redirection to a data: URI when a redirection 32 | // to a web accessible resource is not desirable. 33 | 34 | export default new Map([ 35 | [ '1x1.gif', { 36 | alias: '1x1-transparent.gif', 37 | data: 'blob', 38 | } ], 39 | [ '2x2.png', { 40 | alias: '2x2-transparent.png', 41 | data: 'blob', 42 | } ], 43 | [ '3x2.png', { 44 | alias: '3x2-transparent.png', 45 | data: 'blob', 46 | } ], 47 | [ '32x32.png', { 48 | alias: '32x32-transparent.png', 49 | data: 'blob', 50 | } ], 51 | [ 'addthis_widget.js', { 52 | alias: 'addthis.com/addthis_widget.js', 53 | } ], 54 | [ 'amazon_ads.js', { 55 | alias: 'amazon-adsystem.com/aax2/amzn_ads.js', 56 | data: 'text', 57 | } ], 58 | [ 'amazon_apstag.js', { 59 | } ], 60 | [ 'ampproject_v0.js', { 61 | alias: 'ampproject.org/v0.js', 62 | } ], 63 | [ 'chartbeat.js', { 64 | alias: 'static.chartbeat.com/chartbeat.js', 65 | } ], 66 | [ 'click2load.html', { 67 | params: [ 'aliasURL', 'url' ], 68 | } ], 69 | [ 'doubleclick_instream_ad_status.js', { 70 | alias: 'doubleclick.net/instream/ad_status.js', 71 | data: 'text', 72 | } ], 73 | [ 'empty', { 74 | data: 'text', // Important! 75 | } ], 76 | [ 'fingerprint2.js', { 77 | data: 'text', 78 | } ], 79 | [ 'fingerprint3.js', { 80 | data: 'text', 81 | } ], 82 | [ 'google-analytics_analytics.js', { 83 | alias: [ 84 | 'google-analytics.com/analytics.js', 85 | 'googletagmanager_gtm.js', 86 | 'googletagmanager.com/gtm.js' 87 | ], 88 | data: 'text', 89 | } ], 90 | [ 'google-analytics_cx_api.js', { 91 | alias: 'google-analytics.com/cx/api.js', 92 | } ], 93 | [ 'google-analytics_ga.js', { 94 | alias: 'google-analytics.com/ga.js', 95 | data: 'text', 96 | } ], 97 | [ 'google-analytics_inpage_linkid.js', { 98 | alias: 'google-analytics.com/inpage_linkid.js', 99 | } ], 100 | [ 'google-ima.js', { 101 | } ], 102 | [ 'googlesyndication_adsbygoogle.js', { 103 | alias: 'googlesyndication.com/adsbygoogle.js', 104 | data: 'text', 105 | } ], 106 | [ 'googletagservices_gpt.js', { 107 | alias: 'googletagservices.com/gpt.js', 108 | data: 'text', 109 | } ], 110 | [ 'hd-main.js', { 111 | } ], 112 | [ 'ligatus_angular-tag.js', { 113 | alias: 'ligatus.com/*/angular-tag.js', 114 | } ], 115 | [ 'mxpnl_mixpanel.js', { 116 | } ], 117 | [ 'monkeybroker.js', { 118 | alias: 'd3pkae9owd2lcf.cloudfront.net/mb105.js', 119 | } ], 120 | [ 'noeval.js', { 121 | data: 'text', 122 | } ], 123 | [ 'noeval-silent.js', { 124 | alias: 'silent-noeval.js', 125 | data: 'text', 126 | } ], 127 | [ 'nobab.js', { 128 | alias: 'bab-defuser.js', 129 | data: 'text', 130 | } ], 131 | [ 'nobab2.js', { 132 | data: 'text', 133 | } ], 134 | [ 'nofab.js', { 135 | alias: 'fuckadblock.js-3.2.0', 136 | data: 'text', 137 | } ], 138 | [ 'noop-0.1s.mp3', { 139 | alias: [ 'noopmp3-0.1s', 'abp-resource:blank-mp3' ], 140 | data: 'blob', 141 | } ], 142 | [ 'noop-0.5s.mp3', { 143 | } ], 144 | [ 'noop-1s.mp4', { 145 | alias: 'noopmp4-1s', 146 | data: 'blob', 147 | } ], 148 | [ 'noop.html', { 149 | alias: 'noopframe', 150 | } ], 151 | [ 'noop.js', { 152 | alias: [ 'noopjs', 'abp-resource:blank-js' ], 153 | data: 'text', 154 | } ], 155 | [ 'noop.txt', { 156 | alias: 'nooptext', 157 | data: 'text', 158 | } ], 159 | [ 'noop-vmap1.0.xml', { 160 | alias: 'noopvmap-1.0', 161 | data: 'text', 162 | } ], 163 | [ 'outbrain-widget.js', { 164 | alias: 'widgets.outbrain.com/outbrain.js', 165 | } ], 166 | [ 'popads.js', { 167 | alias: 'popads.net.js', 168 | data: 'text', 169 | } ], 170 | [ 'popads-dummy.js', { 171 | data: 'text', 172 | } ], 173 | [ 'prebid-ads.js', { 174 | data: 'text', 175 | } ], 176 | [ 'scorecardresearch_beacon.js', { 177 | alias: 'scorecardresearch.com/beacon.js', 178 | } ], 179 | [ 'window.open-defuser.js', { 180 | alias: 'nowoif.js', 181 | data: 'text', 182 | } ], 183 | ]); 184 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/1x1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/privaxy/src/resources/vendor/ublock/web_accessible_resources/1x1.gif -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/2x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/privaxy/src/resources/vendor/ublock/web_accessible_resources/2x2.png -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/privaxy/src/resources/vendor/ublock/web_accessible_resources/32x32.png -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/3x2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/privaxy/src/resources/vendor/ublock/web_accessible_resources/3x2.png -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/README.txt: -------------------------------------------------------------------------------- 1 | IMPORTANT 2 | 3 | Content of this folder cannot be accessed without the internal secret token 4 | created for each request to any of the "web accessible resources". 5 | 6 | Any fetch operation made without uBlock Origin's internal secret will result 7 | in failure. This means that despite the content of the folder here declared as 8 | "web accessible resources", it still cannot be seen by the outside world. 9 | 10 | Only uBlock Origin knows the secret token at runtime and hence only 11 | uBlock Origin can access the content of this folder. 12 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/addthis_widget.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | const noopfn = function() { 25 | }; 26 | window.addthis = { 27 | addEventListener: noopfn, 28 | button: noopfn, 29 | counter: noopfn, 30 | init: noopfn, 31 | layers: noopfn, 32 | ready: noopfn, 33 | sharecounters: { 34 | getShareCounts: noopfn 35 | }, 36 | toolbox: noopfn, 37 | update: noopfn 38 | }; 39 | })(); 40 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/amazon_ads.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | if ( amznads ) { 25 | return; 26 | } 27 | var w = window; 28 | var noopfn = function() { 29 | ; 30 | }.bind(); 31 | var amznads = { 32 | appendScriptTag: noopfn, 33 | appendTargetingToAdServerUrl: noopfn, 34 | appendTargetingToQueryString: noopfn, 35 | clearTargetingFromGPTAsync: noopfn, 36 | doAllTasks: noopfn, 37 | doGetAdsAsync: noopfn, 38 | doTask: noopfn, 39 | detectIframeAndGetURL: noopfn, 40 | getAds: noopfn, 41 | getAdsAsync: noopfn, 42 | getAdForSlot: noopfn, 43 | getAdsCallback: noopfn, 44 | getDisplayAds: noopfn, 45 | getDisplayAdsAsync: noopfn, 46 | getDisplayAdsCallback: noopfn, 47 | getKeys: noopfn, 48 | getReferrerURL: noopfn, 49 | getScriptSource: noopfn, 50 | getTargeting: noopfn, 51 | getTokens: noopfn, 52 | getValidMilliseconds: noopfn, 53 | getVideoAds: noopfn, 54 | getVideoAdsAsync: noopfn, 55 | getVideoAdsCallback: noopfn, 56 | handleCallBack: noopfn, 57 | hasAds: noopfn, 58 | renderAd: noopfn, 59 | saveAds: noopfn, 60 | setTargeting: noopfn, 61 | setTargetingForGPTAsync: noopfn, 62 | setTargetingForGPTSync: noopfn, 63 | tryGetAdsAsync: noopfn, 64 | updateAds: noopfn 65 | }; 66 | w.amznads = amznads; 67 | w.amzn_ads = w.amzn_ads || noopfn; 68 | w.aax_write = w.aax_write || noopfn; 69 | w.aax_render_ad = w.aax_render_ad || noopfn; 70 | })(); 71 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/amazon_apstag.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | // https://www.reddit.com/r/uBlockOrigin/comments/ghjqph/ 23 | // https://github.com/NanoMeow/QuickReports/issues/3717 24 | // https://www.reddit.com/r/uBlockOrigin/comments/qyx7en/ 25 | 26 | // https://searchfox.org/mozilla-central/source/browser/extensions/webcompat/shims/apstag.js 27 | // Import queue-related initialization code. 28 | 29 | (function() { 30 | 'use strict'; 31 | const w = window; 32 | const noopfn = function() { 33 | ; // jshint ignore:line 34 | }.bind(); 35 | const _Q = w.apstag && w.apstag._Q || []; 36 | const apstag = { 37 | _Q, 38 | fetchBids: function(a, b) { 39 | if ( typeof b === 'function' ) { 40 | b([]); 41 | } 42 | }, 43 | init: noopfn, 44 | setDisplayBids: noopfn, 45 | targetingKeys: noopfn, 46 | }; 47 | w.apstag = apstag; 48 | _Q.push = function(prefix, args) { 49 | try { 50 | switch (prefix) { 51 | case 'f': 52 | apstag.fetchBids(...args); 53 | break; 54 | } 55 | } catch (e) { 56 | console.trace(e); 57 | } 58 | }; 59 | for ( const cmd of _Q ) { 60 | _Q.push(cmd); 61 | } 62 | })(); 63 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/ampproject_v0.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | const head = document.head; 25 | if ( !head ) { return; } 26 | const style = document.createElement('style'); 27 | style.textContent = [ 28 | 'body {', 29 | ' animation: none !important;', 30 | ' overflow: unset !important;', 31 | '}' 32 | ].join('\n'); 33 | head.appendChild(style); 34 | })(); 35 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/chartbeat.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | const noopfn = function() { 25 | }; 26 | window.pSUPERFLY = { 27 | activity: noopfn, 28 | virtualPage: noopfn 29 | }; 30 | })(); 31 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/click2load.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | uBlock Origin Click-to-Load 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/doubleclick_instream_ad_status.js: -------------------------------------------------------------------------------- 1 | window.google_ad_status = 1; 2 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/empty: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/privaxy/src/resources/vendor/ublock/web_accessible_resources/empty -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/epicker-ui.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | uBlock Origin Element Picker 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/fingerprint2.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2014-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | let browserId = ''; 25 | for ( let i = 0; i < 8; i++ ) { 26 | browserId += (Math.random() * 0x10000 + 0x1000 | 0).toString(16).slice(-4); 27 | } 28 | const fp2 = function(){}; 29 | fp2.get = function(opts, cb) { 30 | if ( !cb ) { cb = opts; } 31 | setTimeout(( ) => { cb(browserId, []); }, 1); 32 | }; 33 | fp2.prototype = { 34 | get: fp2.get 35 | }; 36 | window.Fingerprint2 = fp2; 37 | })(); 38 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/fingerprint3.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2022-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | const visitorId = (( ) => { 25 | let id = ''; 26 | for ( let i = 0; i < 8; i++ ) { 27 | id += (Math.random() * 0x10000 + 0x1000 | 0).toString(16).slice(-4); 28 | } 29 | return id; 30 | })(); 31 | const FingerprintJS = class { 32 | static hashComponents() { 33 | return visitorId; 34 | } 35 | static load() { 36 | return Promise.resolve(new FingerprintJS()); 37 | } 38 | get() { 39 | return Promise.resolve({ 40 | visitorId, 41 | }); 42 | } 43 | }; 44 | window.FingerprintJS = FingerprintJS; 45 | })(); 46 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/google-analytics_analytics.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | // https://developers.google.com/analytics/devguides/collection/analyticsjs/ 25 | const noopfn = function() { 26 | }; 27 | // 28 | const Tracker = function() { 29 | }; 30 | const p = Tracker.prototype; 31 | p.get = noopfn; 32 | p.set = noopfn; 33 | p.send = noopfn; 34 | // 35 | const w = window; 36 | const gaName = w.GoogleAnalyticsObject || 'ga'; 37 | const gaQueue = w[gaName]; 38 | // https://github.com/uBlockOrigin/uAssets/pull/4115 39 | const ga = function() { 40 | const len = arguments.length; 41 | if ( len === 0 ) { return; } 42 | const args = Array.from(arguments); 43 | let fn; 44 | let a = args[len-1]; 45 | if ( a instanceof Object && a.hitCallback instanceof Function ) { 46 | fn = a.hitCallback; 47 | } else if ( a instanceof Function ) { 48 | fn = ( ) => { a(ga.create()); }; 49 | } else { 50 | const pos = args.indexOf('hitCallback'); 51 | if ( pos !== -1 && args[pos+1] instanceof Function ) { 52 | fn = args[pos+1]; 53 | } 54 | } 55 | if ( fn instanceof Function === false ) { return; } 56 | try { 57 | fn(); 58 | } catch (ex) { 59 | } 60 | }; 61 | ga.create = function() { 62 | return new Tracker(); 63 | }; 64 | ga.getByName = function() { 65 | return new Tracker(); 66 | }; 67 | ga.getAll = function() { 68 | return [new Tracker()]; 69 | }; 70 | ga.remove = noopfn; 71 | // https://github.com/uBlockOrigin/uAssets/issues/2107 72 | ga.loaded = true; 73 | w[gaName] = ga; 74 | // https://github.com/gorhill/uBlock/issues/3075 75 | const dl = w.dataLayer; 76 | if ( dl instanceof Object ) { 77 | if ( dl.hide instanceof Object && typeof dl.hide.end === 'function' ) { 78 | dl.hide.end(); 79 | dl.hide.end = ()=>{}; 80 | } 81 | if ( typeof dl.push === 'function' ) { 82 | const doCallback = function(item) { 83 | if ( item instanceof Object === false ) { return; } 84 | if ( typeof item.eventCallback !== 'function' ) { return; } 85 | setTimeout(item.eventCallback, 1); 86 | item.eventCallback = ()=>{}; 87 | }; 88 | dl.push = new Proxy(dl.push, { 89 | apply: function(target, thisArg, args) { 90 | doCallback(args[0]); 91 | return Reflect.apply(target, thisArg, args); 92 | } 93 | }); 94 | if ( Array.isArray(dl) ) { 95 | const q = dl.slice(); 96 | for ( const item of q ) { 97 | doCallback(item); 98 | } 99 | } 100 | } 101 | } 102 | // empty ga queue 103 | if ( gaQueue instanceof Function && Array.isArray(gaQueue.q) ) { 104 | const q = gaQueue.q.slice(); 105 | gaQueue.q.length = 0; 106 | for ( const entry of q ) { 107 | ga(...entry); 108 | } 109 | } 110 | })(); 111 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/google-analytics_cx_api.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | const noopfn = function() { 25 | }; 26 | window.cxApi = { 27 | chooseVariation: function() { 28 | return 0; 29 | }, 30 | getChosenVariation: noopfn, 31 | setAllowHash: noopfn, 32 | setChosenVariation: noopfn, 33 | setCookiePath: noopfn, 34 | setDomainName: noopfn 35 | }; 36 | })(); 37 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/google-analytics_ga.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | const noopfn = function() { 25 | }; 26 | // 27 | const Gaq = function() { 28 | }; 29 | Gaq.prototype.Na = noopfn; 30 | Gaq.prototype.O = noopfn; 31 | Gaq.prototype.Sa = noopfn; 32 | Gaq.prototype.Ta = noopfn; 33 | Gaq.prototype.Va = noopfn; 34 | Gaq.prototype._createAsyncTracker = noopfn; 35 | Gaq.prototype._getAsyncTracker = noopfn; 36 | Gaq.prototype._getPlugin = noopfn; 37 | Gaq.prototype.push = function(a) { 38 | if ( typeof a === 'function' ) { 39 | a(); return; 40 | } 41 | if ( Array.isArray(a) === false ) { return; } 42 | // https://developers.google.com/analytics/devguides/collection/gajs/methods/gaJSApiDomainDirectory#_gat.GA_Tracker_._link 43 | // https://github.com/uBlockOrigin/uBlock-issues/issues/1807 44 | if ( 45 | typeof a[0] === 'string' && 46 | /(^|\.)_link$/.test(a[0]) && 47 | typeof a[1] === 'string' 48 | ) { 49 | try { 50 | window.location.assign(a[1]); 51 | } catch(ex) { 52 | } 53 | } 54 | // https://github.com/gorhill/uBlock/issues/2162 55 | if ( a[0] === '_set' && a[1] === 'hitCallback' && typeof a[2] === 'function' ) { 56 | a[2](); 57 | } 58 | }; 59 | // 60 | const tracker = (function() { 61 | const out = {}; 62 | const api = [ 63 | '_addIgnoredOrganic _addIgnoredRef _addItem _addOrganic', 64 | '_addTrans _clearIgnoredOrganic _clearIgnoredRef _clearOrganic', 65 | '_cookiePathCopy _deleteCustomVar _getName _setAccount', 66 | '_getAccount _getClientInfo _getDetectFlash _getDetectTitle', 67 | '_getLinkerUrl _getLocalGifPath _getServiceMode _getVersion', 68 | '_getVisitorCustomVar _initData _linkByPost', 69 | '_setAllowAnchor _setAllowHash _setAllowLinker _setCampContentKey', 70 | '_setCampMediumKey _setCampNameKey _setCampNOKey _setCampSourceKey', 71 | '_setCampTermKey _setCampaignCookieTimeout _setCampaignTrack _setClientInfo', 72 | '_setCookiePath _setCookiePersistence _setCookieTimeout _setCustomVar', 73 | '_setDetectFlash _setDetectTitle _setDomainName _setLocalGifPath', 74 | '_setLocalRemoteServerMode _setLocalServerMode _setReferrerOverride _setRemoteServerMode', 75 | '_setSampleRate _setSessionTimeout _setSiteSpeedSampleRate _setSessionCookieTimeout', 76 | '_setVar _setVisitorCookieTimeout _trackEvent _trackPageLoadTime', 77 | '_trackPageview _trackSocial _trackTiming _trackTrans', 78 | '_visitCode' 79 | ].join(' ').split(/\s+/); 80 | for ( const method of api ) { 81 | out[method] = noopfn; 82 | } 83 | out._getLinkerUrl = function(a) { 84 | return a; 85 | }; 86 | // https://github.com/AdguardTeam/Scriptlets/issues/154 87 | out._link = function(a) { 88 | if ( typeof a !== 'string' ) { return; } 89 | try { 90 | window.location.assign(a); 91 | } catch(ex) { 92 | } 93 | }; 94 | return out; 95 | })(); 96 | // 97 | const Gat = function() { 98 | }; 99 | Gat.prototype._anonymizeIP = noopfn; 100 | Gat.prototype._createTracker = noopfn; 101 | Gat.prototype._forceSSL = noopfn; 102 | Gat.prototype._getPlugin = noopfn; 103 | Gat.prototype._getTracker = function() { 104 | return tracker; 105 | }; 106 | Gat.prototype._getTrackerByName = function() { 107 | return tracker; 108 | }; 109 | Gat.prototype._getTrackers = noopfn; 110 | Gat.prototype.aa = noopfn; 111 | Gat.prototype.ab = noopfn; 112 | Gat.prototype.hb = noopfn; 113 | Gat.prototype.la = noopfn; 114 | Gat.prototype.oa = noopfn; 115 | Gat.prototype.pa = noopfn; 116 | Gat.prototype.u = noopfn; 117 | const gat = new Gat(); 118 | window._gat = gat; 119 | // 120 | const gaq = new Gaq(); 121 | (function() { 122 | const aa = window._gaq || []; 123 | if ( Array.isArray(aa) ) { 124 | while ( aa[0] ) { 125 | gaq.push(aa.shift()); 126 | } 127 | } 128 | })(); 129 | window._gaq = gaq.qf = gaq; 130 | })(); 131 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/google-analytics_inpage_linkid.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | window._gaq = window._gaq || { 25 | push: function() { 26 | } 27 | }; 28 | })(); 29 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/googlesyndication_adsbygoogle.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | const init = ( ) => { 25 | window.adsbygoogle = { 26 | loaded: true, 27 | push: function() { 28 | } 29 | }; 30 | const phs = document.querySelectorAll('.adsbygoogle'); 31 | const css = 'height:1px!important;max-height:1px!important;max-width:1px!important;width:1px!important;'; 32 | for ( let i = 0; i < phs.length; i++ ) { 33 | const id = `aswift_${i}`; 34 | if ( document.querySelector(`iframe#${id}`) !== null ) { continue; } 35 | const fr = document.createElement('iframe'); 36 | fr.id = id; 37 | fr.style = css; 38 | const cfr = document.createElement('iframe'); 39 | cfr.id = `google_ads_frame${i}`; 40 | fr.appendChild(cfr); 41 | phs[i].appendChild(fr); 42 | } 43 | }; 44 | if ( 45 | document.querySelectorAll('.adsbygoogle').length === 0 && 46 | document.readyState === 'loading' 47 | ) { 48 | window.addEventListener('DOMContentLoaded', init, { once: true }); 49 | } else { 50 | init(); 51 | } 52 | })(); 53 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/googletagmanager_gtm.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | const noopfn = function() { 25 | }; 26 | const w = window; 27 | w.ga = w.ga || noopfn; 28 | const dl = w.dataLayer; 29 | if ( dl instanceof Object === false ) { return; } 30 | if ( dl.hide instanceof Object && typeof dl.hide.end === 'function' ) { 31 | dl.hide.end(); 32 | } 33 | if ( typeof dl.push === 'function' ) { 34 | dl.push = function(o) { 35 | if ( 36 | o instanceof Object && 37 | typeof o.eventCallback === 'function' 38 | ) { 39 | setTimeout(o.eventCallback, 1); 40 | } 41 | }; 42 | } 43 | })(); 44 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/googletagservices_gpt.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | // https://developers.google.com/doubleclick-gpt/reference 25 | const noopfn = function() { 26 | }.bind(); 27 | const noopthisfn = function() { 28 | return this; 29 | }; 30 | const noopnullfn = function() { 31 | return null; 32 | }; 33 | const nooparrayfn = function() { 34 | return []; 35 | }; 36 | const noopstrfn = function() { 37 | return ''; 38 | }; 39 | // 40 | const companionAdsService = { 41 | addEventListener: noopthisfn, 42 | enableSyncLoading: noopfn, 43 | setRefreshUnfilledSlots: noopfn 44 | }; 45 | const contentService = { 46 | addEventListener: noopthisfn, 47 | setContent: noopfn 48 | }; 49 | const PassbackSlot = function() { 50 | }; 51 | let p = PassbackSlot.prototype; 52 | p.display = noopfn; 53 | p.get = noopnullfn; 54 | p.set = noopthisfn; 55 | p.setClickUrl = noopthisfn; 56 | p.setTagForChildDirectedTreatment = noopthisfn; 57 | p.setTargeting = noopthisfn; 58 | p.updateTargetingFromMap = noopthisfn; 59 | const pubAdsService = { 60 | addEventListener: noopthisfn, 61 | clear: noopfn, 62 | clearCategoryExclusions: noopthisfn, 63 | clearTagForChildDirectedTreatment: noopthisfn, 64 | clearTargeting: noopthisfn, 65 | collapseEmptyDivs: noopfn, 66 | defineOutOfPagePassback: function() { return new PassbackSlot(); }, 67 | definePassback: function() { return new PassbackSlot(); }, 68 | disableInitialLoad: noopfn, 69 | display: noopfn, 70 | enableAsyncRendering: noopfn, 71 | enableSingleRequest: noopfn, 72 | enableSyncRendering: noopfn, 73 | enableVideoAds: noopfn, 74 | get: noopnullfn, 75 | getAttributeKeys: nooparrayfn, 76 | getTargeting: noopfn, 77 | getTargetingKeys: nooparrayfn, 78 | getSlots: nooparrayfn, 79 | refresh: noopfn, 80 | removeEventListener: noopfn, 81 | set: noopthisfn, 82 | setCategoryExclusion: noopthisfn, 83 | setCentering: noopfn, 84 | setCookieOptions: noopthisfn, 85 | setForceSafeFrame: noopthisfn, 86 | setLocation: noopthisfn, 87 | setPublisherProvidedId: noopthisfn, 88 | setPrivacySettings: noopthisfn, 89 | setRequestNonPersonalizedAds: noopthisfn, 90 | setSafeFrameConfig: noopthisfn, 91 | setTagForChildDirectedTreatment: noopthisfn, 92 | setTargeting: noopthisfn, 93 | setVideoContent: noopthisfn, 94 | updateCorrelator: noopfn 95 | }; 96 | const SizeMappingBuilder = function() { 97 | }; 98 | p = SizeMappingBuilder.prototype; 99 | p.addSize = noopthisfn; 100 | p.build = noopnullfn; 101 | const Slot = function() { 102 | }; 103 | p = Slot.prototype; 104 | p.addService = noopthisfn; 105 | p.clearCategoryExclusions = noopthisfn; 106 | p.clearTargeting = noopthisfn; 107 | p.defineSizeMapping = noopthisfn; 108 | p.get = noopnullfn; 109 | p.getAdUnitPath = nooparrayfn; 110 | p.getAttributeKeys = nooparrayfn; 111 | p.getCategoryExclusions = nooparrayfn; 112 | p.getDomId = noopstrfn; 113 | p.getResponseInformation = noopnullfn; 114 | p.getSlotElementId = noopstrfn; 115 | p.getSlotId = noopthisfn; 116 | p.getTargeting = nooparrayfn; 117 | p.getTargetingKeys = nooparrayfn; 118 | p.set = noopthisfn; 119 | p.setCategoryExclusion = noopthisfn; 120 | p.setClickUrl = noopthisfn; 121 | p.setCollapseEmptyDiv = noopthisfn; 122 | p.setTargeting = noopthisfn; 123 | p.updateTargetingFromMap = noopthisfn; 124 | // 125 | const gpt = window.googletag || {}; 126 | const cmd = gpt.cmd || []; 127 | gpt.apiReady = true; 128 | gpt.cmd = []; 129 | gpt.cmd.push = function(a) { 130 | try { 131 | a(); 132 | } catch (ex) { 133 | } 134 | return 1; 135 | }; 136 | gpt.companionAds = function() { return companionAdsService; }; 137 | gpt.content = function() { return contentService; }; 138 | gpt.defineOutOfPageSlot = function() { return new Slot(); }; 139 | gpt.defineSlot = function() { return new Slot(); }; 140 | gpt.destroySlots = noopfn; 141 | gpt.disablePublisherConsole = noopfn; 142 | gpt.display = noopfn; 143 | gpt.enableServices = noopfn; 144 | gpt.getVersion = noopstrfn; 145 | gpt.pubads = function() { return pubAdsService; }; 146 | gpt.pubadsReady = true; 147 | gpt.setAdIframeTitle = noopfn; 148 | gpt.sizeMapping = function() { return new SizeMappingBuilder(); }; 149 | window.googletag = gpt; 150 | while ( cmd.length !== 0 ) { 151 | gpt.cmd.push(cmd.shift()); 152 | } 153 | })(); 154 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/hd-main.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | const l = {}; 25 | const noopfn = function() { 26 | }; 27 | const props = [ 28 | "$j","Ad","Bd","Cd","Dd","Ed","Fd","Gd","Hd","Id","Jd","Nj","Oc","Pc","Pe", 29 | "Qc","Qe","Rc","Re","Ri","Sc","Tc","Uc","Vc","Wc","Wg","Xc","Xg","Yc","Yd", 30 | "ad","ae","bd","bf","cd","dd","ed","ef","ek","fd","fg","fh","fk","gd","hd", 31 | "ig","ij","jd","kd","ke","ld","md","mi","nd","od","oh","pd","pf","qd","rd", 32 | "sd","td","ud","vd","wd","wg","xd","xh","yd","zd", 33 | "$d","$e","$k","Ae","Af","Aj","Be","Ce","De","Ee","Ek","Eo","Ep","Fe","Fo", 34 | "Ge","Gh","Hk","Ie","Ip","Je","Ke","Kk","Kq","Le","Lh","Lk","Me","Mm","Ne", 35 | "Oe","Pe","Qe","Re","Rp","Se","Te","Ue","Ve","Vp","We","Xd","Xe","Yd","Ye", 36 | "Zd","Ze","Zf","Zk","ae","af","al","be","bf","bg","ce","cp","df","di","ee", 37 | "ef","fe","ff","gf","gm","he","hf","ie","je","jf","ke","kf","kl","le","lf", 38 | "lk","mf","mg","mn","nf","oe","of","pe","pf","pg","qe","qf","re","rf","se", 39 | "sf","te","tf","ti","ue","uf","ve","vf","we","wf","wg","wi","xe","ye","yf", 40 | "yk","yl","ze","zf","zk" 41 | ]; 42 | for ( let i = 0; i < props.length; i++ ) { 43 | l[props[i]] = noopfn; 44 | } 45 | window.L = window.J = l; 46 | })(); 47 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/ligatus_angular-tag.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | self.adProtect = true; 25 | Object.defineProperties(window, { 26 | uabpdl: { value: true }, 27 | uabDetect: { value: true } 28 | }); 29 | })(); 30 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/monkeybroker.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | const noopfn = function() { 25 | }; 26 | window.pbjs = { libLoaded: true }; 27 | const mb = window.MonkeyBroker || { 28 | addAttribute: noopfn, 29 | addSlot: function(a) { 30 | this.slots[a.slot] = {}; 31 | }, 32 | defineSlot: noopfn, 33 | fillSlot: noopfn, 34 | go: noopfn, 35 | inventoryConditionalPlacement: noopfn, 36 | registerSizeCallback: noopfn, 37 | registerSlotCallback: noopfn, 38 | slots: {}, 39 | version: '' 40 | }; 41 | mb.regSlotsMap = mb.slots; 42 | window.MonkeyBroker = mb; 43 | })(); 44 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/mxpnl_mixpanel.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2021-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | // https://developer.mixpanel.com/docs/javascript-full-api-reference 25 | const mixpanel = { 26 | get_distinct_id() { 27 | return ''; 28 | }, 29 | init(t, cfg) { 30 | if ( cfg instanceof Object === false ) { return; } 31 | if ( 'loaded' in cfg === false ) { return; } 32 | if ( cfg.loaded instanceof Function === false ) { return; } 33 | cfg.loaded(); 34 | }, 35 | register() { 36 | }, 37 | register_once() { 38 | }, 39 | track() { 40 | const cb = Array.from(arguments).pop(); 41 | if ( cb instanceof Function === false ) { return; } 42 | cb(); 43 | }, 44 | }; 45 | const q = self.mixpanel && self.mixpanel._i || []; 46 | self.mixpanel = mixpanel; 47 | for ( const i of q ) { 48 | if ( Array.isArray(i) === false ) { continue; } 49 | mixpanel.init(...i); 50 | } 51 | })(); 52 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/nobab.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | const signatures = [ 25 | [ 'blockadblock' ], 26 | [ 'babasbm' ], 27 | [ /getItem\('babn'\)/ ], 28 | [ 29 | 'getElementById', 30 | 'String.fromCharCode', 31 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789', 32 | 'charAt', 33 | 'DOMContentLoaded', 34 | 'AdBlock', 35 | 'addEventListener', 36 | 'doScroll', 37 | 'fromCharCode', 38 | '<<2|r>>4', 39 | 'sessionStorage', 40 | 'clientWidth', 41 | 'localStorage', 42 | 'Math', 43 | 'random' 44 | ], 45 | ]; 46 | const check = function(s) { 47 | for ( let i = 0; i < signatures.length; i++ ) { 48 | const tokens = signatures[i]; 49 | let match = 0; 50 | for ( let j = 0; j < tokens.length; j++ ) { 51 | const token = tokens[j]; 52 | const pos = token instanceof RegExp 53 | ? s.search(token) 54 | : s.indexOf(token); 55 | if ( pos !== -1 ) { match += 1; } 56 | } 57 | if ( (match / tokens.length) >= 0.8 ) { return true; } 58 | } 59 | return false; 60 | }; 61 | window.eval = new Proxy(window.eval, { // jshint ignore: line 62 | apply: function(target, thisArg, args) { 63 | const a = args[0]; 64 | if ( typeof a !== 'string' || !check(a) ) { 65 | return target.apply(thisArg, args); 66 | } 67 | if ( document.body ) { 68 | document.body.style.removeProperty('visibility'); 69 | } 70 | let el = document.getElementById('babasbmsgx'); 71 | if ( el ) { 72 | el.parentNode.removeChild(el); 73 | } 74 | } 75 | }); 76 | window.setTimeout = new Proxy(window.setTimeout, { 77 | apply: function(target, thisArg, args) { 78 | const a = args[0]; 79 | if ( 80 | typeof a !== 'string' || 81 | /\.bab_elementid.$/.test(a) === false 82 | ) { 83 | return target.apply(thisArg, args); 84 | } 85 | } 86 | }); 87 | })(); 88 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/nobab2.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2021-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | const script = document.currentScript; 25 | if ( script === null ) { return; } 26 | const src = script.src; 27 | if ( typeof src !== 'string' ) { return; } 28 | // The scriplet is meant to act ONLY when it's being used as a redirection 29 | // for specific domains. 30 | const re = new RegExp( 31 | '^https?://[\\w-]+\\.(' + 32 | [ 33 | 'adclixx\\.net', 34 | 'adnetasia\\.com', 35 | 'adtrackers\\.net', 36 | 'bannertrack\\.net', 37 | ].join('|') + 38 | ')/.' 39 | ); 40 | if ( re.test(src) === false ) { return; } 41 | window.nH7eXzOsG = 858; 42 | })(); 43 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/noeval-silent.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | window.eval = new Proxy(window.eval, { // jshint ignore: line 25 | apply: function() { 26 | } 27 | }); 28 | })(); 29 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/noeval.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | const log = console.log.bind(console); 25 | window.eval = new Proxy(window.eval, { // jshint ignore: line 26 | apply: function(target, thisArg, args) { 27 | log(`Document tried to eval... ${args[0]}\n`); 28 | } 29 | }); 30 | })(); 31 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/nofab.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | const noopfn = function() { 25 | }; 26 | const Fab = function() {}; 27 | Fab.prototype.check = noopfn; 28 | Fab.prototype.clearEvent = noopfn; 29 | Fab.prototype.emitEvent = noopfn; 30 | Fab.prototype.on = function(a, b) { 31 | if ( !a ) { b(); } 32 | return this; 33 | }; 34 | Fab.prototype.onDetected = function() { 35 | return this; 36 | }; 37 | Fab.prototype.onNotDetected = function(a) { 38 | a(); 39 | return this; 40 | }; 41 | Fab.prototype.setOption = noopfn; 42 | const fab = new Fab(), 43 | getSetFab = { 44 | get: function() { return Fab; }, 45 | set: function() {} 46 | }, 47 | getsetfab = { 48 | get: function() { return fab; }, 49 | set: function() {} 50 | }; 51 | if ( window.hasOwnProperty('FuckAdBlock') ) { window.FuckAdBlock = Fab; } 52 | else { Object.defineProperty(window, 'FuckAdBlock', getSetFab); } 53 | if ( window.hasOwnProperty('BlockAdBlock') ) { window.BlockAdBlock = Fab; } 54 | else { Object.defineProperty(window, 'BlockAdBlock', getSetFab); } 55 | if ( window.hasOwnProperty('SniffAdBlock') ) { window.SniffAdBlock = Fab; } 56 | else { Object.defineProperty(window, 'SniffAdBlock', getSetFab); } 57 | if ( window.hasOwnProperty('fuckAdBlock') ) { window.fuckAdBlock = fab; } 58 | else { Object.defineProperty(window, 'fuckAdBlock', getsetfab); } 59 | if ( window.hasOwnProperty('blockAdBlock') ) { window.blockAdBlock = fab; } 60 | else { Object.defineProperty(window, 'blockAdBlock', getsetfab); } 61 | if ( window.hasOwnProperty('sniffAdBlock') ) { window.sniffAdBlock = fab; } 62 | else { Object.defineProperty(window, 'sniffAdBlock', getsetfab); } 63 | })(); 64 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/noop-0.1s.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/privaxy/src/resources/vendor/ublock/web_accessible_resources/noop-0.1s.mp3 -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/noop-0.5s.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/privaxy/src/resources/vendor/ublock/web_accessible_resources/noop-0.5s.mp3 -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/noop-1s.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/privaxy/src/resources/vendor/ublock/web_accessible_resources/noop-1s.mp4 -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/noop-vmap1.0.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/noop.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/noop.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | })(); 4 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/noop.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/outbrain-widget.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | const noopfn = function() { 25 | }; 26 | const obr = {}; 27 | const methods = [ 28 | 'callClick', 29 | 'callLoadMore', 30 | 'callRecs', 31 | 'callUserZapping', 32 | 'callWhatIs', 33 | 'cancelRecommendation', 34 | 'cancelRecs', 35 | 'closeCard', 36 | 'closeModal', 37 | 'closeTbx', 38 | 'errorInjectionHandler', 39 | 'getCountOfRecs', 40 | 'getStat', 41 | 'imageError', 42 | 'manualVideoClicked', 43 | 'onOdbReturn', 44 | 'onVideoClick', 45 | 'pagerLoad', 46 | 'recClicked', 47 | 'refreshSpecificWidget', 48 | 'renderSpaWidgets', 49 | 'refreshWidget', 50 | 'reloadWidget', 51 | 'researchWidget', 52 | 'returnedError', 53 | 'returnedHtmlData', 54 | 'returnedIrdData', 55 | 'returnedJsonData', 56 | 'scrollLoad', 57 | 'showDescription', 58 | 'showRecInIframe', 59 | 'userZappingMessage', 60 | 'zappingFormAction' 61 | ]; 62 | obr.extern = { 63 | video: { 64 | getVideoRecs: noopfn, 65 | videoClicked: noopfn 66 | } 67 | }; 68 | methods.forEach(function(a) { 69 | obr.extern[a] = noopfn; 70 | }); 71 | window.OBR = window.OBR || obr; 72 | })(); 73 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/popads-dummy.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | delete window.PopAds; 25 | delete window.popns; 26 | Object.defineProperties(window, { 27 | PopAds: { value: {} }, 28 | popns: { value: {} } 29 | }); 30 | })(); 31 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/popads.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | const magic = String.fromCharCode(Date.now() % 26 + 97) + 25 | Math.floor(Math.random() * 982451653 + 982451653).toString(36); 26 | const oe = window.onerror; 27 | window.onerror = function(msg, src, line, col, error) { 28 | if ( typeof msg === 'string' && msg.indexOf(magic) !== -1 ) { return true; } 29 | if ( oe instanceof Function ) { 30 | return oe(msg, src, line, col, error); 31 | } 32 | }.bind(); 33 | const throwMagic = function() { throw magic; }; 34 | delete window.PopAds; 35 | delete window.popns; 36 | Object.defineProperties(window, { 37 | PopAds: { set: throwMagic }, 38 | popns: { set: throwMagic } 39 | }); 40 | })(); 41 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/prebid-ads.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2022-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | window.canRunAds = true; 25 | window.isAdBlockActive = false; 26 | })(); 27 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/scorecardresearch_beacon.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | window.COMSCORE = { 25 | purge: function() { 26 | window._comscore = []; 27 | }, 28 | beacon: function() { 29 | } 30 | }; 31 | })(); 32 | -------------------------------------------------------------------------------- /privaxy/src/resources/vendor/ublock/web_accessible_resources/window.open-defuser.js: -------------------------------------------------------------------------------- 1 | /******************************************************************************* 2 | 3 | uBlock Origin - a browser extension to block requests. 4 | Copyright (C) 2019-present Raymond Hill 5 | 6 | This program is free software: you can redistribute it and/or modify 7 | it under the terms of the GNU General Public License as published by 8 | the Free Software Foundation, either version 3 of the License, or 9 | (at your option) any later version. 10 | 11 | This program is distributed in the hope that it will be useful, 12 | but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | GNU General Public License for more details. 15 | 16 | You should have received a copy of the GNU General Public License 17 | along with this program. If not, see {http://www.gnu.org/licenses/}. 18 | 19 | Home: https://github.com/gorhill/uBlock 20 | */ 21 | 22 | (function() { 23 | 'use strict'; 24 | let arg1 = '{{1}}'; 25 | if ( arg1 === '{{1}}' ) { arg1 = ''; } 26 | let arg2 = '{{2}}'; 27 | if ( arg2 === '{{2}}' ) { arg2 = ''; } 28 | let arg3 = '{{3}}'; 29 | if ( arg3 === '{{3}}' ) { arg3 = ''; } 30 | const log = /\blog\b/.test(arg3) 31 | ? console.log.bind(console) 32 | : ( ) => { }; 33 | const newSyntax = /^[01]?$/.test(arg1) === false; 34 | let pattern = ''; 35 | let targetResult = true; 36 | let autoRemoveAfter = -1; 37 | if ( newSyntax ) { 38 | pattern = arg1; 39 | if ( pattern.startsWith('!') ) { 40 | targetResult = false; 41 | pattern = pattern.slice(1); 42 | } 43 | autoRemoveAfter = parseInt(arg2); 44 | if ( isNaN(autoRemoveAfter) ) { 45 | autoRemoveAfter = -1; 46 | } 47 | } else { 48 | pattern = arg2; 49 | if ( arg1 === '0' ) { 50 | targetResult = false; 51 | } 52 | } 53 | if ( pattern === '' ) { 54 | pattern = '.?'; 55 | } else if ( /^\/.+\/$/.test(pattern) ) { 56 | pattern = pattern.slice(1,-1); 57 | } else { 58 | pattern = pattern.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); 59 | } 60 | const rePattern = new RegExp(pattern); 61 | const createDecoy = function(tag, urlProp, url) { 62 | const decoy = document.createElement(tag); 63 | decoy[urlProp] = url; 64 | decoy.style.setProperty('height','1px', 'important'); 65 | decoy.style.setProperty('position','fixed', 'important'); 66 | decoy.style.setProperty('top','-1px', 'important'); 67 | decoy.style.setProperty('width','1px', 'important'); 68 | document.body.appendChild(decoy); 69 | setTimeout(( ) => decoy.remove(), autoRemoveAfter * 1000); 70 | return decoy; 71 | }; 72 | window.open = new Proxy(window.open, { 73 | apply: function(target, thisArg, args) { 74 | log('window.open:', ...args); 75 | const url = args[0]; 76 | if ( rePattern.test(url) !== targetResult ) { 77 | return target.apply(thisArg, args); 78 | } 79 | if ( autoRemoveAfter < 0 ) { return null; } 80 | const decoy = /\bobj\b/.test(arg3) 81 | ? createDecoy('object', 'data', url) 82 | : createDecoy('iframe', 'src', url); 83 | let popup = decoy.contentWindow; 84 | if ( typeof popup === 'object' && popup !== null ) { 85 | Object.defineProperty(popup, 'closed', { value: false }); 86 | } else { 87 | const noopFunc = (function(){}).bind(self); 88 | popup = new Proxy(self, { 89 | get: function(target, prop) { 90 | if ( prop === 'closed' ) { return false; } 91 | const r = Reflect.get(...arguments); 92 | if ( typeof r === 'function' ) { return noopFunc; } 93 | return target[prop]; 94 | }, 95 | set: function() { 96 | return Reflect.set(...arguments); 97 | }, 98 | }); 99 | } 100 | if ( /\blog\b/.test(arg3) ) { 101 | popup = new Proxy(popup, { 102 | get: function(target, prop) { 103 | log('window.open / get', prop, '===', target[prop]); 104 | return Reflect.get(...arguments); 105 | }, 106 | set: function(target, prop, value) { 107 | log('window.open / set', prop, '=', value); 108 | return Reflect.set(...arguments); 109 | }, 110 | }); 111 | } 112 | return popup; 113 | } 114 | }); 115 | })(); 116 | -------------------------------------------------------------------------------- /privaxy/src/server/ca.rs: -------------------------------------------------------------------------------- 1 | use openssl::asn1::Asn1Time; 2 | use openssl::bn::{BigNum, MsbOption}; 3 | use openssl::hash::MessageDigest; 4 | use openssl::pkey::PKey; 5 | use openssl::pkey::Private; 6 | use openssl::rsa::Rsa; 7 | use openssl::x509::extension::{BasicConstraints, KeyUsage, SubjectKeyIdentifier}; 8 | use openssl::x509::X509NameBuilder; 9 | use openssl::x509::X509; 10 | 11 | const ORGANIZATION_NAME: &str = "Privaxy"; 12 | 13 | pub fn make_ca_certificate() -> (X509, PKey) { 14 | let rsa = Rsa::generate(2048).unwrap(); 15 | let key_pair = PKey::from_rsa(rsa).unwrap(); 16 | 17 | let mut x509_name = X509NameBuilder::new().unwrap(); 18 | x509_name.append_entry_by_text("C", "US").unwrap(); 19 | x509_name.append_entry_by_text("ST", "CA").unwrap(); 20 | x509_name 21 | .append_entry_by_text("O", ORGANIZATION_NAME) 22 | .unwrap(); 23 | x509_name 24 | .append_entry_by_text("CN", ORGANIZATION_NAME) 25 | .unwrap(); 26 | let x509_name = x509_name.build(); 27 | 28 | let mut cert_builder = X509::builder().unwrap(); 29 | cert_builder.set_version(2).unwrap(); 30 | 31 | let serial_number = { 32 | let mut serial = BigNum::new().unwrap(); 33 | serial.rand(159, MsbOption::MAYBE_ZERO, false).unwrap(); 34 | serial.to_asn1_integer().unwrap() 35 | }; 36 | 37 | cert_builder.set_serial_number(&serial_number).unwrap(); 38 | cert_builder.set_subject_name(&x509_name).unwrap(); 39 | cert_builder.set_issuer_name(&x509_name).unwrap(); 40 | cert_builder.set_pubkey(&key_pair).unwrap(); 41 | 42 | let not_before = Asn1Time::days_from_now(0).unwrap(); 43 | cert_builder.set_not_before(¬_before).unwrap(); 44 | 45 | let not_after = Asn1Time::days_from_now(3650).unwrap(); 46 | 47 | cert_builder.set_not_after(¬_after).unwrap(); 48 | cert_builder 49 | .append_extension(BasicConstraints::new().critical().ca().build().unwrap()) 50 | .unwrap(); 51 | cert_builder 52 | .append_extension( 53 | KeyUsage::new() 54 | .critical() 55 | .key_cert_sign() 56 | .crl_sign() 57 | .build() 58 | .unwrap(), 59 | ) 60 | .unwrap(); 61 | 62 | let subject_key_identifier = SubjectKeyIdentifier::new() 63 | .build(&cert_builder.x509v3_context(None, None)) 64 | .unwrap(); 65 | 66 | cert_builder 67 | .append_extension(subject_key_identifier) 68 | .unwrap(); 69 | cert_builder 70 | .sign(&key_pair, MessageDigest::sha256()) 71 | .unwrap(); 72 | 73 | let cert = cert_builder.build(); 74 | 75 | (cert, key_pair) 76 | } 77 | -------------------------------------------------------------------------------- /privaxy/src/server/cert.rs: -------------------------------------------------------------------------------- 1 | use http::uri::Authority; 2 | use openssl::{ 3 | asn1::Asn1Time, 4 | bn::{BigNum, MsbOption}, 5 | hash::MessageDigest, 6 | pkey::{PKey, PKeyRef, Private}, 7 | rsa::Rsa, 8 | x509::{ 9 | extension::{ 10 | AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectAlternativeName, 11 | SubjectKeyIdentifier, 12 | }, 13 | X509NameBuilder, X509Ref, X509Req, X509ReqBuilder, X509, 14 | }, 15 | }; 16 | use rustls::{Certificate, PrivateKey, ServerConfig}; 17 | use std::{str::FromStr, sync::Arc}; 18 | use tokio::sync::Mutex; 19 | use uluru::LRUCache; 20 | 21 | const MAX_CACHED_CERTIFICATES: usize = 1_000; 22 | 23 | #[derive(Clone)] 24 | pub struct SignedWithCaCert { 25 | authority: Authority, 26 | pub server_configuration: ServerConfig, 27 | } 28 | 29 | impl SignedWithCaCert { 30 | fn new( 31 | authority: Authority, 32 | private_key: PKey, 33 | ca_certificate: X509, 34 | ca_private_key: PKey, 35 | ) -> Self { 36 | let x509 = 37 | Self::build_ca_signed_cert(&ca_certificate, &ca_private_key, &authority, &private_key); 38 | 39 | let certs = vec![ 40 | Certificate(x509.to_der().unwrap()), 41 | Certificate(ca_certificate.to_der().unwrap()), 42 | ]; 43 | 44 | let server_configuration = ServerConfig::builder() 45 | .with_safe_default_cipher_suites() 46 | .with_safe_default_kx_groups() 47 | .with_safe_default_protocol_versions() 48 | .unwrap() 49 | .with_no_client_auth() 50 | .with_single_cert(certs, PrivateKey(private_key.private_key_to_der().unwrap())) 51 | .unwrap(); 52 | 53 | Self { 54 | authority, 55 | server_configuration, 56 | } 57 | } 58 | 59 | fn build_certificate_request(key_pair: &PKey, authority: &Authority) -> X509Req { 60 | let mut request_builder = X509ReqBuilder::new().unwrap(); 61 | request_builder.set_pubkey(key_pair).unwrap(); 62 | 63 | let mut x509_name = X509NameBuilder::new().unwrap(); 64 | 65 | // Only 64 characters are allowed in the CN field. 66 | // (ub-common-name INTEGER ::= 64), browsers are not using CN anymore but uses SANs instead. 67 | // Let's use a shorter entry. 68 | // RFC 3280. 69 | let authority_host = authority.host(); 70 | let common_name = if authority_host.len() > 64 { 71 | "privaxy_cn_too_long.local" 72 | } else { 73 | authority_host 74 | }; 75 | 76 | x509_name.append_entry_by_text("CN", common_name).unwrap(); 77 | let x509_name = x509_name.build(); 78 | request_builder.set_subject_name(&x509_name).unwrap(); 79 | 80 | request_builder 81 | .sign(key_pair, MessageDigest::sha256()) 82 | .unwrap(); 83 | 84 | request_builder.build() 85 | } 86 | 87 | fn build_ca_signed_cert( 88 | ca_cert: &X509Ref, 89 | ca_key_pair: &PKeyRef, 90 | authority: &Authority, 91 | private_key: &PKey, 92 | ) -> X509 { 93 | let req = Self::build_certificate_request(private_key, authority); 94 | 95 | let mut cert_builder = X509::builder().unwrap(); 96 | cert_builder.set_version(2).unwrap(); 97 | 98 | let serial_number = { 99 | let mut serial = BigNum::new().unwrap(); 100 | serial.rand(159, MsbOption::MAYBE_ZERO, false).unwrap(); 101 | serial.to_asn1_integer().unwrap() 102 | }; 103 | 104 | cert_builder.set_serial_number(&serial_number).unwrap(); 105 | cert_builder.set_subject_name(req.subject_name()).unwrap(); 106 | cert_builder 107 | .set_issuer_name(ca_cert.subject_name()) 108 | .unwrap(); 109 | cert_builder.set_pubkey(private_key).unwrap(); 110 | 111 | let not_before = Asn1Time::days_from_now(0).unwrap(); 112 | cert_builder.set_not_before(¬_before).unwrap(); 113 | 114 | let not_after = Asn1Time::days_from_now(365).unwrap(); 115 | cert_builder.set_not_after(¬_after).unwrap(); 116 | 117 | cert_builder 118 | .append_extension(BasicConstraints::new().build().unwrap()) 119 | .unwrap(); 120 | 121 | cert_builder 122 | .append_extension( 123 | KeyUsage::new() 124 | .critical() 125 | .non_repudiation() 126 | .digital_signature() 127 | .key_encipherment() 128 | .build() 129 | .unwrap(), 130 | ) 131 | .unwrap(); 132 | 133 | let subject_alternative_name = match std::net::IpAddr::from_str(authority.host()) { 134 | // If we are able to parse the authority as an ip address, let's build an "IP" field instead 135 | // of a "DNS" one. 136 | Ok(_ip_addr) => { 137 | let mut san = SubjectAlternativeName::new(); 138 | san.ip(authority.host()); 139 | 140 | san 141 | } 142 | Err(_err) => { 143 | let mut san = SubjectAlternativeName::new(); 144 | san.dns(authority.host()); 145 | san 146 | } 147 | } 148 | .build(&cert_builder.x509v3_context(Some(ca_cert), None)) 149 | .unwrap(); 150 | 151 | cert_builder 152 | .append_extension(subject_alternative_name) 153 | .unwrap(); 154 | 155 | let subject_key_identifier = SubjectKeyIdentifier::new() 156 | .build(&cert_builder.x509v3_context(Some(ca_cert), None)) 157 | .unwrap(); 158 | cert_builder 159 | .append_extension(subject_key_identifier) 160 | .unwrap(); 161 | 162 | let auth_key_identifier = AuthorityKeyIdentifier::new() 163 | .keyid(false) 164 | .issuer(false) 165 | .build(&cert_builder.x509v3_context(Some(ca_cert), None)) 166 | .unwrap(); 167 | cert_builder.append_extension(auth_key_identifier).unwrap(); 168 | 169 | cert_builder 170 | .sign(ca_key_pair, MessageDigest::sha256()) 171 | .unwrap(); 172 | 173 | cert_builder.build() 174 | } 175 | } 176 | 177 | #[derive(Clone)] 178 | pub struct CertCache { 179 | cache: Arc>>, 180 | // We use a single RSA key for all certificates. 181 | private_key: PKey, 182 | ca_certificate: X509, 183 | ca_private_key: PKey, 184 | } 185 | 186 | impl CertCache { 187 | pub fn new(ca_certificate: X509, ca_private_key: PKey) -> Self { 188 | Self { 189 | cache: Arc::new(Mutex::new(LRUCache::default())), 190 | private_key: { 191 | let rsa = Rsa::generate(2048).unwrap(); 192 | PKey::from_rsa(rsa).unwrap() 193 | }, 194 | ca_certificate, 195 | ca_private_key, 196 | } 197 | } 198 | 199 | async fn insert(&self, certificate: SignedWithCaCert) { 200 | let mut cache = self.cache.lock().await; 201 | cache.insert(certificate); 202 | } 203 | 204 | pub async fn get(&self, authority: Authority) -> SignedWithCaCert { 205 | let mut cache = self.cache.lock().await; 206 | 207 | match cache.find(|cert| cert.authority == authority) { 208 | Some(certificate) => certificate.clone(), 209 | None => { 210 | // We release the previously acquired lock early as `insert`, which we will call just 211 | // afterwards also waits to acquire a lock. 212 | std::mem::drop(cache); 213 | 214 | let private_key = self.private_key.clone(); 215 | 216 | let ca_certificate = self.ca_certificate.clone(); 217 | let ca_private_key = self.ca_private_key.clone(); 218 | 219 | // This operation is somewhat CPU intensive and on some lower powered machines, 220 | // not running it inside of a thread pool may cause it to block the executor for too long. 221 | let certificate = tokio::task::spawn_blocking(move || { 222 | SignedWithCaCert::new(authority, private_key, ca_certificate, ca_private_key) 223 | }) 224 | .await 225 | .unwrap(); 226 | 227 | self.insert(certificate.clone()).await; 228 | certificate 229 | } 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /privaxy/src/server/events.rs: -------------------------------------------------------------------------------- 1 | use chrono::{DateTime, Utc}; 2 | use serde::Serialize; 3 | 4 | #[derive(Debug, Serialize, Clone)] 5 | pub struct Event { 6 | pub now: DateTime, 7 | pub method: String, 8 | pub url: String, 9 | pub is_request_blocked: bool, 10 | } 11 | -------------------------------------------------------------------------------- /privaxy/src/server/lib.rs: -------------------------------------------------------------------------------- 1 | use crate::blocker::AdblockRequester; 2 | use crate::events::Event; 3 | use crate::proxy::exclusions::LocalExclusionStore; 4 | use hyper::server::conn::AddrStream; 5 | use hyper::service::{make_service_fn, service_fn}; 6 | use hyper::{Client, Server}; 7 | use proxy::exclusions; 8 | use reqwest::redirect::Policy; 9 | use std::convert::Infallible; 10 | use std::net::SocketAddr; 11 | use std::sync::Arc; 12 | use std::thread; 13 | use std::time::Duration; 14 | use tokio::sync::broadcast; 15 | 16 | pub mod blocker; 17 | mod blocker_utils; 18 | mod ca; 19 | mod cert; 20 | pub mod configuration; 21 | pub mod events; 22 | mod proxy; 23 | pub mod statistics; 24 | 25 | #[derive(Debug, Clone)] 26 | pub struct PrivaxyServer { 27 | pub ca_certificate_pem: String, 28 | pub configuration_updater_sender: tokio::sync::mpsc::Sender, 29 | pub configuration_save_lock: Arc>, 30 | pub blocking_disabled_store: blocker::BlockingDisabledStore, 31 | pub statistics: statistics::Statistics, 32 | pub local_exclusion_store: exclusions::LocalExclusionStore, 33 | // A Sender is required to subscribe to broadcasted messages 34 | pub requests_broadcast_sender: broadcast::Sender, 35 | } 36 | 37 | pub async fn start_privaxy() -> PrivaxyServer { 38 | let ip = [127, 0, 0, 1]; 39 | 40 | // We use reqwest instead of hyper's client to perform most of the proxying as it's more convenient 41 | // to handle compression as well as offers a more convenient interface. 42 | let client = reqwest::Client::builder() 43 | .use_rustls_tls() 44 | .redirect(Policy::none()) 45 | .no_proxy() 46 | .gzip(true) 47 | .brotli(true) 48 | .deflate(true) 49 | .build() 50 | .unwrap(); 51 | 52 | let configuration = match configuration::Configuration::read_from_home(client.clone()).await { 53 | Ok(configuration) => configuration, 54 | Err(err) => { 55 | println!( 56 | "An error occured while trying to process the configuration file: {:?}", 57 | err 58 | ); 59 | std::process::exit(1) 60 | } 61 | }; 62 | 63 | let local_exclusion_store = 64 | LocalExclusionStore::new(Vec::from_iter(configuration.exclusions.clone().into_iter())); 65 | let local_exclusion_store_clone = local_exclusion_store.clone(); 66 | 67 | let ca_certificate = match configuration.ca_certificate() { 68 | Ok(ca_certificate) => ca_certificate, 69 | Err(err) => { 70 | println!("Unable to decode ca certificate: {:?}", err); 71 | std::process::exit(1) 72 | } 73 | }; 74 | 75 | let ca_certificate_pem = std::str::from_utf8(&ca_certificate.to_pem().unwrap()) 76 | .unwrap() 77 | .to_string(); 78 | 79 | let ca_private_key = match configuration.ca_private_key() { 80 | Ok(ca_private_key) => ca_private_key, 81 | Err(err) => { 82 | println!("Unable to decode ca private key: {:?}", err); 83 | std::process::exit(1) 84 | } 85 | }; 86 | 87 | let cert_cache = cert::CertCache::new(ca_certificate, ca_private_key); 88 | 89 | let statistics = statistics::Statistics::new(); 90 | let statistics_clone = statistics.clone(); 91 | 92 | let (broadcast_tx, _broadcast_rx) = broadcast::channel(32); 93 | let broadcast_tx_clone = broadcast_tx.clone(); 94 | 95 | let blocking_disabled_store = 96 | blocker::BlockingDisabledStore(Arc::new(std::sync::RwLock::new(false))); 97 | let blocking_disabled_store_clone = blocking_disabled_store.clone(); 98 | 99 | let (crossbeam_sender, crossbeam_receiver) = crossbeam_channel::unbounded(); 100 | let blocker_sender = crossbeam_sender.clone(); 101 | 102 | let blocker_requester = AdblockRequester::new(blocker_sender); 103 | 104 | let configuration_updater = configuration::ConfigurationUpdater::new( 105 | configuration.clone(), 106 | client.clone(), 107 | blocker_requester.clone(), 108 | None, 109 | ) 110 | .await; 111 | 112 | let configuration_updater_tx = configuration_updater.tx.clone(); 113 | configuration_updater_tx.send(configuration).await.unwrap(); 114 | 115 | configuration_updater.start(); 116 | 117 | thread::spawn(move || { 118 | let blocker = blocker::Blocker::new( 119 | crossbeam_sender, 120 | crossbeam_receiver, 121 | blocking_disabled_store, 122 | ); 123 | 124 | blocker.handle_requests() 125 | }); 126 | 127 | let https_connector = hyper_rustls::HttpsConnectorBuilder::new() 128 | .with_native_roots() 129 | .https_or_http() 130 | .enable_http1() 131 | .build(); 132 | 133 | // The hyper client is only used to perform upgrades. We don't need to 134 | // handle compression. 135 | // Hyper's client don't follow redirects, which is what we want, nothing to 136 | // disable here. 137 | let hyper_client = Client::builder().build(https_connector); 138 | 139 | let make_service = make_service_fn(move |conn: &AddrStream| { 140 | let client_ip_address = conn.remote_addr().ip(); 141 | 142 | let client = client.clone(); 143 | let hyper_client = hyper_client.clone(); 144 | let cert_cache = cert_cache.clone(); 145 | let blocker_requester = blocker_requester.clone(); 146 | let broadcast_tx = broadcast_tx.clone(); 147 | let statistics = statistics.clone(); 148 | let local_exclusion_store = local_exclusion_store.clone(); 149 | 150 | async move { 151 | Ok::<_, Infallible>(service_fn(move |req| { 152 | proxy::serve_mitm_session( 153 | blocker_requester.clone(), 154 | hyper_client.clone(), 155 | client.clone(), 156 | req, 157 | cert_cache.clone(), 158 | broadcast_tx.clone(), 159 | statistics.clone(), 160 | client_ip_address, 161 | local_exclusion_store.clone(), 162 | ) 163 | })) 164 | } 165 | }); 166 | 167 | let proxy_server_addr = SocketAddr::from((ip, 8100)); 168 | 169 | let server = Server::bind(&proxy_server_addr) 170 | .http1_preserve_header_case(true) 171 | .http1_title_case_headers(true) 172 | .tcp_keepalive(Some(Duration::from_secs(600))) 173 | .serve(make_service); 174 | 175 | tokio::spawn(server); 176 | 177 | log::info!("Proxy available at http://{}", proxy_server_addr); 178 | 179 | PrivaxyServer { 180 | ca_certificate_pem, 181 | configuration_updater_sender: configuration_updater_tx, 182 | configuration_save_lock: Arc::new(tokio::sync::Mutex::new(())), 183 | blocking_disabled_store: blocking_disabled_store_clone, 184 | statistics: statistics_clone, 185 | local_exclusion_store: local_exclusion_store_clone, 186 | requests_broadcast_sender: broadcast_tx_clone, 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /privaxy/src/server/main.rs: -------------------------------------------------------------------------------- 1 | use privaxy::start_privaxy; 2 | use std::time::Duration; 3 | 4 | const RUST_LOG_ENV_KEY: &str = "RUST_LOG"; 5 | 6 | #[tokio::main] 7 | async fn main() { 8 | if std::env::var(RUST_LOG_ENV_KEY).is_err() { 9 | std::env::set_var(RUST_LOG_ENV_KEY, "privaxy=info"); 10 | } 11 | 12 | env_logger::init(); 13 | 14 | start_privaxy().await; 15 | 16 | loop { 17 | tokio::time::sleep(Duration::from_secs(3600 * 24 * 30 * 365)).await 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /privaxy/src/server/proxy/exclusions.rs: -------------------------------------------------------------------------------- 1 | use lazy_static::lazy_static; 2 | use std::sync::{Arc, RwLock}; 3 | use wildmatch::WildMatch; 4 | 5 | #[derive(Debug, Clone)] 6 | struct WildMatchCollection(Vec); 7 | 8 | impl WildMatchCollection { 9 | fn new(patterns: Vec) -> Self { 10 | Self( 11 | patterns 12 | .into_iter() 13 | .map(|pattern| { 14 | // Making things case insensitive 15 | 16 | let pattern_lowercase = pattern.to_lowercase(); 17 | WildMatch::new(&pattern_lowercase) 18 | }) 19 | .collect(), 20 | ) 21 | } 22 | 23 | fn is_match(&self, element: &str) -> bool { 24 | // Making things case insensitive 25 | let lowercase_element = element.to_lowercase(); 26 | 27 | self.0 28 | .iter() 29 | .any(|pattern| pattern.matches(&lowercase_element)) 30 | } 31 | } 32 | 33 | lazy_static! { 34 | static ref DEFAULT_EXCLUSIONS: WildMatchCollection = { 35 | let mut exclusions = Vec::new(); 36 | 37 | // Apple service exclusions, as defined in : https://support.apple.com/en-us/HT210060 38 | // > Apple services will fail any connection that uses 39 | // > HTTPS Interception (SSL Inspection). If the HTTPS traffic 40 | // > traverses a web proxy, disable HTTPS Interception for the hosts 41 | // > listed in this article. 42 | exclusions.push(String::from("*.apple.com")); 43 | exclusions.push(String::from("static.ips.apple.com")); 44 | exclusions.push(String::from("*.push.apple.com")); 45 | exclusions.push(String::from("setup.icloud.com")); 46 | exclusions.push(String::from("*.business.apple.com")); 47 | exclusions.push(String::from("*.school.apple.com")); 48 | exclusions.push(String::from("upload.appleschoolcontent.com")); 49 | exclusions.push(String::from("ws-ee-maidsvc.icloud.com")); 50 | exclusions.push(String::from("itunes.com")); 51 | exclusions.push(String::from("appldnld.apple.com.edgesuite.net")); 52 | exclusions.push(String::from("*.itunes.apple.com")); 53 | exclusions.push(String::from("updates-http.cdn-apple.com")); 54 | exclusions.push(String::from("updates.cdn-apple.com")); 55 | exclusions.push(String::from("*.apps.apple.com")); 56 | exclusions.push(String::from("*.mzstatic.com")); 57 | exclusions.push(String::from("*.appattest.apple.com")); 58 | exclusions.push(String::from("doh.dns.apple.com")); 59 | exclusions.push(String::from("appleid.cdn-apple.com")); 60 | exclusions.push(String::from("*.apple-cloudkit.com")); 61 | exclusions.push(String::from("*.apple-livephotoskit.com")); 62 | exclusions.push(String::from("*.apzones.com")); 63 | exclusions.push(String::from("*.cdn-apple.com")); 64 | exclusions.push(String::from("*.gc.apple.com")); 65 | exclusions.push(String::from("*.icloud.com")); 66 | exclusions.push(String::from("*.icloud.com.cn")); 67 | exclusions.push(String::from("*.icloud.apple.com")); 68 | exclusions.push(String::from("*.icloud-content.com")); 69 | exclusions.push(String::from("*.iwork.apple.com")); 70 | exclusions.push(String::from("mask.icloud.com")); 71 | exclusions.push(String::from("mask-h2.icloud.com")); 72 | exclusions.push(String::from("mask-api.icloud.com")); 73 | exclusions.push(String::from("devimages-cdn.apple.com")); 74 | exclusions.push(String::from("download.developer.apple.com")); 75 | 76 | WildMatchCollection::new(exclusions) 77 | }; 78 | } 79 | 80 | #[derive(Debug, Clone)] 81 | pub struct LocalExclusionStore(Arc>); 82 | 83 | impl LocalExclusionStore { 84 | pub fn new(exclusions: Vec) -> Self { 85 | let collection = WildMatchCollection::new(exclusions); 86 | Self(Arc::new(RwLock::new(collection))) 87 | } 88 | 89 | pub fn replace_exclusions(&mut self, exclusions: Vec) { 90 | let new_exclusion_store = LocalExclusionStore::new(exclusions); 91 | 92 | *self.0.write().unwrap() = new_exclusion_store.0.read().unwrap().clone(); 93 | } 94 | 95 | pub fn contains(&self, element: &str) -> bool { 96 | if DEFAULT_EXCLUSIONS.is_match(element) { 97 | true 98 | } else { 99 | self.0.read().unwrap().is_match(element) 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /privaxy/src/server/proxy/html_rewriter.rs: -------------------------------------------------------------------------------- 1 | use crate::{blocker::AdblockRequester, statistics::Statistics}; 2 | use crossbeam_channel::Receiver; 3 | use hyper::body::Bytes; 4 | use lol_html::{element, HtmlRewriter, Settings}; 5 | use regex::Regex; 6 | use std::collections::HashSet; 7 | use std::fmt::Write; 8 | use tokio::sync; 9 | 10 | type InternalBodyChannel = ( 11 | sync::mpsc::UnboundedSender<(Bytes, Option)>, 12 | sync::mpsc::UnboundedReceiver<(Bytes, Option)>, 13 | ); 14 | 15 | struct AdblockProperties { 16 | url: String, 17 | ids: HashSet, 18 | classes: HashSet, 19 | } 20 | 21 | pub struct Rewriter { 22 | url: String, 23 | adblock_requester: AdblockRequester, 24 | receiver: Receiver, 25 | body_sender: hyper::body::Sender, 26 | statistics: Statistics, 27 | internal_body_channel: InternalBodyChannel, 28 | } 29 | 30 | impl Rewriter { 31 | pub(crate) fn new( 32 | url: String, 33 | adblock_requester: AdblockRequester, 34 | receiver: Receiver, 35 | body_sender: hyper::body::Sender, 36 | statistics: Statistics, 37 | ) -> Self { 38 | Self { 39 | url, 40 | body_sender, 41 | statistics, 42 | adblock_requester, 43 | receiver, 44 | internal_body_channel: sync::mpsc::unbounded_channel(), 45 | } 46 | } 47 | 48 | pub(crate) fn rewrite(self) { 49 | let (internal_body_sender, internal_body_receiver) = self.internal_body_channel; 50 | let body_sender = self.body_sender; 51 | let adblock_requester = self.adblock_requester.clone(); 52 | let statistics = self.statistics.clone(); 53 | 54 | let mut classes = HashSet::new(); 55 | let mut ids = HashSet::new(); 56 | 57 | tokio::spawn(Self::write_body( 58 | internal_body_receiver, 59 | body_sender, 60 | adblock_requester, 61 | statistics, 62 | )); 63 | 64 | let mut rewriter = HtmlRewriter::new( 65 | Settings { 66 | element_content_handlers: vec![ 67 | element!("*", |element| { 68 | let id = element.get_attribute("id"); 69 | 70 | if let Some(id) = id { 71 | ids.insert(id); 72 | } 73 | 74 | Ok(()) 75 | }), 76 | element!("*", |element| { 77 | let class = element.get_attribute("class"); 78 | 79 | if let Some(class) = class { 80 | let re = Regex::new(r"\s+").unwrap(); 81 | let classes_without_duplicate_spaces = re.replace_all(&class, " "); 82 | 83 | let class = classes_without_duplicate_spaces 84 | .split(' ') 85 | .map(|s| s.to_string()) 86 | .collect::>(); 87 | 88 | classes.extend(class); 89 | } 90 | 91 | Ok(()) 92 | }), 93 | // Let's discard of end html and body tag 94 | // to inject style and scripts before the implicit 95 | // close. 96 | element!("html, body", |element| { 97 | element 98 | .on_end_tag(|end| { 99 | end.remove(); 100 | Ok(()) 101 | }) 102 | .unwrap(); 103 | 104 | Ok(()) 105 | }), 106 | ], 107 | ..Settings::default() 108 | }, 109 | |c: &[u8]| { 110 | let _result = internal_body_sender.send((Bytes::copy_from_slice(c), None)); 111 | }, 112 | ); 113 | 114 | for message in self.receiver { 115 | rewriter.write(&message).unwrap(); 116 | } 117 | rewriter.end().unwrap(); 118 | 119 | let _result = internal_body_sender.send(( 120 | Bytes::new(), 121 | Some(AdblockProperties { 122 | ids, 123 | classes, 124 | url: self.url, 125 | }), 126 | )); 127 | } 128 | 129 | async fn write_body( 130 | mut receiver: sync::mpsc::UnboundedReceiver<(Bytes, Option)>, 131 | mut body_sender: hyper::body::Sender, 132 | adblock_requester: AdblockRequester, 133 | statistics: Statistics, 134 | ) { 135 | while let Some((bytes, adblock_properties)) = receiver.recv().await { 136 | if let Err(_err) = body_sender.send_data(bytes).await { 137 | break; 138 | } 139 | if let Some(adblock_properties) = adblock_properties { 140 | let mut response_has_been_modified = false; 141 | 142 | let blocker_result = adblock_requester 143 | .get_cosmetic_response( 144 | adblock_properties.url, 145 | Vec::from_iter(adblock_properties.ids.into_iter()), 146 | Vec::from_iter(adblock_properties.classes.into_iter()), 147 | ) 148 | .await; 149 | 150 | let mut to_append_to_response = format!( 151 | r#" 152 | 153 | 156 | "#, 157 | hidden_selectors = { 158 | // We insert one `display: none !important;` entry per selector 159 | // as otherwise, a single malformed selector would be breaking blocking. 160 | blocker_result 161 | .hidden_selectors 162 | .into_iter() 163 | .map(|selector| { 164 | format!( 165 | r#" 166 | {} 167 | {{ 168 | display: none !important; 169 | }} 170 | "#, 171 | selector 172 | ) 173 | }) 174 | .collect::() 175 | }, 176 | style_selectors = { 177 | let style_selectors = blocker_result.style_selectors; 178 | 179 | if !style_selectors.is_empty() { 180 | response_has_been_modified = true 181 | } 182 | 183 | style_selectors 184 | .into_iter() 185 | .map(|(selector, content)| { 186 | format!( 187 | "{selector} {{ {content} }}", 188 | selector = selector, 189 | content = content.join(";") 190 | ) 191 | }) 192 | .collect::() 193 | } 194 | ); 195 | 196 | if let Some(injected_script) = blocker_result.injected_script { 197 | response_has_been_modified = true; 198 | 199 | write!( 200 | to_append_to_response, 201 | r#" 202 | 203 | 204 | 205 | "#, 206 | injected_script 207 | ) 208 | .unwrap(); 209 | } 210 | 211 | if response_has_been_modified { 212 | statistics.increment_modified_responses(); 213 | } 214 | 215 | let bytes = Bytes::copy_from_slice(&to_append_to_response.into_bytes()); 216 | 217 | if let Err(_err) = body_sender.send_data(bytes).await { 218 | break; 219 | } 220 | } 221 | } 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /privaxy/src/server/proxy/mitm.rs: -------------------------------------------------------------------------------- 1 | use super::{exclusions::LocalExclusionStore, serve::serve}; 2 | use crate::{blocker::AdblockRequester, cert::CertCache, events::Event, statistics::Statistics}; 3 | use http::uri::{Authority, Scheme}; 4 | use hyper::{ 5 | client::HttpConnector, http, server::conn::Http, service::service_fn, upgrade::Upgraded, Body, 6 | Method, Request, Response, 7 | }; 8 | use hyper_rustls::HttpsConnector; 9 | use std::{net::IpAddr, sync::Arc}; 10 | use tokio::{net::TcpStream, sync::broadcast}; 11 | use tokio_rustls::TlsAcceptor; 12 | 13 | #[allow(clippy::too_many_arguments)] 14 | pub(crate) async fn serve_mitm_session( 15 | adblock_requester: AdblockRequester, 16 | hyper_client: hyper::Client>, 17 | client: reqwest::Client, 18 | req: Request, 19 | cert_cache: CertCache, 20 | broadcast_tx: broadcast::Sender, 21 | statistics: Statistics, 22 | client_ip_address: IpAddr, 23 | local_exclusion_store: LocalExclusionStore, 24 | ) -> Result, hyper::Error> { 25 | let authority = match req.uri().authority().cloned() { 26 | Some(authority) => authority, 27 | None => { 28 | let mut response = Response::new(Body::empty()); 29 | *response.status_mut() = http::StatusCode::BAD_REQUEST; 30 | 31 | log::warn!("Received a request without proper authority, sending bad request"); 32 | 33 | return Ok(response); 34 | } 35 | }; 36 | 37 | if Method::CONNECT == req.method() { 38 | // Received an HTTP request like: 39 | // ``` 40 | // CONNECT www.domain.com:443 HTTP/1.1 41 | // Host: www.domain.com:443 42 | // Proxy-Connection: Keep-Alive 43 | // ``` 44 | // 45 | // When HTTP method is CONNECT we should return an empty body 46 | // then we can eventually upgrade the connection and talk a new protocol. 47 | let server_configuration = 48 | Arc::new(cert_cache.get(authority.clone()).await.server_configuration); 49 | 50 | tokio::task::spawn(async move { 51 | match hyper::upgrade::on(req).await { 52 | Ok(mut upgraded) => { 53 | let is_host_blacklisted = local_exclusion_store.contains(authority.host()); 54 | 55 | if is_host_blacklisted { 56 | let _result = tunnel(&mut upgraded, &authority).await; 57 | 58 | return; 59 | } 60 | 61 | let http = Http::new(); 62 | 63 | match TlsAcceptor::from(server_configuration) 64 | .accept(upgraded) 65 | .await 66 | { 67 | Ok(tls_stream) => { 68 | let _result = http 69 | .serve_connection( 70 | tls_stream, 71 | service_fn(move |req| { 72 | serve( 73 | adblock_requester.clone(), 74 | req, 75 | hyper_client.clone(), 76 | client.clone(), 77 | authority.clone(), 78 | Scheme::HTTPS, 79 | broadcast_tx.clone(), 80 | statistics.clone(), 81 | client_ip_address, 82 | ) 83 | }), 84 | ) 85 | .with_upgrades() 86 | .await; 87 | } 88 | // Couldn't perform the tls handshake, they may only support TLS features that we don't or 89 | // make use of untrusted certificates. Let's add them to a blacklist so we'll be able to 90 | // tunnel them instead of trying to perform MITM. 91 | // No blocking will be able to be performed. 92 | Err(error) => { 93 | if error.kind() == std::io::ErrorKind::UnexpectedEof { 94 | log::warn!("Unable to perform handshake for host: {}. Consider excluding it from blocking. The service may not tolerate TLS interception.", authority); 95 | } 96 | } 97 | } 98 | } 99 | Err(e) => log::error!("upgrade error: {}", e), 100 | } 101 | }); 102 | 103 | Ok(Response::new(Body::empty())) 104 | } else { 105 | // The request is not of method `CONNECT`. Therefore, 106 | // this request is for an HTTP resource. 107 | serve( 108 | adblock_requester, 109 | req, 110 | hyper_client.clone(), 111 | client.clone(), 112 | authority, 113 | Scheme::HTTP, 114 | broadcast_tx, 115 | statistics, 116 | client_ip_address, 117 | ) 118 | .await 119 | } 120 | } 121 | 122 | async fn tunnel(mut upgraded: &mut Upgraded, authority: &Authority) -> std::io::Result<()> { 123 | let mut server = TcpStream::connect(authority.to_string()).await?; 124 | 125 | tokio::io::copy_bidirectional(&mut upgraded, &mut server).await?; 126 | 127 | log::debug!("Started tunneling host: {}", authority); 128 | 129 | Ok(()) 130 | } 131 | -------------------------------------------------------------------------------- /privaxy/src/server/proxy/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod mitm; 2 | pub(crate) mod serve; 3 | pub(crate) use mitm::serve_mitm_session; 4 | pub(crate) mod exclusions; 5 | pub(crate) mod html_rewriter; 6 | -------------------------------------------------------------------------------- /privaxy/src/server/proxy/serve.rs: -------------------------------------------------------------------------------- 1 | use super::html_rewriter::Rewriter; 2 | use crate::blocker::AdblockRequester; 3 | use crate::events::Event; 4 | use crate::statistics::Statistics; 5 | use adblock::blocker::BlockerResult; 6 | use http::uri::{Authority, Scheme}; 7 | use http::{StatusCode, Uri}; 8 | use hyper::body::Bytes; 9 | use hyper::client::HttpConnector; 10 | use hyper::{http, Body, Request, Response}; 11 | use hyper_rustls::HttpsConnector; 12 | use std::net::IpAddr; 13 | use tokio::sync::broadcast; 14 | 15 | #[allow(clippy::too_many_arguments)] 16 | pub(crate) async fn serve( 17 | adblock_requester: AdblockRequester, 18 | request: Request, 19 | hyper_client: hyper::Client>, 20 | client: reqwest::Client, 21 | authority: Authority, 22 | scheme: Scheme, 23 | broadcast_sender: broadcast::Sender, 24 | statistics: Statistics, 25 | client_ip_address: IpAddr, 26 | ) -> Result, hyper::Error> { 27 | let scheme_string = scheme.to_string(); 28 | 29 | let uri = match http::uri::Builder::new() 30 | .scheme(scheme) 31 | .authority(authority) 32 | .path_and_query(match request.uri().path_and_query() { 33 | Some(path_and_query) => path_and_query.as_str(), 34 | None => "/", 35 | }) 36 | .build() 37 | { 38 | Ok(uri) => uri, 39 | Err(_err) => { 40 | return Ok(get_empty_response(http::StatusCode::BAD_REQUEST)); 41 | } 42 | }; 43 | 44 | if request.headers().contains_key(http::header::UPGRADE) { 45 | return Ok(perform_two_ends_upgrade(request, uri, hyper_client).await); 46 | } 47 | 48 | let (mut parts, body) = request.into_parts(); 49 | parts.uri = uri.clone(); 50 | 51 | let (sender, new_body) = Body::channel(); 52 | 53 | let req = Request::from_parts(parts, body); 54 | 55 | log::debug!("{} {}", req.method(), req.uri()); 56 | 57 | statistics.increment_top_clients(client_ip_address); 58 | 59 | let (is_request_blocked, blocker_result) = adblock_requester 60 | .is_network_url_blocked( 61 | uri.to_string(), 62 | match req.headers().get(http::header::REFERER) { 63 | Some(referer) => referer.to_str().unwrap().to_string(), 64 | // When no referer, we default to `uri` as we otherwise may get many false 65 | // positives due to the blocker thinking it's third party requests. 66 | None => uri.to_string(), 67 | }, 68 | ) 69 | .await; 70 | 71 | let _result = broadcast_sender.send(Event { 72 | now: chrono::Utc::now(), 73 | method: req.method().to_string(), 74 | url: req.uri().to_string(), 75 | is_request_blocked, 76 | }); 77 | 78 | if is_request_blocked { 79 | statistics.increment_blocked_requests(); 80 | statistics.increment_top_blocked_paths(format!( 81 | "{}://{}{}", 82 | scheme_string, 83 | uri.host().unwrap(), 84 | uri.path() 85 | )); 86 | 87 | log::debug!("Blocked request: {}", uri); 88 | 89 | return Ok(get_blocked_by_privaxy_response(blocker_result)); 90 | } 91 | 92 | let mut new_response = Response::new(new_body); 93 | 94 | let mut request_headers = req.headers().clone(); 95 | request_headers.remove(http::header::CONNECTION); 96 | request_headers.remove(http::header::HOST); 97 | 98 | let mut response = match client 99 | .request(req.method().clone(), req.uri().to_string()) 100 | .headers(request_headers) 101 | .body(req.into_body()) 102 | .send() 103 | .await 104 | { 105 | Ok(response) => response, 106 | Err(err) => return Ok(get_informative_error_response(&err.to_string())), 107 | }; 108 | 109 | statistics.increment_proxied_requests(); 110 | 111 | *new_response.headers_mut() = response.headers().clone(); 112 | 113 | let (mut parts, new_new_body) = new_response.into_parts(); 114 | parts.status = response.status(); 115 | 116 | let new_response = Response::from_parts(parts, new_new_body); 117 | 118 | if let Some(content_type) = response.headers().get(http::header::CONTENT_TYPE) { 119 | if let Ok(value) = content_type.to_str() { 120 | if value.contains("text/html") { 121 | let (sender_rewriter, receiver_rewriter) = crossbeam_channel::unbounded::(); 122 | 123 | let rewriter = Rewriter::new( 124 | uri.to_string(), 125 | adblock_requester, 126 | receiver_rewriter, 127 | sender, 128 | statistics, 129 | ); 130 | 131 | tokio::task::spawn_blocking(|| rewriter.rewrite()); 132 | 133 | while let Ok(Some(chunk)) = response.chunk().await { 134 | if let Err(_err) = sender_rewriter.send(chunk) { 135 | break; 136 | } 137 | } 138 | 139 | return Ok(new_response); 140 | } 141 | } 142 | 143 | tokio::spawn(write_proxied_body(response, sender)); 144 | 145 | return Ok(new_response); 146 | } 147 | 148 | tokio::spawn(write_proxied_body(response, sender)); 149 | 150 | Ok(new_response) 151 | } 152 | 153 | fn get_informative_error_response(reason: &str) -> Response { 154 | let mut response_body = String::from(include_str!("../../resources/head.html")); 155 | response_body += 156 | &include_str!("../../resources/error.html").replace("#{request_error_reson}#", reason); 157 | 158 | let mut response = Response::new(Body::from(response_body)); 159 | *response.status_mut() = http::StatusCode::BAD_GATEWAY; 160 | 161 | response 162 | } 163 | 164 | fn get_blocked_by_privaxy_response(blocker_result: BlockerResult) -> Response { 165 | // We don't redirect to network urls due to security concerns. 166 | if let Some(resource) = blocker_result.redirect { 167 | let response = Response::new(Body::from(resource)); 168 | 169 | return response; 170 | } 171 | 172 | let filter_information = match blocker_result.filter { 173 | Some(filter) => filter, 174 | None => "No information".to_string(), 175 | }; 176 | 177 | let mut response_body = String::from(include_str!("../../resources/head.html")); 178 | response_body += &include_str!("../../resources/blocked_by_privaxy.html") 179 | .replace("#{matching_filter}#", &filter_information); 180 | 181 | let mut response = Response::new(Body::from(response_body)); 182 | *response.status_mut() = http::StatusCode::FORBIDDEN; 183 | 184 | response 185 | } 186 | 187 | fn get_empty_response(status_code: http::StatusCode) -> Response { 188 | let mut response = Response::new(Body::empty()); 189 | *response.status_mut() = status_code; 190 | 191 | response 192 | } 193 | 194 | async fn write_proxied_body(mut response: reqwest::Response, mut sender: hyper::body::Sender) { 195 | while let Ok(Some(chunk)) = response.chunk().await { 196 | // The other end is broken, let's abort immediately. 197 | if let Err(_err) = sender.send_data(chunk).await { 198 | break; 199 | } 200 | } 201 | } 202 | 203 | /// When we receive a request to perform an upgrade, we need to initiate a bidirectional tunnel. 204 | /// We upgrade the request towards the target server, towards the proxy end and we connect both through a duplex stream. 205 | async fn perform_two_ends_upgrade( 206 | request: Request, 207 | uri: Uri, 208 | hyper_client: hyper::Client>, 209 | ) -> Response { 210 | let (mut duplex_client, mut duplex_server) = tokio::io::duplex(32); 211 | 212 | let mut new_request = Request::new(Body::empty()); 213 | *new_request.headers_mut() = request.headers().clone(); 214 | *new_request.uri_mut() = uri; 215 | 216 | tokio::spawn(async move { 217 | match hyper::upgrade::on(request).await { 218 | Ok(mut upgraded_client) => { 219 | let _result = 220 | tokio::io::copy_bidirectional(&mut upgraded_client, &mut duplex_client).await; 221 | } 222 | Err(e) => { 223 | log::debug!("Unable to upgrade: {}", e) 224 | } 225 | } 226 | }); 227 | 228 | let response = match hyper_client.request(new_request).await { 229 | Ok(response) => response, 230 | Err(_err) => return get_empty_response(http::StatusCode::BAD_REQUEST), 231 | }; 232 | 233 | let mut new_response = get_empty_response(StatusCode::SWITCHING_PROTOCOLS); 234 | *new_response.headers_mut() = response.headers().clone(); 235 | 236 | match hyper::upgrade::on(response).await { 237 | Ok(mut upgraded_server) => { 238 | tokio::spawn(async move { 239 | let _result = 240 | tokio::io::copy_bidirectional(&mut upgraded_server, &mut duplex_server).await; 241 | }); 242 | } 243 | Err(e) => { 244 | log::debug!("Unable to upgrade: {}", e) 245 | } 246 | } 247 | 248 | new_response 249 | } 250 | -------------------------------------------------------------------------------- /privaxy/src/server/statistics.rs: -------------------------------------------------------------------------------- 1 | use serde::Serialize; 2 | use std::{ 3 | collections::HashMap, 4 | net::IpAddr, 5 | sync::{Arc, Mutex}, 6 | }; 7 | use uluru::LRUCache; 8 | 9 | const ENTRIES_PER_STATISTICS_TABLE: u8 = 50; 10 | 11 | #[derive(Debug, Serialize)] 12 | pub struct SerializableStatistics { 13 | pub proxied_requests: u64, 14 | pub blocked_requests: u64, 15 | pub modified_responses: u64, 16 | #[serde(with = "tuple_vec_map")] 17 | pub top_blocked_paths: Vec<(String, u64)>, 18 | #[serde(with = "tuple_vec_map")] 19 | pub top_clients: Vec<(String, u64)>, 20 | } 21 | 22 | #[derive(Debug, Clone)] 23 | pub struct Statistics { 24 | pub proxied_requests: Arc>, 25 | pub blocked_requests: Arc>, 26 | pub modified_responses: Arc>, 27 | pub top_blocked_paths: Arc>>, 28 | pub top_clients: Arc>>, 29 | } 30 | 31 | impl Default for Statistics { 32 | fn default() -> Self { 33 | Self::new() 34 | } 35 | } 36 | 37 | impl Statistics { 38 | pub fn new() -> Self { 39 | Self { 40 | proxied_requests: Arc::new(Mutex::new(0)), 41 | blocked_requests: Arc::new(Mutex::new(0)), 42 | modified_responses: Arc::new(Mutex::new(0)), 43 | top_blocked_paths: Arc::new(Mutex::new(LRUCache::default())), 44 | top_clients: Arc::new(Mutex::new(HashMap::new())), 45 | } 46 | } 47 | 48 | pub fn increment_top_blocked_paths(&self, path_: String) { 49 | let mut top_blocked_paths = self.top_blocked_paths.lock().unwrap(); 50 | 51 | match top_blocked_paths.find(|(path, _count)| path == &path_) { 52 | Some((_path, count)) => { 53 | *count += 1; 54 | } 55 | None => { 56 | top_blocked_paths.insert((path_, 1)); 57 | } 58 | } 59 | } 60 | 61 | pub fn increment_top_clients(&self, client: IpAddr) { 62 | *self.top_clients.lock().unwrap().entry(client).or_insert(0) += 1; 63 | } 64 | 65 | pub fn increment_proxied_requests(&self) -> u64 { 66 | let mut proxied_requests = self.proxied_requests.lock().unwrap(); 67 | 68 | *proxied_requests += 1; 69 | *proxied_requests 70 | } 71 | 72 | pub fn increment_blocked_requests(&self) -> u64 { 73 | let mut blocked_requests = self.blocked_requests.lock().unwrap(); 74 | 75 | *blocked_requests += 1; 76 | *blocked_requests 77 | } 78 | 79 | pub fn increment_modified_responses(&self) -> u64 { 80 | let mut modified_responses = self.modified_responses.lock().unwrap(); 81 | 82 | *modified_responses += 1; 83 | *modified_responses 84 | } 85 | 86 | pub fn get_serialized(&self) -> SerializableStatistics { 87 | SerializableStatistics { 88 | proxied_requests: *self.proxied_requests.lock().unwrap(), 89 | blocked_requests: *self.blocked_requests.lock().unwrap(), 90 | modified_responses: *self.modified_responses.lock().unwrap(), 91 | top_blocked_paths: { 92 | let top_blocked_paths = self.top_blocked_paths.lock().unwrap(); 93 | let mut top_blocked_paths_iterator = top_blocked_paths.iter(); 94 | 95 | let mut top_blocked_paths = (0..=ENTRIES_PER_STATISTICS_TABLE) 96 | .into_iter() 97 | .filter_map(|_| { 98 | let (path, count) = top_blocked_paths_iterator.next()?; 99 | 100 | Some((path.clone(), *count)) 101 | }) 102 | .collect::>(); 103 | 104 | top_blocked_paths.sort_by(|a, b| b.1.cmp(&a.1)); 105 | 106 | top_blocked_paths 107 | }, 108 | top_clients: { 109 | let top_clients = self.top_clients.lock().unwrap(); 110 | let mut top_clients_iter = top_clients.iter(); 111 | 112 | let mut top_clients = (0..=ENTRIES_PER_STATISTICS_TABLE) 113 | .into_iter() 114 | .filter_map(|_| { 115 | let (ipv4, count) = top_clients_iter.next()?; 116 | 117 | Some((ipv4.to_string(), *count)) 118 | }) 119 | .collect::>(); 120 | 121 | top_clients.sort_by(|a, b| b.1.cmp(&a.1)); 122 | 123 | top_clients 124 | }, 125 | } 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src-tauri/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | -------------------------------------------------------------------------------- /src-tauri/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "privaxy_app" 3 | version = "0.1.0" 4 | description = "A Tauri App" 5 | authors = ["you"] 6 | license = "" 7 | repository = "" 8 | default-run = "privaxy_app" 9 | edition = "2021" 10 | rust-version = "1.59" 11 | 12 | [build-dependencies] 13 | tauri-build = { version = "1.2.1", features = [] } 14 | 15 | [dependencies] 16 | serde_json = "1.0" 17 | log = "0.4.17" 18 | privaxy = { path = "../privaxy" } 19 | env_logger = "0.10.0" 20 | serde = { version = "1.0", features = ["derive"] } 21 | tauri = { version = "1.2.2", features = ["api-all", "system-tray"] } 22 | tokio = { version = "1.22.0", features = ["full"] } 23 | reqwest = "0.11.13" 24 | num-format = "0.4.0" 25 | 26 | [features] 27 | # by default Tauri runs in production mode 28 | # when `tauri dev` runs it is executed with `cargo run --no-default-features` if `devPath` is an URL 29 | default = ["custom-protocol"] 30 | # this feature is used for production builds where `devPath` points to the filesystem 31 | # DO NOT remove this 32 | custom-protocol = ["tauri/custom-protocol"] 33 | -------------------------------------------------------------------------------- /src-tauri/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | tauri_build::build() 3 | } 4 | -------------------------------------------------------------------------------- /src-tauri/icons/128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/src-tauri/icons/128x128.png -------------------------------------------------------------------------------- /src-tauri/icons/128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/src-tauri/icons/128x128@2x.png -------------------------------------------------------------------------------- /src-tauri/icons/32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/src-tauri/icons/32x32.png -------------------------------------------------------------------------------- /src-tauri/icons/Square107x107Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/src-tauri/icons/Square107x107Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square142x142Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/src-tauri/icons/Square142x142Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square150x150Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/src-tauri/icons/Square150x150Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square284x284Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/src-tauri/icons/Square284x284Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square30x30Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/src-tauri/icons/Square30x30Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square310x310Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/src-tauri/icons/Square310x310Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square44x44Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/src-tauri/icons/Square44x44Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square71x71Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/src-tauri/icons/Square71x71Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/Square89x89Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/src-tauri/icons/Square89x89Logo.png -------------------------------------------------------------------------------- /src-tauri/icons/StoreLogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/src-tauri/icons/StoreLogo.png -------------------------------------------------------------------------------- /src-tauri/icons/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/src-tauri/icons/icon.icns -------------------------------------------------------------------------------- /src-tauri/icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/src-tauri/icons/icon.ico -------------------------------------------------------------------------------- /src-tauri/icons/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/src-tauri/icons/icon.png -------------------------------------------------------------------------------- /src-tauri/icons/tray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Barre/privaxy/5dad688538bc7397d71d1c9cfd9d9d53bcf68032/src-tauri/icons/tray.png -------------------------------------------------------------------------------- /src-tauri/src/commands.rs: -------------------------------------------------------------------------------- 1 | use privaxy::configuration::{Configuration, Filter}; 2 | use privaxy::{statistics::SerializableStatistics, PrivaxyServer}; 3 | 4 | use crate::FilterStatusChangeRequest; 5 | 6 | #[tauri::command] 7 | pub(crate) fn get_statistics( 8 | privaxy_server: tauri::State<'_, PrivaxyServer>, 9 | ) -> Result { 10 | // https://github.com/tauri-apps/tauri/issues/2533 11 | Ok(privaxy_server.statistics.get_serialized()) 12 | } 13 | 14 | #[tauri::command] 15 | pub(crate) fn get_blocking_enabled(privaxy_server: tauri::State<'_, PrivaxyServer>) -> bool { 16 | !*privaxy_server.blocking_disabled_store.0.read().unwrap() 17 | } 18 | 19 | #[tauri::command] 20 | pub(crate) fn set_blocking_enabled(enabled: bool, privaxy_server: tauri::State<'_, PrivaxyServer>) { 21 | *privaxy_server.blocking_disabled_store.0.write().unwrap() = !enabled; 22 | } 23 | 24 | #[tauri::command] 25 | pub(crate) async fn get_custom_filters( 26 | http_client: tauri::State<'_, reqwest::Client>, 27 | ) -> Result { 28 | let configuration = match Configuration::read_from_home(http_client.inner().clone()).await { 29 | Ok(configuration) => configuration, 30 | Err(_) => return Err(()), 31 | }; 32 | 33 | let custom_filters = configuration.custom_filters.join("\n"); 34 | 35 | Ok(custom_filters) 36 | } 37 | 38 | #[tauri::command] 39 | pub(crate) async fn set_custom_filters( 40 | input: String, 41 | privaxy_server: tauri::State<'_, PrivaxyServer>, 42 | http_client: tauri::State<'_, reqwest::Client>, 43 | ) -> Result<(), ()> { 44 | let _guard = privaxy_server.configuration_save_lock.lock().await; 45 | 46 | let mut configuration = match Configuration::read_from_home(http_client.inner().clone()).await { 47 | Ok(configuration) => configuration, 48 | Err(_) => return Err(()), 49 | }; 50 | 51 | if configuration.set_custom_filters(&input).await.is_err() { 52 | return Err(()); 53 | } 54 | 55 | privaxy_server 56 | .configuration_updater_sender 57 | .send(configuration.clone()) 58 | .await 59 | .unwrap(); 60 | 61 | Ok(()) 62 | } 63 | 64 | #[tauri::command] 65 | pub(crate) async fn get_exclusions( 66 | http_client: tauri::State<'_, reqwest::Client>, 67 | ) -> Result { 68 | let configuration = match Configuration::read_from_home(http_client.inner().clone()).await { 69 | Ok(configuration) => configuration, 70 | Err(_) => return Err(()), 71 | }; 72 | let exclusions = Vec::from_iter(configuration.exclusions.into_iter()).join("\n"); 73 | 74 | Ok(exclusions) 75 | } 76 | 77 | #[tauri::command] 78 | pub(crate) async fn set_exclusions( 79 | input: String, 80 | privaxy_server: tauri::State<'_, PrivaxyServer>, 81 | http_client: tauri::State<'_, reqwest::Client>, 82 | ) -> Result<(), ()> { 83 | let _guard = privaxy_server.configuration_save_lock.lock().await; 84 | 85 | let mut configuration = match Configuration::read_from_home(http_client.inner().clone()).await { 86 | Ok(configuration) => configuration, 87 | Err(_) => return Err(()), 88 | }; 89 | 90 | if configuration 91 | .set_exclusions(&input, privaxy_server.local_exclusion_store.clone()) 92 | .await 93 | .is_err() 94 | { 95 | return Err(()); 96 | } 97 | 98 | privaxy_server 99 | .configuration_updater_sender 100 | .send(configuration.clone()) 101 | .await 102 | .unwrap(); 103 | 104 | Ok(()) 105 | } 106 | 107 | #[tauri::command] 108 | pub(crate) async fn get_filters_configuration( 109 | http_client: tauri::State<'_, reqwest::Client>, 110 | ) -> Result, ()> { 111 | let configuration = match Configuration::read_from_home(http_client.inner().clone()).await { 112 | Ok(configuration) => configuration, 113 | Err(_) => return Err(()), 114 | }; 115 | 116 | Ok(configuration.filters) 117 | } 118 | 119 | #[tauri::command] 120 | pub(crate) async fn change_filter_status( 121 | filter_status_change_request: Vec, 122 | privaxy_server: tauri::State<'_, PrivaxyServer>, 123 | http_client: tauri::State<'_, reqwest::Client>, 124 | ) -> Result, ()> { 125 | let _guard = privaxy_server.configuration_save_lock.lock().await; 126 | 127 | let mut configuration = match Configuration::read_from_home(http_client.inner().clone()).await { 128 | Ok(configuration) => configuration, 129 | Err(_) => return Err(()), 130 | }; 131 | 132 | for filter in filter_status_change_request { 133 | if configuration 134 | .set_filter_enabled_status(&filter.file_name, filter.enabled) 135 | .await 136 | .is_err() 137 | { 138 | return Err(()); 139 | } 140 | } 141 | 142 | privaxy_server 143 | .configuration_updater_sender 144 | .send(configuration.clone()) 145 | .await 146 | .unwrap(); 147 | 148 | Ok(configuration.filters) 149 | } 150 | -------------------------------------------------------------------------------- /src-tauri/src/main.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr( 2 | all(not(debug_assertions), target_os = "windows"), 3 | windows_subsystem = "windows" 4 | )] 5 | use num_format::{Locale, ToFormattedString}; 6 | use privaxy::events::Event; 7 | use privaxy::start_privaxy; 8 | use serde::Deserialize; 9 | use std::io::Write; 10 | use std::path::PathBuf; 11 | use tauri::{CustomMenuItem, SystemTray, SystemTrayEvent, SystemTrayMenu, Window}; 12 | use tauri::{Manager, SystemTrayMenuItem}; 13 | use tokio::sync::broadcast; 14 | use tokio::time; 15 | 16 | mod commands; 17 | 18 | const RUST_LOG_ENV_KEY: &str = "RUST_LOG"; 19 | 20 | #[derive(Debug, Deserialize)] 21 | struct SaveCertificatePayload(PathBuf); 22 | 23 | pub async fn stream_events(window: Window, events_sender: broadcast::Sender) { 24 | let mut events_receiver = events_sender.subscribe(); 25 | 26 | while let Ok(event) = events_receiver.recv().await { 27 | window.emit("logged_request", event).unwrap() 28 | } 29 | } 30 | 31 | #[derive(Debug, Deserialize)] 32 | pub struct FilterStatusChangeRequest { 33 | enabled: bool, 34 | file_name: String, 35 | } 36 | 37 | fn main() { 38 | if std::env::var(RUST_LOG_ENV_KEY).is_err() { 39 | std::env::set_var(RUST_LOG_ENV_KEY, "privaxy_app=info"); 40 | } 41 | 42 | env_logger::init(); 43 | 44 | let tokio_runtime = tokio::runtime::Builder::new_multi_thread() 45 | .enable_all() 46 | .build() 47 | .unwrap(); 48 | let _guard = tokio_runtime.enter(); 49 | 50 | let privaxy_server = tokio_runtime.block_on(async { start_privaxy().await }); 51 | let privaxy_server_clone = privaxy_server.clone(); 52 | let privaxy_ca_certificate = privaxy_server.ca_certificate_pem.clone(); 53 | 54 | let mut proxied_requests = CustomMenuItem::new("Proxied requests", "Proxied requests: 0"); 55 | proxied_requests.enabled = false; 56 | let mut blocked_requests = CustomMenuItem::new("Blocked requests", "Proxied requests: 0"); 57 | blocked_requests.enabled = false; 58 | let mut modified_responses = CustomMenuItem::new("Modified responses", "Modified responses: 0"); 59 | modified_responses.enabled = false; 60 | 61 | let open_app = CustomMenuItem::new("Open app".to_string(), "Open app"); 62 | let quit = CustomMenuItem::new("Quit".to_string(), "Quit"); 63 | let tray_menu = SystemTrayMenu::new() 64 | .add_item(open_app) 65 | .add_native_item(SystemTrayMenuItem::Separator) 66 | .add_item(proxied_requests) 67 | .add_item(blocked_requests) 68 | .add_item(modified_responses) 69 | .add_native_item(SystemTrayMenuItem::Separator) 70 | .add_item(quit); 71 | 72 | let broadcast_sender = privaxy_server.requests_broadcast_sender.clone(); 73 | 74 | tauri::Builder::default() 75 | .manage(privaxy_server) 76 | .manage(reqwest::Client::new()) 77 | .invoke_handler(tauri::generate_handler![ 78 | commands::get_statistics, 79 | commands::get_blocking_enabled, 80 | commands::set_blocking_enabled, 81 | commands::get_custom_filters, 82 | commands::set_custom_filters, 83 | commands::get_exclusions, 84 | commands::set_exclusions, 85 | commands::get_filters_configuration, 86 | commands::change_filter_status 87 | ]) 88 | .setup(move |app| { 89 | let main_window = app.get_window("main").unwrap(); 90 | 91 | let app_handle = app.handle(); 92 | 93 | tokio::spawn(async move { 94 | loop { 95 | let statistics = privaxy_server_clone.statistics.get_serialized(); 96 | 97 | let proxied_requests = app_handle.tray_handle().get_item("Proxied requests"); 98 | let blocked_requests = app_handle.tray_handle().get_item("Blocked requests"); 99 | let modified_responses = 100 | app_handle.tray_handle().get_item("Modified responses"); 101 | 102 | let _ = proxied_requests.set_title(format!( 103 | "Proxied requests: {}", 104 | statistics.proxied_requests.to_formatted_string(&Locale::en), 105 | )); 106 | let _ = blocked_requests.set_title(format!( 107 | "Blocked requests: {}", 108 | statistics.blocked_requests.to_formatted_string(&Locale::en), 109 | )); 110 | let _ = modified_responses.set_title(format!( 111 | "Modified responses: {}", 112 | statistics 113 | .modified_responses 114 | .to_formatted_string(&Locale::en), 115 | )); 116 | 117 | time::sleep(std::time::Duration::from_millis(400)).await; 118 | } 119 | }); 120 | 121 | tokio::spawn(async { stream_events(main_window, broadcast_sender).await }); 122 | 123 | let _ = app.listen_global("save_ca_file", move |event| { 124 | if let Some(path) = event.payload() { 125 | let path = serde_json::from_str::(path) 126 | .unwrap() 127 | .0; 128 | 129 | let mut file = match std::fs::File::create(path) { 130 | Ok(file) => file, 131 | Err(err) => { 132 | log::error!("Unable to save ca file: {:?}", err); 133 | return; 134 | } 135 | }; 136 | if let Err(err) = file.write_all(privaxy_ca_certificate.as_bytes()) { 137 | log::error!("Unable to write ca file: {:?}", err) 138 | } 139 | } 140 | }); 141 | 142 | #[cfg(target_os = "macos")] 143 | app.set_activation_policy(tauri::ActivationPolicy::Accessory); 144 | 145 | Ok(()) 146 | }) 147 | .system_tray(SystemTray::new().with_menu(tray_menu)) 148 | .on_window_event(|event| { 149 | if let tauri::WindowEvent::CloseRequested { api, .. } = event.event() { 150 | event.window().hide().unwrap(); 151 | api.prevent_close(); 152 | } 153 | }) 154 | .on_system_tray_event(|app, event| { 155 | if let SystemTrayEvent::MenuItemClick { id, .. } = event { 156 | match id.as_str() { 157 | "Quit" => { 158 | std::process::exit(0); 159 | } 160 | "Open app" => { 161 | let window = app.get_window("main").unwrap(); 162 | window.show().unwrap(); 163 | window.set_focus().unwrap(); 164 | } 165 | _ => {} 166 | } 167 | } 168 | }) 169 | .run(tauri::generate_context!()) 170 | .expect("error while running privaxy"); 171 | } 172 | -------------------------------------------------------------------------------- /src-tauri/tauri.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "build": { 3 | "beforeBuildCommand": "cd web_frontend && trunk build", 4 | "beforeDevCommand": "cd web_frontend && trunk serve", 5 | "devPath": "http://127.0.0.1:8080", 6 | "distDir": "../web_frontend/dist" 7 | }, 8 | "package": { 9 | "productName": "Privaxy", 10 | "version": "0.5.2" 11 | }, 12 | "tauri": { 13 | "allowlist": { 14 | "all": true 15 | }, 16 | "systemTray": { 17 | "iconPath": "icons/tray.png", 18 | "iconAsTemplate": true 19 | }, 20 | "bundle": { 21 | "active": true, 22 | "category": "DeveloperTool", 23 | "copyright": "", 24 | "deb": { 25 | "depends": [] 26 | }, 27 | "externalBin": [], 28 | "icon": [ 29 | "icons/32x32.png", 30 | "icons/128x128.png", 31 | "icons/128x128@2x.png", 32 | "icons/icon.icns", 33 | "icons/icon.ico" 34 | ], 35 | "identifier": "net.privaxy", 36 | "longDescription": "", 37 | "macOS": { 38 | "entitlements": null, 39 | "exceptionDomain": "", 40 | "frameworks": [], 41 | "providerShortName": null, 42 | "signingIdentity": null 43 | }, 44 | "resources": [], 45 | "shortDescription": "", 46 | "targets": "all", 47 | "windows": { 48 | "certificateThumbprint": null, 49 | "digestAlgorithm": "sha256", 50 | "timestampUrl": "" 51 | } 52 | }, 53 | "security": { 54 | "csp": null 55 | }, 56 | "updater": { 57 | "active": false 58 | }, 59 | "windows": [ 60 | { 61 | "fullscreen": false, 62 | "minHeight": 600, 63 | "resizable": true, 64 | "title": "Privaxy", 65 | "width": 800, 66 | "minWidth": 800 67 | } 68 | ] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /web_frontend/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "web_frontend" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | 7 | [dependencies] 8 | yew = "0.19.3" 9 | yew-router = "0.16.0" 10 | serde_json = "1.0.81" 11 | tauri-sys = { git = "https://github.com/JonasKruckenberg/tauri-sys", rev = "0c864ee8c2b7d2eb44add60ca7973c9f2454f899", features = [ 12 | "dialog", 13 | "event", 14 | "tauri", 15 | ] } 16 | num-format = "0.4.0" 17 | serde = { version = "1.0.137", features = ["derive"] } 18 | gloo-utils = "0.1.3" 19 | wasm-bindgen = "0.2.80" 20 | wasm-bindgen-futures = "0.4.30" 21 | futures = "0.3.21" 22 | log = "0.4.17" 23 | wasm-logger = "0.2.0" 24 | gloo-timers = { version = "0.2.4", features = ["futures"] } 25 | serde-tuple-vec-map = "1.0.1" 26 | web-sys = { version = "0.3.60", features = [ 27 | "Document", 28 | "HtmlHeadElement", 29 | "Element", 30 | "NamedNodeMap", 31 | ] } 32 | -------------------------------------------------------------------------------- /web_frontend/Trunk.toml: -------------------------------------------------------------------------------- 1 | [build] 2 | target = "index.html" 3 | dist = "dist" 4 | 5 | [[hooks]] 6 | stage = "pre_build" 7 | command = "sh" 8 | command_arguments = [ 9 | "-c", 10 | "npx tailwindcss build -i src/tailwind.css -o dist/.stage/tailwind.css", 11 | ] 12 | -------------------------------------------------------------------------------- /web_frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Privaxy 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /web_frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "devDependencies": { 3 | "@tailwindcss/forms": "^0.4.0", 4 | "autoprefixer": "^10.4.0", 5 | "postcss": "^8.4.5", 6 | "tailwindcss": "^3.0.7" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /web_frontend/src/blocking_enabled.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use tauri_sys::tauri; 3 | use wasm_bindgen_futures::spawn_local; 4 | use yew::{classes, html, Component, Context, Html}; 5 | 6 | #[derive(Deserialize, Serialize)] 7 | struct TauriBlockingEnabledArg { 8 | enabled: bool, 9 | } 10 | 11 | pub struct BlockingEnabled { 12 | blocking_enabled: bool, 13 | } 14 | 15 | #[derive(Debug)] 16 | pub enum Message { 17 | EnableBlocking, 18 | DisableBlocking, 19 | BlockingEnabled, 20 | BlockingDisabled, 21 | SetCurrentBlockingState, 22 | } 23 | 24 | impl Component for BlockingEnabled { 25 | type Message = Message; 26 | type Properties = (); 27 | 28 | fn create(ctx: &Context) -> Self { 29 | ctx.link().send_message(Message::SetCurrentBlockingState); 30 | 31 | Self { 32 | blocking_enabled: true, 33 | } 34 | } 35 | 36 | fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { 37 | let message_callback = ctx.link().callback(|message: Message| message); 38 | log::error!("{:?}", &msg); 39 | match msg { 40 | Message::EnableBlocking => { 41 | spawn_local(async move { 42 | match tauri::invoke::<_, ()>( 43 | "set_blocking_enabled", 44 | &TauriBlockingEnabledArg { enabled: true }, 45 | ) 46 | .await 47 | { 48 | Ok(_) => { 49 | message_callback.emit(Message::BlockingEnabled); 50 | } 51 | Err(err) => { 52 | log::error!("{:?}", err); 53 | message_callback.emit(Message::BlockingDisabled) 54 | } 55 | } 56 | }); 57 | } 58 | Message::DisableBlocking => { 59 | spawn_local(async move { 60 | match tauri::invoke::<_, ()>( 61 | "set_blocking_enabled", 62 | &TauriBlockingEnabledArg { enabled: false }, 63 | ) 64 | .await 65 | { 66 | Ok(_) => { 67 | message_callback.emit(Message::BlockingDisabled); 68 | } 69 | Err(err) => { 70 | log::error!("{:?}", err); 71 | message_callback.emit(Message::BlockingEnabled) 72 | } 73 | } 74 | }); 75 | } 76 | Message::BlockingEnabled => { 77 | self.blocking_enabled = true; 78 | } 79 | Message::BlockingDisabled => { 80 | self.blocking_enabled = false; 81 | } 82 | Message::SetCurrentBlockingState => { 83 | spawn_local(async move { 84 | match tauri::invoke::<_, bool>("get_blocking_enabled", &()).await { 85 | Ok(bool_) => { 86 | if bool_ { 87 | message_callback.emit(Message::BlockingEnabled); 88 | } else { 89 | message_callback.emit(Message::BlockingDisabled); 90 | } 91 | } 92 | Err(err) => { 93 | log::error!("{:?}", err); 94 | } 95 | } 96 | }); 97 | } 98 | } 99 | 100 | true 101 | } 102 | 103 | fn view(&self, ctx: &Context) -> Html { 104 | let enable_blocking = ctx.link().callback(|_| Message::EnableBlocking); 105 | let disable_blocking = ctx.link().callback(|_| Message::DisableBlocking); 106 | 107 | let button_classes = classes!( 108 | "inline-flex", 109 | "items-center", 110 | "justify-center", 111 | "px-4", 112 | "py-2", 113 | "border", 114 | "transition", 115 | "ease-in-out", 116 | "duration-150", 117 | "border-transparent", 118 | "text-sm", 119 | "text-sm", 120 | "font-medium", 121 | "rounded-md", 122 | "shadow-sm", 123 | "text-white", 124 | "focus:outline-none", 125 | "focus:ring-2", 126 | "focus:ring-offset-2", 127 | "focus:ring-offset-gray-100", 128 | ); 129 | 130 | if self.blocking_enabled { 131 | html! { 132 | 141 | } 142 | } else { 143 | html! { 144 | 155 | } 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /web_frontend/src/dashboard.rs: -------------------------------------------------------------------------------- 1 | use crate::blocking_enabled::BlockingEnabled; 2 | use crate::save_ca_certificate::SaveCaCertificate; 3 | use futures::future::{AbortHandle, Abortable}; 4 | use gloo_timers::future::TimeoutFuture; 5 | use num_format::{Locale, ToFormattedString}; 6 | use serde::Deserialize; 7 | use tauri_sys::tauri; 8 | use wasm_bindgen_futures::spawn_local; 9 | use yew::{html, Component, Context, Html}; 10 | 11 | #[derive(Debug, Deserialize, PartialEq, Eq)] 12 | pub struct Message { 13 | proxied_requests: Option, 14 | blocked_requests: Option, 15 | modified_responses: Option, 16 | #[serde(with = "tuple_vec_map")] 17 | top_blocked_paths: Vec<(String, u64)>, 18 | #[serde(with = "tuple_vec_map")] 19 | top_clients: Vec<(String, u64)>, 20 | } 21 | 22 | pub struct Dashboard { 23 | message: Message, 24 | abort_handle: AbortHandle, 25 | } 26 | 27 | impl Component for Dashboard { 28 | type Message = Message; 29 | type Properties = (); 30 | 31 | fn create(ctx: &Context) -> Self { 32 | let message_callback = ctx.link().callback(|message: Message| message); 33 | 34 | let (abort_handle, abort_registration) = AbortHandle::new_pair(); 35 | let future = Abortable::new( 36 | async move { 37 | loop { 38 | let mut message: Message = tauri::invoke("get_statistics", &()).await.unwrap(); 39 | 40 | // Invoke seems to reshuffle the data? 41 | message.top_clients.sort_by(|a, b| b.1.cmp(&a.1)); 42 | message.top_blocked_paths.sort_by(|a, b| b.1.cmp(&a.1)); 43 | 44 | message_callback.emit(message); 45 | 46 | TimeoutFuture::new(200).await; 47 | } 48 | }, 49 | abort_registration, 50 | ); 51 | 52 | spawn_local(async { 53 | let _result = future.await; 54 | }); 55 | 56 | Self { 57 | abort_handle, 58 | message: Message { 59 | proxied_requests: None, 60 | blocked_requests: None, 61 | modified_responses: None, 62 | top_blocked_paths: Vec::new(), 63 | top_clients: Vec::new(), 64 | }, 65 | } 66 | } 67 | 68 | fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { 69 | let update = self.message != msg; 70 | 71 | self.message = msg; 72 | update 73 | } 74 | 75 | fn view(&self, _ctx: &Context) -> Html { 76 | fn some_or_loading(s: Option) -> String { 77 | match s { 78 | Some(s) => s.to_formatted_string(&Locale::en), 79 | None => "Loading".to_string(), 80 | } 81 | } 82 | 83 | fn render_list_element(key: &str, count: u64) -> Html { 84 | html! { 85 |
  • 86 |
    87 |
    88 | 89 |

    { key }

    90 |
    91 |
    { count.to_formatted_string(&Locale::en) }
    92 |
    93 |
  • 94 | } 95 | } 96 | 97 | html! { 98 | <> 99 |
    100 |
    101 |

    { "Dashboard" }
    103 |

    104 |
    105 |
    107 | 108 | 109 |
    110 |
    111 | 112 |
    114 |
    115 |
    116 | {"Proxied requests"} 117 |
    118 |
    119 |
    120 | { some_or_loading(self.message.proxied_requests) } 121 |
    122 |
    123 |
    124 | 125 |
    126 |
    127 | {"Blocked requests"} 128 |
    129 |
    130 |
    131 | { some_or_loading(self.message.blocked_requests) } 132 |
    133 |
    134 |
    135 | 136 |
    137 |
    138 | {"Modified responses"} 139 |
    140 |
    141 |
    142 | { some_or_loading(self.message.modified_responses) } 143 |
    144 |
    145 |
    146 |
    147 |
    148 |
    149 |
    150 |

    {"Top blocked paths"}

    151 |
    152 |
    153 |
      154 | { for self.message.top_blocked_paths.iter().map(|(path, 155 | count)|render_list_element(path, *count)) } 156 |
    157 | 158 |
    159 |
    160 |
    161 |
    162 |

    {"Top clients"}

    163 |
    164 |
    165 |
      166 | { for self.message.top_clients.iter().map(|(client, 167 | count)|render_list_element(client, *count)) } 168 |
    169 |
    170 |
    171 |
    172 | 173 | } 174 | } 175 | 176 | fn destroy(&mut self, _ctx: &Context) { 177 | self.abort_handle.abort() 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /web_frontend/src/main.rs: -------------------------------------------------------------------------------- 1 | use yew::functional::*; 2 | use yew::prelude::*; 3 | use yew_router::prelude::*; 4 | 5 | mod blocking_enabled; 6 | mod dashboard; 7 | mod filters; 8 | mod requests; 9 | mod save_button; 10 | mod save_ca_certificate; 11 | mod settings; 12 | mod settings_textarea; 13 | mod submit_banner; 14 | 15 | #[derive(Debug, Clone, Copy, PartialEq, Routable)] 16 | enum Route { 17 | #[at("/")] 18 | Dashboard, 19 | #[at("/requests")] 20 | Requests, 21 | #[at("/settings/:s")] 22 | Settings, 23 | #[not_found] 24 | #[at("/404")] 25 | NotFound, 26 | } 27 | 28 | #[function_component(NotFound)] 29 | fn not_found() -> Html { 30 | html! { 31 |
    32 |
    33 |
    34 |

    {"404"}

    35 |
    36 |
    37 |

    {"Page not 38 | found"}

    39 |

    {"Please check the URL in the address bar and try 40 | again."}

    41 |
    42 |
    43 |
    44 |
    45 |
    46 | } 47 | } 48 | 49 | fn switch(route: &Route) -> Html { 50 | fn get_classes(current_route: Route, for_route_link: Route) -> Classes { 51 | if current_route == for_route_link { 52 | classes!( 53 | "bg-gray-900", 54 | "text-white", 55 | "px-3", 56 | "py-2", 57 | "rounded-md", 58 | "text-sm", 59 | "font-medium" 60 | ) 61 | } else { 62 | classes!( 63 | "text-gray-300", 64 | "hover:bg-gray-700", 65 | "hover:text-white", 66 | "px-3", 67 | "py-2", 68 | "rounded-md", 69 | "text-sm", 70 | "font-medium" 71 | ) 72 | } 73 | } 74 | 75 | let navigation = html! { 76 | }; 92 | 93 | match route { 94 | Route::Dashboard => { 95 | set_title("Dashboard"); 96 | 97 | html! { <>{navigation}
    } 98 | } 99 | Route::Requests => { 100 | set_title("Requests"); 101 | html! { <>{navigation}
    } 102 | } 103 | Route::Settings => { 104 | html! {<>{navigation}
    render={Switch::render(settings::switch_settings)} />
    } 105 | } 106 | Route::NotFound => { 107 | set_title("Not Found"); 108 | html! { <>{navigation} } 109 | } 110 | } 111 | } 112 | 113 | #[function_component(App)] 114 | fn app() -> Html { 115 | html! { 116 | 117 | render={Switch::render(switch)} /> 118 | 119 | } 120 | } 121 | 122 | fn set_title(title: &str) { 123 | gloo_utils::document().set_title(&format!("{} | Privaxy", title)); 124 | } 125 | 126 | fn main() { 127 | wasm_logger::init(wasm_logger::Config::default()); 128 | 129 | yew::start_app::(); 130 | } 131 | -------------------------------------------------------------------------------- /web_frontend/src/requests.rs: -------------------------------------------------------------------------------- 1 | use futures::future::{AbortHandle, Abortable}; 2 | use futures::StreamExt; 3 | use serde::Deserialize; 4 | use tauri_sys::event; 5 | use wasm_bindgen_futures::spawn_local; 6 | use yew::{html, Component, Context, Html}; 7 | 8 | const MAX_REQUESTS_SHOWN: usize = 500; 9 | 10 | #[derive(Deserialize)] 11 | pub struct Message { 12 | now: String, 13 | method: String, 14 | url: String, 15 | is_request_blocked: bool, 16 | } 17 | 18 | pub struct Requests { 19 | messages: Vec, 20 | abort_handle: AbortHandle, 21 | } 22 | 23 | impl Component for Requests { 24 | type Message = Message; 25 | type Properties = (); 26 | 27 | fn create(ctx: &Context) -> Self { 28 | let message_callback = ctx.link().callback(|message: Message| message); 29 | 30 | let (abort_handle, abort_registration) = AbortHandle::new_pair(); 31 | let future = Abortable::new( 32 | async move { 33 | let mut events = event::listen::("logged_request").await.unwrap(); 34 | while let Some(event) = events.next().await { 35 | message_callback.emit(event.payload); 36 | } 37 | }, 38 | abort_registration, 39 | ); 40 | 41 | spawn_local(async { 42 | let _result = future.await; 43 | }); 44 | 45 | Self { 46 | abort_handle, 47 | messages: Vec::new(), 48 | } 49 | } 50 | 51 | fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { 52 | self.messages.insert(0, msg); 53 | 54 | self.messages.truncate(MAX_REQUESTS_SHOWN); 55 | 56 | // The server only sends new messages when there is actually 57 | // new data. 58 | true 59 | } 60 | 61 | fn view(&self, _ctx: &Context) -> Html { 62 | fn render_element(element: &Message) -> Html { 63 | let background = { 64 | if element.is_request_blocked { 65 | "bg-red-50" 66 | } else { 67 | "" 68 | } 69 | }; 70 | 71 | html! { 72 | 73 | 74 | 75 | {&element.now} 76 | 77 | 78 | 80 | {&element.method} 81 | 82 | 83 | 84 | {&element.url} 85 | 86 | 87 | } 88 | } 89 | 90 | html! { 91 | <> 92 |

    93 | {"Requests feed"} 94 |
    95 |

    96 |
    97 |
    98 |
    99 |
    100 | 101 | 102 | 103 | 107 | 111 | 115 | 116 | 117 | 118 | { for self.messages.iter().map(render_element) } 119 | 120 |
    105 | {"Timestamp"} 106 | 109 | {"Method"} 110 | 113 | {"Path"} 114 |
    121 |
    122 |
    123 |
    124 |
    125 | 126 | 127 | } 128 | } 129 | 130 | fn destroy(&mut self, _ctx: &Context) { 131 | self.abort_handle.abort() 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /web_frontend/src/resources/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /web_frontend/src/save_button.rs: -------------------------------------------------------------------------------- 1 | use web_sys::MouseEvent; 2 | use yew::{classes, html, Callback, Component, Context, Html, Properties}; 3 | 4 | #[derive(PartialEq, Eq)] 5 | pub enum SaveButtonState { 6 | Loading, 7 | Enabled, 8 | Disabled, 9 | } 10 | 11 | #[derive(Properties, PartialEq)] 12 | pub struct Props { 13 | pub state: SaveButtonState, 14 | pub onclick: Callback, 15 | } 16 | 17 | pub struct SaveButton; 18 | 19 | impl Component for SaveButton { 20 | type Message = (); 21 | type Properties = Props; 22 | 23 | fn create(_ctx: &Context) -> Self { 24 | Self 25 | } 26 | 27 | fn view(&self, ctx: &Context) -> Html { 28 | let mut save_button_classes = classes!( 29 | "inline-flex", 30 | "items-center", 31 | "justify-center", 32 | "focus:ring-blue-500", 33 | "bg-blue-600", 34 | "hover:bg-blue-700", 35 | "px-4", 36 | "py-2", 37 | "border", 38 | "transition", 39 | "ease-in-out", 40 | "duration-150", 41 | "border-transparent", 42 | "text-sm", 43 | "text-sm", 44 | "font-medium", 45 | "rounded-md", 46 | "shadow-sm", 47 | "text-white", 48 | "focus:outline-none", 49 | "focus:ring-2", 50 | "focus:ring-offset-2", 51 | "focus:ring-offset-gray-100", 52 | ); 53 | 54 | let properties = ctx.props(); 55 | 56 | if properties.state == SaveButtonState::Disabled 57 | || properties.state == SaveButtonState::Loading 58 | { 59 | save_button_classes.push("opacity-50"); 60 | save_button_classes.push("cursor-not-allowed"); 61 | } 62 | 63 | let button_text = if properties.state == SaveButtonState::Loading { 64 | "Loading..." 65 | } else { 66 | "Save changes" 67 | }; 68 | 69 | html! { 70 | 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /web_frontend/src/save_ca_certificate.rs: -------------------------------------------------------------------------------- 1 | use std::path::{Path, PathBuf}; 2 | 3 | use serde::Serialize; 4 | use tauri_sys::dialog::FileDialogBuilder; 5 | use tauri_sys::event::emit; 6 | use wasm_bindgen_futures::spawn_local; 7 | use yew::{html, Component, Context, Html}; 8 | 9 | #[derive(Serialize)] 10 | struct SaveCertificatePayload(PathBuf); 11 | 12 | pub struct SaveCaCertificate; 13 | 14 | pub enum Message { 15 | SaveCaCertificate, 16 | } 17 | 18 | impl Component for SaveCaCertificate { 19 | type Message = Message; 20 | type Properties = (); 21 | 22 | fn create(_ctx: &Context) -> Self { 23 | Self {} 24 | } 25 | 26 | fn update(&mut self, _ctx: &Context, msg: Self::Message) -> bool { 27 | match msg { 28 | Message::SaveCaCertificate => { 29 | spawn_local(async move { 30 | let path = FileDialogBuilder::new() 31 | .add_filter("privaxy_ca_cert", &["pem"]) 32 | .set_default_path(Path::new("privaxy_ca_cert.pem")) 33 | .save() 34 | .await 35 | .unwrap(); 36 | if let Some(path) = path { 37 | let _ = emit("save_ca_file", &SaveCertificatePayload(path)).await; 38 | } 39 | }); 40 | } 41 | } 42 | 43 | false 44 | } 45 | 46 | fn view(&self, ctx: &Context) -> Html { 47 | let save_ca_certificate = ctx.link().callback(|_| Message::SaveCaCertificate); 48 | 49 | html! { 50 | 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /web_frontend/src/settings.rs: -------------------------------------------------------------------------------- 1 | use crate::filters::Filters; 2 | use crate::set_title; 3 | use crate::settings_textarea::SettingsTextarea; 4 | use yew::prelude::*; 5 | use yew::{html, Html}; 6 | use yew_router::prelude::*; 7 | 8 | #[derive(Clone, Copy, Routable, PartialEq, Eq)] 9 | pub enum SettingsRoute { 10 | #[at("/settings/filters")] 11 | Filters, 12 | #[at("/settings/exclusions")] 13 | Exclusions, 14 | #[at("/settings/custom-filters")] 15 | CustomFilters, 16 | } 17 | 18 | pub fn switch_settings(route: &SettingsRoute) -> Html { 19 | fn get_classes(current_route: SettingsRoute, for_route_link: SettingsRoute) -> Classes { 20 | if current_route == for_route_link { 21 | classes!( 22 | "bg-gray-100", 23 | "text-gray-900", 24 | "flex", 25 | "items-center", 26 | "px-3", 27 | "py-2", 28 | "text-sm", 29 | "font-medium", 30 | "rounded-md" 31 | ) 32 | } else { 33 | classes!( 34 | "text-gray-600", 35 | "hover:bg-gray-50", 36 | "hover:text-gray-900", 37 | "flex", 38 | "items-center", 39 | "px-3", 40 | "py-2", 41 | "text-sm", 42 | "font-medium", 43 | "rounded-md" 44 | ) 45 | } 46 | } 47 | 48 | let content = match route { 49 | SettingsRoute::Filters => { 50 | set_title("Settings - Filters"); 51 | 52 | html! { } 53 | } 54 | SettingsRoute::Exclusions => { 55 | set_title("Settings - Exclusions"); 56 | 57 | let description = html! {
    58 |

    59 | {"Exclusions are hosts or domains that are not passed through the MITM pipeline. "} 60 | {"Excluded entries will be transparently tunneled."} 61 |

    62 |
    63 |

    {"?"}{" matches exactly one occurrence of any character."}

    64 |

    {"*"}{" matches arbitrary many (including zero) occurrences of any character."}

    65 |
    66 | }; 67 | let textarea_description = "Insert one entry per line"; 68 | let set_resource_name = "set_exclusions"; 69 | let get_resource_name = "get_exclusions"; 70 | 71 | html! {} 72 | } 73 | SettingsRoute::CustomFilters => { 74 | set_title("Settings - Custom Filters"); 75 | 76 | let description = html! { 77 |

    78 | {"Insert EasyList compatible filters. Comment filters by prefixing lines with "} {"!"}{"."} 79 |

    80 | }; 81 | 82 | let textarea_description = "Insert one filter per line"; 83 | let set_resource_name = "set_custom_filters".to_string(); 84 | let get_resource_name = "get_custom_filters"; 85 | 86 | html! {} 87 | } 88 | }; 89 | 90 | html! {
    91 | 96 |
    { content }
    97 |
    98 | } 99 | } 100 | -------------------------------------------------------------------------------- /web_frontend/src/settings_textarea.rs: -------------------------------------------------------------------------------- 1 | use crate::save_button; 2 | use crate::submit_banner; 3 | use serde::Serialize; 4 | use tauri_sys::tauri; 5 | use wasm_bindgen_futures::spawn_local; 6 | use web_sys::HtmlInputElement; 7 | use yew::virtual_dom::VNode; 8 | use yew::Properties; 9 | use yew::{html, Component, Context, Html, InputEvent, TargetCast}; 10 | 11 | #[derive(Serialize)] 12 | struct SettingTauriPayload { 13 | input: String, 14 | } 15 | 16 | #[derive(Properties, PartialEq)] 17 | pub struct Props { 18 | pub h1: String, 19 | pub description: VNode, 20 | pub input_name: String, 21 | pub textarea_description: String, 22 | pub set_resource_name: String, 23 | pub get_resource_name: String, 24 | } 25 | 26 | pub struct SettingsTextarea { 27 | is_save_button_enabled: bool, 28 | changes_saved: bool, 29 | input_data: String, 30 | previous_input_data: String, 31 | } 32 | 33 | pub enum Message { 34 | LoadCurrentState, 35 | UpdateInput(String), 36 | UpdatePreviousInputData, 37 | Save, 38 | Saved, 39 | } 40 | 41 | impl Component for SettingsTextarea { 42 | type Message = Message; 43 | type Properties = Props; 44 | 45 | fn create(ctx: &Context) -> Self { 46 | ctx.link().send_message(Message::LoadCurrentState); 47 | 48 | Self { 49 | is_save_button_enabled: false, 50 | input_data: String::new(), 51 | previous_input_data: String::new(), 52 | changes_saved: false, 53 | } 54 | } 55 | 56 | fn update(&mut self, ctx: &Context, msg: Self::Message) -> bool { 57 | match msg { 58 | Message::UpdateInput(input_value) => { 59 | self.changes_saved = false; 60 | self.is_save_button_enabled = true; 61 | 62 | self.input_data = input_value; 63 | } 64 | Message::Save => { 65 | if !self.is_save_button_enabled { 66 | return false; 67 | } 68 | 69 | let resource_name = ctx.props().set_resource_name.clone(); 70 | let input_data = self.input_data.clone(); 71 | 72 | spawn_local(async move { 73 | tauri::invoke::<_, ()>( 74 | &resource_name, 75 | &SettingTauriPayload { input: input_data }, 76 | ) 77 | .await 78 | .unwrap(); 79 | }); 80 | 81 | ctx.link().send_message(Message::Saved); 82 | } 83 | Message::Saved => { 84 | ctx.link().send_message(Message::UpdatePreviousInputData); 85 | 86 | self.changes_saved = true; 87 | self.is_save_button_enabled = false; 88 | } 89 | Message::LoadCurrentState => { 90 | let resource_name = ctx.props().get_resource_name.clone(); 91 | 92 | let message_callback = ctx.link().callback(|message: Message| message); 93 | 94 | spawn_local(async move { 95 | let payload = tauri::invoke::<_, String>(&resource_name, &()) 96 | .await 97 | .unwrap(); 98 | 99 | message_callback.emit(Message::UpdateInput(payload)); 100 | message_callback.emit(Message::UpdatePreviousInputData) 101 | }); 102 | } 103 | Message::UpdatePreviousInputData => { 104 | self.previous_input_data = self.input_data.clone(); 105 | } 106 | } 107 | true 108 | } 109 | 110 | fn changed(&mut self, ctx: &Context) -> bool { 111 | ctx.link().send_message(Message::UpdateInput(String::new())); 112 | ctx.link().send_message(Message::LoadCurrentState); 113 | 114 | self.changes_saved = false; 115 | 116 | true 117 | } 118 | 119 | fn view(&self, ctx: &Context) -> Html { 120 | let button_state = 121 | if !self.is_save_button_enabled || (self.input_data == self.previous_input_data) { 122 | save_button::SaveButtonState::Disabled 123 | } else { 124 | save_button::SaveButtonState::Enabled 125 | }; 126 | 127 | let success_banner = if self.changes_saved { 128 | let icon = html! { 129 | 131 | 133 | 134 | }; 135 | html! { 136 | 137 | } 138 | } else { 139 | html! {} 140 | }; 141 | 142 | let oninput = ctx.link().callback(|e: InputEvent| { 143 | let input = e.target_unchecked_into::(); 144 | let value = input.value(); 145 | 146 | Message::UpdateInput(value) 147 | }); 148 | 149 | let onclick = ctx.link().callback(|_| Message::Save); 150 | 151 | let props = ctx.props(); 152 | 153 | html! { 154 | <> 155 |
    156 |

    { &props.h1 }

    157 |
    158 | {props.description.clone()} 159 | 160 | {success_banner} 161 | 162 |
    163 | 164 |
    165 | 166 |
    167 |
    168 | 169 | 170 | } 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /web_frontend/src/submit_banner.rs: -------------------------------------------------------------------------------- 1 | use yew::{classes, html, virtual_dom::VNode, Component, Context, Html, Properties}; 2 | 3 | pub struct SubmitBanner; 4 | 5 | #[derive(PartialEq, Eq, Clone, Copy)] 6 | pub enum Color { 7 | Green, 8 | Red, 9 | } 10 | 11 | #[derive(Properties, PartialEq)] 12 | pub struct Props { 13 | pub message: String, 14 | pub color: Color, 15 | pub icon: VNode, 16 | } 17 | 18 | impl Component for SubmitBanner { 19 | type Message = (); 20 | type Properties = Props; 21 | 22 | fn create(_ctx: &Context) -> Self { 23 | Self 24 | } 25 | 26 | fn view(&self, ctx: &Context) -> Html { 27 | let props = ctx.props(); 28 | 29 | let first_color = match props.color { 30 | Color::Green => "bg-green-500", 31 | Color::Red => "bg-red-500", 32 | }; 33 | 34 | let second_color = match props.color { 35 | Color::Green => "bg-green-700", 36 | Color::Red => "bg-red-700", 37 | }; 38 | 39 | html! { 40 |
    41 |
    42 |
    43 | 44 | {props.icon.clone()} 45 | 46 |

    47 | {&props.message} 48 |

    49 |
    50 |
    51 |
    52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /web_frontend/src/tailwind.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* Thanks to Plausible analytics for this beautiful circle :) 6 | https://github.com/plausible/analytics/blob/7d37208d52c2331a5ba22df21c8179bb3313c2da/assets/css/app.css#L112 */ 7 | .pulsating-circle { 8 | position: absolute; 9 | width: 10px; 10 | height: 10px; 11 | } 12 | 13 | .pulsating-circle::before { 14 | content: ''; 15 | position: relative; 16 | display: block; 17 | width: 300%; 18 | height: 300%; 19 | box-sizing: border-box; 20 | margin-left: -100%; 21 | margin-top: -100%; 22 | border-radius: 45px; 23 | background-color: #9ae6b4; 24 | animation: pulse-ring 3s cubic-bezier(0.215, 0.61, 0.355, 1) infinite; 25 | @apply bg-green-500; 26 | } 27 | 28 | .pulsating-circle::after { 29 | content: ''; 30 | position: absolute; 31 | left: 0; 32 | top: 0; 33 | display: block; 34 | width: 100%; 35 | height: 100%; 36 | background-color: white; 37 | border-radius: 15px; 38 | animation: pulse-dot 3s cubic-bezier(0.455, 0.03, 0.515, 0.955) -.4s infinite; 39 | @apply bg-green-500; 40 | } 41 | 42 | 43 | @keyframes pulse-ring { 44 | 0% { 45 | transform: scale(.33); 46 | } 47 | 48 | 50% { 49 | transform: scale(1); 50 | } 51 | 52 | 40%, 100% { 53 | opacity: 0; 54 | } 55 | } 56 | 57 | @keyframes pulse-dot { 58 | 0% { 59 | transform: scale(.8); 60 | } 61 | 62 | 25% { 63 | transform: scale(1); 64 | } 65 | 66 | 50%, 100% { 67 | transform: scale(.8); 68 | } 69 | } 70 | 71 | .loader { 72 | border-top-color: rgba(255, 255, 255, 0); 73 | animation: spinner 0.5s cubic-bezier(0, 0.86, 0.93, 0.71) infinite; 74 | } 75 | 76 | @keyframes spinner { 77 | 0% { 78 | transform: rotate(0deg); 79 | } 80 | 100% { 81 | transform: rotate(360deg); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /web_frontend/tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | theme: { 3 | extend: {} 4 | }, 5 | 6 | content: [ 7 | './src/**/*.rs', 8 | ], 9 | 10 | plugins: [ 11 | require('@tailwindcss/forms'), 12 | ] 13 | } 14 | --------------------------------------------------------------------------------