pax_global_header00006660000000000000000000000064146424135740014524gustar00rootroot0000000000000052 comment=ba2b2748e472c18f5595200438be55ed1f943f45 nh3-0.2.18/000077500000000000000000000000001464241357400123045ustar00rootroot00000000000000nh3-0.2.18/.github/000077500000000000000000000000001464241357400136445ustar00rootroot00000000000000nh3-0.2.18/.github/FUNDING.yml000066400000000000000000000000211464241357400154520ustar00rootroot00000000000000github: messense nh3-0.2.18/.github/dependabot.yml000066400000000000000000000007701464241357400165000ustar00rootroot00000000000000# To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "cargo" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" nh3-0.2.18/.github/workflows/000077500000000000000000000000001464241357400157015ustar00rootroot00000000000000nh3-0.2.18/.github/workflows/CI.yml000066400000000000000000000146221464241357400167240ustar00rootroot00000000000000name: CI on: push: branches: - main tags: [ 'v*' ] pull_request: jobs: macos: runs-on: macos-latest steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: 3.12 architecture: x64 - uses: dtolnay/rust-toolchain@nightly - name: Build wheels - x86_64 uses: messense/maturin-action@v1 with: target: x86_64 args: --release --out dist --sdist sccache: true - name: Build wheels - universal2 uses: messense/maturin-action@v1 with: target: universal2-apple-darwin args: --release --out dist sccache: true - name: Install built wheel - universal2 run: | pip install nh3 --no-index --find-links dist --force-reinstall pip install pytest cd tests && pytest - name: Upload wheels uses: actions/upload-artifact@v3 with: name: wheels path: dist windows: runs-on: windows-latest strategy: matrix: target: [x64, x86] steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: 3.12 architecture: ${{ matrix.target }} - uses: dtolnay/rust-toolchain@nightly - name: Build wheels uses: messense/maturin-action@v1 with: target: ${{ matrix.target }} args: --release --out dist - name: Install built wheel run: | pip install nh3 --no-index --find-links dist --force-reinstall pip install pytest cd tests && pytest - name: Upload wheels uses: actions/upload-artifact@v3 with: name: wheels path: dist linux: runs-on: ubuntu-latest strategy: matrix: target: [x86_64, i686] steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: 3.12 architecture: x64 - name: Build wheels uses: messense/maturin-action@v1 with: rust-toolchain: nightly target: ${{ matrix.target }} manylinux: auto args: --release --out dist sccache: true - name: Install built wheel if: matrix.target == 'x86_64' run: | pip install nh3 --no-index --find-links dist --force-reinstall pip install pytest cd tests && pytest - name: Upload wheels uses: actions/upload-artifact@v3 with: name: wheels path: dist linux-cross: runs-on: ubuntu-latest strategy: matrix: target: [aarch64, armv7, s390x, ppc64le, ppc64] steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: 3.12 - name: Build wheels uses: messense/maturin-action@v1 with: rust-toolchain: nightly target: ${{ matrix.target }} manylinux: auto args: --release --out dist sccache: true - uses: uraimo/run-on-arch-action@v2.3.0 if: matrix.target != 'ppc64' name: Install built wheel with: arch: ${{ matrix.target }} distro: ubuntu20.04 githubToken: ${{ github.token }} install: | apt-get update apt-get install -y --no-install-recommends python3 python3-pip pip3 install -U pip pytest run: | pip3 install nh3 --no-index --find-links dist/ --force-reinstall cd tests && pytest - name: Upload wheels uses: actions/upload-artifact@v3 with: name: wheels path: dist musllinux: runs-on: ubuntu-latest strategy: matrix: target: - x86_64-unknown-linux-musl - i686-unknown-linux-musl steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: 3.12 architecture: x64 - name: Build wheels uses: messense/maturin-action@v1 with: target: ${{ matrix.target }} manylinux: musllinux_1_2 args: --release --out dist sccache: true - name: Install built wheel if: matrix.target == 'x86_64-unknown-linux-musl' uses: addnab/docker-run-action@v3 with: image: alpine:latest options: -v ${{ github.workspace }}:/io -w /io run: | apk add py3-virtualenv python3 -m virtualenv .venv source .venv/bin/activate pip install -U pip pytest pip install nh3 --no-index --find-links /io/dist/ --force-reinstall cd tests && python3 -m pytest - name: Upload wheels uses: actions/upload-artifact@v3 with: name: wheels path: dist musllinux-cross: runs-on: ubuntu-latest strategy: matrix: platform: - target: aarch64-unknown-linux-musl arch: aarch64 - target: armv7-unknown-linux-musleabihf arch: armv7 steps: - uses: actions/checkout@v3 - uses: actions/setup-python@v4 with: python-version: 3.12 - name: Build wheels uses: messense/maturin-action@v1 with: target: ${{ matrix.platform.target }} manylinux: musllinux_1_2 args: --release --out dist sccache: true - uses: uraimo/run-on-arch-action@v2.3.0 name: Install built wheel with: arch: ${{ matrix.platform.arch }} distro: alpine_latest githubToken: ${{ github.token }} install: | apk add py3-virtualenv run: | python3 -m virtualenv .venv source .venv/bin/activate pip install pytest pip install nh3 --no-index --find-links dist/ --force-reinstall cd tests && python3 -m pytest - name: Upload wheels uses: actions/upload-artifact@v3 with: name: wheels path: dist release: name: Release runs-on: ubuntu-latest if: "startsWith(github.ref, 'refs/tags/')" needs: [ macos, windows, linux, linux-cross ] steps: - uses: actions/download-artifact@v3 with: name: wheels - uses: actions/setup-python@v4 with: python-version: 3.12 - name: Publish to PyPI env: TWINE_USERNAME: __token__ TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} run: | pip install --upgrade twine twine upload --skip-existing * nh3-0.2.18/.gitignore000066400000000000000000000012141464241357400142720ustar00rootroot00000000000000/target # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ venv/ bin/ build/ develop-eggs/ dist/ eggs/ lib/ lib64/ parts/ sdist/ var/ include/ man/ *.egg-info/ .installed.cfg *.egg # Installer logs pip-log.txt pip-delete-this-directory.txt pip-selfcheck.json # Unit test / coverage reports htmlcov/ .tox/ .coverage .cache nosetests.xml coverage.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject # Rope .ropeproject # Django stuff: *.log *.pot # Sphinx documentation docs/_build/ # PyCharm .idea/ .python-version .pytest_cache/ .DS_Store nh3-0.2.18/.readthedocs.yml000066400000000000000000000004341464241357400153730ustar00rootroot00000000000000# https://docs.readthedocs.io/en/stable/config-file/v2.html#supported-settings version: 2 sphinx: builder: html build: os: "ubuntu-22.04" tools: python: "3.12" rust: "1.70" python: install: - requirements: docs/requirements.txt - method: pip path: . nh3-0.2.18/Cargo.lock000066400000000000000000000401471464241357400142170ustar00rootroot00000000000000# This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "ammonia" version = "4.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ab99eae5ee58501ab236beb6f20f6ca39be615267b014899c89b2f0bc18a459" dependencies = [ "html5ever", "maplit", "once_cell", "tendril", "url", ] [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "futf" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" dependencies = [ "mac", "new_debug_unreachable", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "html5ever" version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c13771afe0e6e846f1e67d038d4cb29998a6779f93c809212e4e9c32efd244d4" dependencies = [ "log", "mac", "markup5ever", "proc-macro2", "quote", "syn", ] [[package]] name = "idna" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "indoc" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "lock_api" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "mac" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" [[package]] name = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "markup5ever" version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16ce3abbeba692c8b8441d036ef91aea6df8da2c6b6e21c7e14d3c18e526be45" dependencies = [ "log", "phf", "phf_codegen", "string_cache", "string_cache_codegen", "tendril", ] [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "new_debug_unreachable" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" [[package]] name = "nh3" version = "0.2.18" dependencies = [ "ammonia", "pyo3", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "parking_lot" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets", ] [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "phf" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ade2d8b8f33c7333b51bcf0428d37e217e9f32192ae4772156f65063b8ce03dc" dependencies = [ "phf_shared 0.11.2", ] [[package]] name = "phf_codegen" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8d39688d359e6b34654d328e262234662d16cc0f60ec8dcbe5e718709342a5a" dependencies = [ "phf_generator 0.11.2", "phf_shared 0.11.2", ] [[package]] name = "phf_generator" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" dependencies = [ "phf_shared 0.10.0", "rand", ] [[package]] name = "phf_generator" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48e4cc64c2ad9ebe670cb8fd69dd50ae301650392e81c05f9bfcb2d5bdbc24b0" dependencies = [ "phf_shared 0.11.2", "rand", ] [[package]] name = "phf_shared" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" dependencies = [ "siphasher", ] [[package]] name = "phf_shared" version = "0.11.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90fcb95eef784c2ac79119d1dd819e162b5da872ce6f3c3abe1e8ca1c082f72b" dependencies = [ "siphasher", ] [[package]] name = "portable-atomic" version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" [[package]] name = "ppv-lite86" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" [[package]] name = "precomputed-hash" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1962a33ed2a201c637fc14a4e0fd4e06e6edfdeee6a5fede0dab55507ad74cf7" dependencies = [ "cfg-if", "indoc", "libc", "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", "unindent", ] [[package]] name = "pyo3-build-config" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ab7164b2202753bd33afc7f90a10355a719aa973d1f94502c50d06f3488bc420" dependencies = [ "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6424906ca49013c0829c5c1ed405e20e2da2dc78b82d198564880a704e6a7b7" dependencies = [ "libc", "pyo3-build-config", ] [[package]] name = "pyo3-macros" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "82b2f19e153122d64afd8ce7aaa72f06a00f52e34e1d1e74b6d71baea396460a" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", "syn", ] [[package]] name = "pyo3-macros-backend" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd698c04cac17cf0fe63d47790ab311b8b25542f5cb976b65c374035c50f1eef" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", "syn", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ "getrandom", ] [[package]] name = "redox_syscall" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c82cf8cff14456045f55ec4241383baeff27af886adb72ffb2162f99911de0fd" dependencies = [ "bitflags", ] [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "siphasher" version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38b58827f4464d87d377d175e90bf58eb00fd8716ff0a62f80356b5e61555d0d" [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "string_cache" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b" dependencies = [ "new_debug_unreachable", "once_cell", "parking_lot", "phf_shared 0.10.0", "precomputed-hash", "serde", ] [[package]] name = "string_cache_codegen" version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" dependencies = [ "phf_generator 0.10.0", "phf_shared 0.10.0", "proc-macro2", "quote", ] [[package]] name = "syn" version = "2.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "201fcda3845c23e8212cd466bfebf0bd20694490fc0356ae8e428e0824a915a6" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "target-lexicon" version = "0.12.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e1fc403891a21bcfb7c37834ba66a547a8f402146eba7265b5a6d88059c9ff2f" [[package]] name = "tendril" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" dependencies = [ "futf", "mac", "utf-8", ] [[package]] name = "tinyvec" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce6b6a2fb3a985e99cebfaefa9faa3024743da73304ca1c683a36429613d3d22" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "unicode-bidi" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-normalization" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unindent" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "url" version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", "percent-encoding", ] [[package]] name = "utf-8" version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" nh3-0.2.18/Cargo.toml000066400000000000000000000007551464241357400142430ustar00rootroot00000000000000[package] name = "nh3" version = "0.2.18" authors = ["messense "] edition = "2021" description = "Python bindings to the ammonia HTML sanitization library." license = "MIT" repository = "https://github.com/messense/nh3" readme = "README.md" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [lib] crate-type = ["cdylib"] [dependencies] ammonia = "4.0.0" pyo3 = { version = "0.22.0", features = ["abi3-py37", "gil-refs"] } nh3-0.2.18/LICENSE000066400000000000000000000020761464241357400133160ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2021-present Messense Lv Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. nh3-0.2.18/README.md000066400000000000000000000025651464241357400135730ustar00rootroot00000000000000# nh3 ![CI](https://github.com/messense/nh3/workflows/CI/badge.svg) [![PyPI](https://img.shields.io/pypi/v/nh3.svg)](https://pypi.org/project/nh3) [![Documentation Status](https://readthedocs.org/projects/nh3/badge/?version=latest)](https://nh3.readthedocs.io/en/latest/?badge=latest) Python bindings to the [ammonia](https://github.com/rust-ammonia/ammonia) HTML sanitization library. ## Installation ```bash pip install nh3 ``` ## Usage See [the documentation](https://nh3.readthedocs.io/en/latest/). ## Performance A quick benchmark showing that nh3 is about 20 times faster than the deprecated [bleach](https://pypi.org/project/bleach/) package. Measured on a MacBook Air (M2, 2022). ```ipython Python 3.11.0 (main, Oct 25 2022, 16:25:24) [Clang 14.0.0 (clang-1400.0.29.102)] Type 'copyright', 'credits' or 'license' for more information IPython 8.9.0 -- An enhanced Interactive Python. Type '?' for help. In [1]: import requests In [2]: import bleach In [3]: import nh3 In [4]: html = requests.get("https://www.google.com").text In [5]: %timeit bleach.clean(html) 2.85 ms ± 22.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) In [6]: %timeit nh3.clean(html) 138 µs ± 860 ns per loop (mean ± std. dev. of 7 runs, 10,000 loops each) ``` ## License This work is released under the MIT license. A copy of the license is provided in the [LICENSE](./LICENSE) file. nh3-0.2.18/docs/000077500000000000000000000000001464241357400132345ustar00rootroot00000000000000nh3-0.2.18/docs/Makefile000066400000000000000000000011721464241357400146750ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = . BUILDDIR = _build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) nh3-0.2.18/docs/conf.py000066400000000000000000000034551464241357400145420ustar00rootroot00000000000000# Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # import sys # sys.path.insert(0, os.path.abspath('..')) # -- Project information ----------------------------------------------------- project = 'nh3' copyright = '2021-present, messense' author = 'messense' # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', 'sphinx.ext.coverage', ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. exclude_patterns = [ '.DS_Store', '.venv', '_build', 'Thumbs.db', ] # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = 'shibuya' html_theme_options = { 'page_layout': 'compact', 'github_url': 'https://github.com/messense/nh3', } pygments_style = "xcode" pygments_dark_style = "monokai" nh3-0.2.18/docs/index.rst000066400000000000000000000027431464241357400151030ustar00rootroot00000000000000nh3 === Python bindings to the `ammonia `__ HTML sanitization library. Installation ------------ .. code-block:: bash pip install nh3 Usage ----- Use ``clean()`` to sanitize HTML fragments: .. code-block:: pycon >>> import nh3 >>> nh3.clean("hi") 'hi' >>> nh3.clean("XSS?") 'XSS?' It has many options to customize the sanitization, as documented below. For example, to only allow ```` tags: .. code-block:: python >>> nh3.clean("Hello", tags={"b"}) 'Hello' API reference ------------- .. automodule:: nh3 :members: .. attribute:: ALLOWED_TAGS The default set of tags allowed by ``clean()``. Useful for customizing the default to add or remove some tags: .. code-block:: pycon >>> tags = nh3.ALLOWED_TAGS - {"b"} >>> nh3.clean("yeah", tags=tags) 'yeah' .. attribute:: ALLOWED_ATTRIBUTES The default mapping of tags to allowed attributes for ``clean()``. Useful for customizing the default to add or remove some attributes: .. code-block:: pycon >>> from copy import deepcopy >>> attributes = deepcopy(nh3.ALLOWED_ATTRIBUTES) >>> attributes["img"].add("data-invert") >>> nh3.clean("", attributes=attributes) '' nh3-0.2.18/docs/make.bat000066400000000000000000000014401464241357400146400ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=. set BUILDDIR=_build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.https://www.sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd nh3-0.2.18/docs/requirements.txt000066400000000000000000000000171464241357400165160ustar00rootroot00000000000000shibuya sphinx nh3-0.2.18/nh3.pyi000066400000000000000000000014251464241357400135210ustar00rootroot00000000000000from typing import Callable, Dict, Optional, Set ALLOWED_TAGS: Set[str] ALLOWED_ATTRIBUTES: Dict[str, Set[str]] def clean( html: str, tags: Optional[Set[str]] = None, clean_content_tags: Optional[Set[str]] = None, attributes: Optional[Dict[str, Set[str]]] = None, attribute_filter: Optional[Callable[[str, str, str], Optional[str]]] = None, strip_comments: bool = True, link_rel: Optional[str] = "noopener noreferrer", generic_attribute_prefixes: Optional[Set[str]] = None, tag_attribute_values: Optional[Dict[str, Dict[str, Set[str]]]] = None, set_tag_attribute_values: Optional[Dict[str, Dict[str, str]]] = None, url_schemes: Optional[Set[str]] = None, ) -> str: ... def clean_text(html: str) -> str: ... def is_html(html: str) -> bool: ... nh3-0.2.18/pyproject.toml000066400000000000000000000001771464241357400152250ustar00rootroot00000000000000[build-system] requires = ["maturin>=1.0,<2.0"] build-backend = "maturin" [tool.maturin] features = ["pyo3/extension-module"] nh3-0.2.18/src/000077500000000000000000000000001464241357400130735ustar00rootroot00000000000000nh3-0.2.18/src/lib.rs000066400000000000000000000235451464241357400142200ustar00rootroot00000000000000use std::borrow::Cow; use std::collections::{HashMap, HashSet}; use pyo3::exceptions::PyTypeError; use pyo3::prelude::*; use pyo3::types::{PyString, PyTuple}; /// Sanitize an HTML fragment according to the given options. /// /// :param html: Input HTML fragment /// :type html: ``str`` /// :param tags: Sets the tags that are allowed. /// :type tags: ``set[str]``, optional /// :param clean_content_tags: Sets the tags whose contents will be completely removed from the output. /// :type clean_content_tags: ``set[str]``, optional /// :param attributes: Sets the HTML attributes that are allowed on specific tags, /// ``*`` key means the attributes are allowed on any tag. /// :type attributes: ``dict[str, set[str]]``, optional /// :param attribute_filter: Allows rewriting of all attributes using a callback. /// The callback takes name of the element, attribute and its value. /// Returns ``None`` to remove the attribute, or a value to use. /// :type attribute_filter: ``Callable[[str, str, str], str | None]``, optional /// :param strip_comments: Configures the handling of HTML comments, defaults to ``True``. /// :type strip_comments: ``bool`` /// :param link_rel: Configures a ``rel`` attribute that will be added on links, defaults to ``noopener noreferrer``. /// To turn on rel-insertion, pass a space-separated list. /// If ``rel`` is in the generic or tag attributes, this must be set to ``None``. Common ``rel`` values to include: /// /// - ``noopener``: This prevents a particular type of XSS attack, and should usually be turned on for untrusted HTML. /// - ``noreferrer``: This prevents the browser from sending the source URL to the website that is linked to. /// - ``nofollow``: This prevents search engines from using this link for ranking, which disincentivizes spammers. /// :type link_rel: ``str`` /// :param generic_attribute_prefixes: Sets the prefix of attributes that are allowed on any tag. /// :type generic_attribute_prefixes: ``set[str]``, optional /// :param tag_attribute_values: Sets the values of HTML attributes that are allowed on specific tags. /// The value is structured as a map from tag names to a map from attribute names to a set of attribute values. /// If a tag is not itself whitelisted, adding entries to this map will do nothing. /// :type tag_attribute_values: ``dict[str, dict[str, set[str]]]``, optional /// :param set_tag_attribute_values: Sets the values of HTML attributes that are to be set on specific tags. /// The value is structured as a map from tag names to a map from attribute names to an attribute value. /// If a tag is not itself whitelisted, adding entries to this map will do nothing. /// :type set_tag_attribute_values: ``dict[str, dict[str, str]]``, optional /// :param url_schemes: Sets the URL schemes permitted on ``href`` and ``src`` attributes. /// :type url_schemes: ``set[str]``, optional /// :return: Sanitized HTML fragment /// :rtype: ``str`` /// /// For example: /// /// .. code-block:: pycon /// /// >>> import nh3 /// >>> nh3.clean("hi") /// 'hi' /// >>> nh3.clean("XSS?") /// 'XSS?' #[pyfunction(signature = ( html, tags = None, clean_content_tags = None, attributes = None, attribute_filter = None, strip_comments = true, link_rel = "noopener noreferrer", generic_attribute_prefixes = None, tag_attribute_values = None, set_tag_attribute_values = None, url_schemes = None, ))] #[allow(clippy::too_many_arguments)] fn clean( py: Python, html: &str, tags: Option>, clean_content_tags: Option>, attributes: Option>>, attribute_filter: Option, strip_comments: bool, link_rel: Option<&str>, generic_attribute_prefixes: Option>, tag_attribute_values: Option>>>, set_tag_attribute_values: Option>>, url_schemes: Option>, ) -> PyResult { if let Some(callback) = attribute_filter.as_ref() { if !callback.as_ref(py).is_callable() { return Err(PyTypeError::new_err("attribute_filter must be callable")); } } let cleaned = py.allow_threads(|| { if tags.is_some() || clean_content_tags.is_some() || attributes.is_some() || attribute_filter.is_some() || !strip_comments || link_rel != Some("noopener noreferrer") || generic_attribute_prefixes.is_some() || tag_attribute_values.is_some() || set_tag_attribute_values.is_some() || url_schemes.is_some() { let mut cleaner = ammonia::Builder::default(); if let Some(tags) = tags { cleaner.tags(tags); } if let Some(tags) = clean_content_tags { cleaner.clean_content_tags(tags); } if let Some(mut attrs) = attributes { if let Some(generic_attrs) = attrs.remove("*") { cleaner.generic_attributes(generic_attrs); } cleaner.tag_attributes(attrs); } if let Some(prefixes) = generic_attribute_prefixes { cleaner.generic_attribute_prefixes(prefixes); } if let Some(values) = tag_attribute_values { cleaner.tag_attribute_values(values); } if let Some(values) = set_tag_attribute_values { cleaner.set_tag_attribute_values(values); } if let Some(callback) = attribute_filter { cleaner.attribute_filter(move |element, attribute, value| { Python::with_gil(|py| { let res = callback.call_bound( py, PyTuple::new_bound( py, [ PyString::new_bound(py, element), PyString::new_bound(py, attribute), PyString::new_bound(py, value), ], ), None, ); let err = match res { Ok(val) => { if val.is_none(py) { return None; } else if let Ok(s) = val.downcast::(py) { match s.to_str() { Ok(s) => return Some(Cow::::Owned(s.to_string())), Err(err) => err, } } else { PyTypeError::new_err( "expected attribute_filter to return str or None", ) } } Err(err) => err, }; err.write_unraisable_bound( py, Some(&PyTuple::new_bound( py, [ PyString::new_bound(py, element), PyString::new_bound(py, attribute), PyString::new_bound(py, value), ], )), ); Some(value.into()) }) }); } cleaner.strip_comments(strip_comments); cleaner.link_rel(link_rel); if let Some(url_schemes) = url_schemes { cleaner.url_schemes(url_schemes); } cleaner.clean(html).to_string() } else { ammonia::clean(html) } }); Ok(cleaned) } /// Turn an arbitrary string into unformatted HTML. /// /// Roughly equivalent to Python’s html.escape() or PHP’s htmlspecialchars and /// htmlentities. Escaping is as strict as possible, encoding every character /// that has special meaning to the HTML parser. /// /// :param html: Input HTML fragment /// :type html: ``str`` /// :return: Cleaned text /// :rtype: ``str`` /// /// For example: /// /// .. code-block:: pycon /// /// >>> nh3.clean_text('Robert"); abuse();//') /// 'Robert"); abuse();//' #[pyfunction] fn clean_text(py: Python, html: &str) -> String { py.allow_threads(|| ammonia::clean_text(html)) } /// Determine if a given string contains HTML. /// /// This function parses the full string and checks for any HTML syntax. /// /// Note: This function will return True for strings that contain invalid HTML syntax /// like ```` and even ``Vec::::new()``. /// /// :param html: Input string /// :type html: ``str`` /// :rtype: ``bool`` /// /// For example: /// /// .. code-block:: pycon /// /// >>> nh3.is_html("plain text") /// False /// >>> nh3.is_html("

html!

") /// True #[pyfunction] fn is_html(py: Python, html: &str) -> bool { py.allow_threads(|| ammonia::is_html(html)) } /// Python bindings to the ammonia HTML sanitization library ( https://github.com/rust-ammonia/ammonia ). #[pymodule] fn nh3(_py: Python, m: &Bound) -> PyResult<()> { m.add("__version__", env!("CARGO_PKG_VERSION"))?; m.add_function(wrap_pyfunction!(clean, m)?)?; m.add_function(wrap_pyfunction!(clean_text, m)?)?; m.add_function(wrap_pyfunction!(is_html, m)?)?; let a = ammonia::Builder::default(); m.add("ALLOWED_TAGS", a.clone_tags())?; m.add("ALLOWED_ATTRIBUTES", a.clone_tag_attributes())?; Ok(()) } nh3-0.2.18/tests/000077500000000000000000000000001464241357400134465ustar00rootroot00000000000000nh3-0.2.18/tests/test_nh3.py000066400000000000000000000046511464241357400155550ustar00rootroot00000000000000import nh3 import pytest def test_clean(): html = "I'm not trying to XSS you" assert nh3.clean(html) == 'I\'m not trying to XSS you' assert nh3.clean(html, tags={"img"}) == 'I\'m not trying to XSS you' assert ( nh3.clean(html, tags={"img"}, attributes={}) == "I'm not trying to XSS you" ) assert nh3.clean(html, attributes={}) == "I'm not trying to XSS you" assert ( nh3.clean('baidu') == 'baidu' ) assert ( nh3.clean('baidu', link_rel=None) == 'baidu' ) assert ( nh3.clean( "", clean_content_tags={"script", "style"}, ) == "" ) assert ( nh3.clean('
', generic_attribute_prefixes={"data-"}) == '
' ) assert ( nh3.clean( "", tags={"my-tag"}, tag_attribute_values={"my-tag": {"my-attr": {"val"}}}, ) == '' ) assert ( nh3.clean( "", tags={"my-tag"}, set_tag_attribute_values={"my-tag": {"my-attr": "val"}}, ) == '' ) def test_clean_with_attribute_filter(): html = "Home" def attribute_filter(element, attribute, value): if element == "img" and attribute == "src": return None return value assert ( nh3.clean(html, attribute_filter=attribute_filter, link_rel=None) == 'Home' ) with pytest.raises(TypeError): nh3.clean(html, attribute_filter="not a callable") # attribute_filter may raise exception, but it's an infallible API # which writes a unraisable exception nh3.clean(html, attribute_filter=lambda _element, _attribute, _value: True) def test_clean_text(): res = nh3.clean_text('Robert"); abuse();//') assert res == "Robert"); abuse();//" def test_is_html(): assert not nh3.is_html("plain text") assert nh3.is_html("

html!

")