feat: add csrf scraping
This commit is contained in:
parent
591b30104c
commit
f2902f6175
328
Cargo.lock
generated
328
Cargo.lock
generated
@ -70,6 +70,12 @@ version = "2.9.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cfg-if"
|
name = "cfg-if"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
@ -144,6 +150,29 @@ version = "0.8.21"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cssparser"
|
||||||
|
version = "0.34.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b7c66d1cd8ed61bf80b38432613a7a2f09401ab8d0501110655f8b341484a3e3"
|
||||||
|
dependencies = [
|
||||||
|
"cssparser-macros",
|
||||||
|
"dtoa-short",
|
||||||
|
"itoa",
|
||||||
|
"phf",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cssparser-macros"
|
||||||
|
version = "0.6.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331"
|
||||||
|
dependencies = [
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ctrlc"
|
name = "ctrlc"
|
||||||
version = "3.4.7"
|
version = "3.4.7"
|
||||||
@ -177,12 +206,72 @@ dependencies = [
|
|||||||
"powerfmt",
|
"powerfmt",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "derive_more"
|
||||||
|
version = "0.99.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dtoa"
|
||||||
|
version = "1.0.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d6add3b8cff394282be81f3fc1a0605db594ed69890078ca6e2cab1c408bcf04"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "dtoa-short"
|
||||||
|
version = "0.3.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87"
|
||||||
|
dependencies = [
|
||||||
|
"dtoa",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "ego-tree"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b2972feb8dffe7bc8c5463b1dacda1b0dfbed3710e50f977d965429692d74cd8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
||||||
|
|
||||||
|
[[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 = "fxhash"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "getopts"
|
||||||
|
version = "0.2.23"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cba6ae63eb948698e300f645f87c70f76630d505f23b8907cf1e193ee85048c1"
|
||||||
|
dependencies = [
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hashbrown"
|
name = "hashbrown"
|
||||||
version = "0.14.5"
|
version = "0.14.5"
|
||||||
@ -201,6 +290,18 @@ version = "0.5.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "html5ever"
|
||||||
|
version = "0.29.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b7410cae13cbc75623c98ac4cbfd1f0bedddf3227afc24f370cf0f50a44a11c"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"mac",
|
||||||
|
"markup5ever",
|
||||||
|
"match_token",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "httparse"
|
name = "httparse"
|
||||||
version = "1.10.1"
|
version = "1.10.1"
|
||||||
@ -246,12 +347,55 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "log"
|
||||||
|
version = "0.4.27"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "mac"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "markup5ever"
|
||||||
|
version = "0.14.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c7a7213d12e1864c0f002f52c2923d4556935a43dec5e71355c2760e0f6e7a18"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"phf",
|
||||||
|
"phf_codegen",
|
||||||
|
"string_cache",
|
||||||
|
"string_cache_codegen",
|
||||||
|
"tendril",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "match_token"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "88a9689d8d44bf9964484516275f5cd4c9b59457a6940c1d5d0ecbb94510a36b"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "memchr"
|
name = "memchr"
|
||||||
version = "2.7.5"
|
version = "2.7.5"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "new_debug_unreachable"
|
||||||
|
version = "1.0.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.30.1"
|
version = "0.30.1"
|
||||||
@ -305,12 +449,76 @@ dependencies = [
|
|||||||
"windows-targets",
|
"windows-targets",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "paste"
|
||||||
|
version = "1.0.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078"
|
||||||
|
dependencies = [
|
||||||
|
"phf_macros",
|
||||||
|
"phf_shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_codegen"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "aef8048c789fa5e851558d709946d6d79a8ff88c0440c587967f8e94bfb1216a"
|
||||||
|
dependencies = [
|
||||||
|
"phf_generator",
|
||||||
|
"phf_shared",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_generator"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d"
|
||||||
|
dependencies = [
|
||||||
|
"phf_shared",
|
||||||
|
"rand",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_macros"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216"
|
||||||
|
dependencies = [
|
||||||
|
"phf_generator",
|
||||||
|
"phf_shared",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "phf_shared"
|
||||||
|
version = "0.11.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5"
|
||||||
|
dependencies = [
|
||||||
|
"siphasher",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "powerfmt"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "precomputed-hash"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.95"
|
version = "1.0.95"
|
||||||
@ -329,6 +537,21 @@ dependencies = [
|
|||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand"
|
||||||
|
version = "0.8.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||||
|
dependencies = [
|
||||||
|
"rand_core",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rand_core"
|
||||||
|
version = "0.6.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "redox_syscall"
|
name = "redox_syscall"
|
||||||
version = "0.5.13"
|
version = "0.5.13"
|
||||||
@ -344,6 +567,40 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "scraper"
|
||||||
|
version = "0.23.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "527e65d9d888567588db4c12da1087598d0f6f8b346cc2c5abc91f05fc2dffe2"
|
||||||
|
dependencies = [
|
||||||
|
"cssparser",
|
||||||
|
"ego-tree",
|
||||||
|
"getopts",
|
||||||
|
"html5ever",
|
||||||
|
"precomputed-hash",
|
||||||
|
"selectors",
|
||||||
|
"tendril",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "selectors"
|
||||||
|
version = "0.26.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd568a4c9bb598e291a08244a5c1f5a8a6650bee243b5b0f8dbb3d9cc1d87fe8"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cssparser",
|
||||||
|
"derive_more",
|
||||||
|
"fxhash",
|
||||||
|
"log",
|
||||||
|
"new_debug_unreachable",
|
||||||
|
"phf",
|
||||||
|
"phf_codegen",
|
||||||
|
"precomputed-hash",
|
||||||
|
"servo_arc",
|
||||||
|
"smallvec",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.219"
|
version = "1.0.219"
|
||||||
@ -373,18 +630,64 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "servo_arc"
|
||||||
|
version = "0.4.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "204ea332803bd95a0b60388590d59cf6468ec9becf626e2451f1d26a1d972de4"
|
||||||
|
dependencies = [
|
||||||
|
"stable_deref_trait",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "shlex"
|
name = "shlex"
|
||||||
version = "1.3.0"
|
version = "1.3.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "siphasher"
|
||||||
|
version = "1.0.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.15.1"
|
version = "1.15.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stable_deref_trait"
|
||||||
|
version = "1.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "string_cache"
|
||||||
|
version = "0.8.9"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bf776ba3fa74f83bf4b63c3dcbbf82173db2632ed8452cb2d891d33f459de70f"
|
||||||
|
dependencies = [
|
||||||
|
"new_debug_unreachable",
|
||||||
|
"parking_lot",
|
||||||
|
"phf_shared",
|
||||||
|
"precomputed-hash",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "string_cache_codegen"
|
||||||
|
version = "0.5.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c711928715f1fe0fe509c53b43e993a9a557babc2d0a3567d0a3006f1ac931a0"
|
||||||
|
dependencies = [
|
||||||
|
"phf_generator",
|
||||||
|
"phf_shared",
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "strsim"
|
name = "strsim"
|
||||||
version = "0.11.1"
|
version = "0.11.1"
|
||||||
@ -402,6 +705,17 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[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]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "2.0.12"
|
version = "2.0.12"
|
||||||
@ -506,6 +820,8 @@ dependencies = [
|
|||||||
"dashmap",
|
"dashmap",
|
||||||
"httparse",
|
"httparse",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
|
"paste",
|
||||||
|
"scraper",
|
||||||
"serde",
|
"serde",
|
||||||
"shlex",
|
"shlex",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
@ -518,6 +834,18 @@ version = "1.0.18"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-width"
|
||||||
|
version = "0.2.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "4a1a07cc7db3810833284e8d372ccdc6da29741639ecc70c9ec107df0fa6154c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf-8"
|
||||||
|
version = "0.7.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utf8parse"
|
name = "utf8parse"
|
||||||
version = "0.2.2"
|
version = "0.2.2"
|
||||||
|
@ -12,6 +12,8 @@ ctrlc = "3"
|
|||||||
dashmap = "6"
|
dashmap = "6"
|
||||||
httparse = "1"
|
httparse = "1"
|
||||||
parking_lot = { version = "0", features = ["arc_lock", "serde"] }
|
parking_lot = { version = "0", features = ["arc_lock", "serde"] }
|
||||||
|
paste = "1"
|
||||||
|
scraper = "0"
|
||||||
serde = { version = "1", features = ["derive"] }
|
serde = { version = "1", features = ["derive"] }
|
||||||
shlex = "1"
|
shlex = "1"
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
|
@ -49,6 +49,7 @@ pub fn start_client(config: config::Schema) -> anyhow::Result<()> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
pub fn handle_daemon_client(mut stream: UnixStream, config: &Arc<config::Schema>) {
|
pub fn handle_daemon_client(mut stream: UnixStream, config: &Arc<config::Schema>) {
|
||||||
let Ok(stream2) = stream.try_clone() else {
|
let Ok(stream2) = stream.try_clone() else {
|
||||||
return;
|
return;
|
||||||
@ -65,9 +66,15 @@ pub fn handle_daemon_client(mut stream: UnixStream, config: &Arc<config::Schema>
|
|||||||
"help" => {
|
"help" => {
|
||||||
_ = writeln!(
|
_ = writeln!(
|
||||||
stream,
|
stream,
|
||||||
"available commands:\n help\n configcount\n confdump\n hosts <list|create|delete> ..."
|
"available commands (in no particular order):\n help\n configcount\n userdump \n csrfdump\n confdump\n hosts <list|create|delete> ..."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
"userdump" => {
|
||||||
|
_ = writeln!(stream, "{:#?}", crate::listener::AUTH_MAP);
|
||||||
|
},
|
||||||
|
"csrfdump" => {
|
||||||
|
_ = writeln!(stream, "{:#?}", crate::listener::CSRF_MAP);
|
||||||
|
},
|
||||||
"configcount" => {
|
"configcount" => {
|
||||||
_ = writeln!(
|
_ = writeln!(
|
||||||
stream,
|
stream,
|
||||||
|
502
src/listener/mod.rs
Normal file
502
src/listener/mod.rs
Normal file
@ -0,0 +1,502 @@
|
|||||||
|
use core::str;
|
||||||
|
use std::{
|
||||||
|
io::{self, Read, Write},
|
||||||
|
net::{Shutdown, TcpListener, TcpStream},
|
||||||
|
str::Utf8Error,
|
||||||
|
sync::Arc,
|
||||||
|
thread,
|
||||||
|
};
|
||||||
|
|
||||||
|
use dashmap::{DashMap, mapref::one::Ref};
|
||||||
|
use httparse::{Request, Response};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::{config, constants, utils::headers::HeadersExt};
|
||||||
|
|
||||||
|
pub fn start_listener_loop(config_arc: &Arc<config::Schema>, listener: &TcpListener) -> ! {
|
||||||
|
for stream in listener.incoming() {
|
||||||
|
match stream {
|
||||||
|
Ok(mut client) => {
|
||||||
|
let config_arc = config_arc.clone();
|
||||||
|
thread::spawn(|| {
|
||||||
|
if let Err(err) = handle_client(&mut client, config_arc.as_ref()) {
|
||||||
|
eprintln!("err: invalid req head ({err}), closing...");
|
||||||
|
_ = client.shutdown(Shutdown::Both);
|
||||||
|
}
|
||||||
|
drop(client);
|
||||||
|
drop(config_arc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(err) => eprintln!("error with an incoming listener {err:#?}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable!("listener had to be killed unexpectedly");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum ClientError {
|
||||||
|
#[error("error reading request head: {0}")]
|
||||||
|
HeadReadError(io::Error),
|
||||||
|
#[error("failed to find \"host\" header")]
|
||||||
|
HostHeaderNotFound,
|
||||||
|
#[error("requested host {0:?}, but it's not registered")]
|
||||||
|
HostNotRegistered(String),
|
||||||
|
#[error("failed to connect to backend: {0}")]
|
||||||
|
BackendConnectFail(io::Error),
|
||||||
|
#[error("io error exchanging client and backend: {0}")]
|
||||||
|
ExchangeIoError(io::Error),
|
||||||
|
#[error("io error capturing {0} buffer: {0}")]
|
||||||
|
BufferError(&'static str, io::Error),
|
||||||
|
#[error("io error writing auth headers: {0}")]
|
||||||
|
AuthHeadersIoError(io::Error),
|
||||||
|
#[error("failed to parse response head: {0}")]
|
||||||
|
ParseResponseHead(httparse::Error),
|
||||||
|
#[error("attempted to parse an unfinished response")]
|
||||||
|
UnfinishedResponse,
|
||||||
|
#[error("error with text encoding: {0}")]
|
||||||
|
TextEncodingError(Utf8Error),
|
||||||
|
#[error("error building html csrf finder selector: {0}")]
|
||||||
|
SelectorBuildError(String),
|
||||||
|
#[error("couldn't find csrf token in response")]
|
||||||
|
CsrfNotFound,
|
||||||
|
#[error("invalid header {0:?}")]
|
||||||
|
InvalidHeader(&'static str),
|
||||||
|
}
|
||||||
|
|
||||||
|
// fn(req, (adrr, etc)) -> Action
|
||||||
|
// Action {
|
||||||
|
// CaptureBody?
|
||||||
|
// CaptureResponse?
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// cases:
|
||||||
|
// * GET login -> get response to access csrf token
|
||||||
|
// * POST login -> get body to handle authentication
|
||||||
|
//
|
||||||
|
// auth headers are just added depending on the ID cookie soo, keep a hashmap of IDs and user
|
||||||
|
// data??
|
||||||
|
//
|
||||||
|
// check authentication first of everything as it changes decisions on everything and give it to fn
|
||||||
|
// too
|
||||||
|
// fn(req, Option<AuthData>, (adrr, etc)) -> Action
|
||||||
|
//
|
||||||
|
// and also add an action to... hook in the response without even letting backend know (redirect
|
||||||
|
// ppl out and such), also in no case we need multiple actions. NOPE, POST login should capture
|
||||||
|
// body and hijack the response, I'll also just make this flexible too.
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum ProxyAction<'a> {
|
||||||
|
// combining like this eases a lot the implementation
|
||||||
|
CaptureBodyAndHijack(String),
|
||||||
|
// (
|
||||||
|
// fn(&httparse::Request, &[u8]) -> Result<(), ClientError>,
|
||||||
|
// fn(&httparse::Request, &mut TcpStream) -> Result<(), ClientError>,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
CaptureResponse(String),
|
||||||
|
//fn(&httparse::Request, &[u8]) -> Result<(), ClientError>),
|
||||||
|
ContWithMaybeAuth(Option<Ref<'a, String, AuthData>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ProxyAction<'_> {
|
||||||
|
// username - password
|
||||||
|
#[allow(dead_code)]
|
||||||
|
type CaptureBodyConclussion = (String, String);
|
||||||
|
|
||||||
|
pub fn capture_body(
|
||||||
|
_req: &httparse::Request,
|
||||||
|
_id: &str,
|
||||||
|
_body: &[u8],
|
||||||
|
) -> Result<Self::CaptureBodyConclussion, ClientError> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn hijack(
|
||||||
|
_req: &httparse::Request,
|
||||||
|
_body_conclussion: Self::CaptureBodyConclussion,
|
||||||
|
_stream: &mut TcpStream,
|
||||||
|
) -> Result<(), ClientError> {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn capture_response(
|
||||||
|
_req: &httparse::Request,
|
||||||
|
_res: &httparse::Response,
|
||||||
|
id: String,
|
||||||
|
csrf_keyname: &str,
|
||||||
|
body: &[u8],
|
||||||
|
) -> Result<(), ClientError> {
|
||||||
|
// Find csrf token and add it to list of things
|
||||||
|
let document = scraper::Html::parse_document(
|
||||||
|
str::from_utf8(body).map_err(ClientError::TextEncodingError)?,
|
||||||
|
);
|
||||||
|
let selector = scraper::Selector::parse(format!("input[name=\"{csrf_keyname}\"]").as_str())
|
||||||
|
.map_err(|err| ClientError::SelectorBuildError(format!("{err}")))?;
|
||||||
|
let csrf = document
|
||||||
|
.select(&selector)
|
||||||
|
.next()
|
||||||
|
.and_then(|elem| elem.attr("value"))
|
||||||
|
.ok_or(ClientError::CsrfNotFound)?;
|
||||||
|
|
||||||
|
println!("inserting {id} - {csrf}");
|
||||||
|
CSRF_MAP.insert(id, csrf.to_string());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AuthData {
|
||||||
|
pub username: String,
|
||||||
|
pub email: String,
|
||||||
|
pub fullname: Option<String>,
|
||||||
|
}
|
||||||
|
impl AuthData {
|
||||||
|
pub fn write_as_headers(&self, into: &mut impl Write) -> Result<(), ClientError> {
|
||||||
|
use constants::HTTP_HEADERS_NEWLINE as NL;
|
||||||
|
|
||||||
|
let r: Result<_, io::Error> = try {
|
||||||
|
into.write_all(b"X-WEBAUTH-USER: ")?;
|
||||||
|
into.write_all(self.username.as_bytes())?;
|
||||||
|
into.write_all(NL)?;
|
||||||
|
into.write_all(b"X-WEBAUTH-EMAIL: ")?;
|
||||||
|
into.write_all(self.email.as_bytes())?;
|
||||||
|
into.write_all(NL)?;
|
||||||
|
if let Some(fullname) = self.fullname.as_ref() {
|
||||||
|
into.write_all(b"X-WEBAUTH-FULLNAME: ")?;
|
||||||
|
into.write_all(fullname.as_bytes())?;
|
||||||
|
into.write_all(NL)?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
r.map_err(ClientError::AuthHeadersIoError)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn what_to_do<'a>(
|
||||||
|
req: &httparse::Request,
|
||||||
|
id_auth: Option<(String, Option<Ref<'a, String, AuthData>>)>,
|
||||||
|
) -> ProxyAction<'a> {
|
||||||
|
let trimedpath = req
|
||||||
|
.path
|
||||||
|
.and_then(|path| path.split_once('?').map(|spl| spl.0).or(Some(path)));
|
||||||
|
// FIXME: auth might not be set up until gitea replies and sets an ID
|
||||||
|
match (trimedpath, req.method, id_auth) {
|
||||||
|
(Some("/user/login"), Some("GET"), Some((id, None))) => {
|
||||||
|
// We gotta get the gitea response to find the csrf token and save that
|
||||||
|
ProxyAction::CaptureResponse(id)
|
||||||
|
}
|
||||||
|
(Some("/user/login"), Some("POST"), Some((id, None))) => {
|
||||||
|
// Here we gotta get the post body, check csrf, and check passwd
|
||||||
|
// Then get their account data, add it to their ID and redirect back to home
|
||||||
|
ProxyAction::CaptureBodyAndHijack(id)
|
||||||
|
}
|
||||||
|
(_, _, id_auth) => ProxyAction::ContWithMaybeAuth(id_auth.and_then(|id_auth| id_auth.1)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: might wanna save this one between runtimes
|
||||||
|
pub static AUTH_MAP: std::sync::LazyLock<DashMap<String, AuthData>> =
|
||||||
|
std::sync::LazyLock::new(DashMap::new);
|
||||||
|
// TODO: time expire old keys?
|
||||||
|
pub static CSRF_MAP: std::sync::LazyLock<DashMap<String, String>> =
|
||||||
|
std::sync::LazyLock::new(DashMap::new);
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
|
fn handle_client(client: &mut TcpStream, config: &config::Schema) -> Result<(), ClientError> {
|
||||||
|
use ClientError as E;
|
||||||
|
use ProxyAction as A;
|
||||||
|
|
||||||
|
http_bodies::parse_http!(client_req, client, Request, let (req, _, body_over));
|
||||||
|
|
||||||
|
let theres_body = req.headers.expect_body();
|
||||||
|
|
||||||
|
let host_header = req.headers.header("host").ok_or(E::HostHeaderNotFound)?;
|
||||||
|
let cookies = req.headers.header("cookie");
|
||||||
|
|
||||||
|
// Now find that header and pass everything
|
||||||
|
let read_hosts = config.hosts.read();
|
||||||
|
let (addr, id_keyname, csrf_keyname) = read_hosts
|
||||||
|
.get(host_header.as_ref())
|
||||||
|
.ok_or_else(|| E::HostNotRegistered(host_header.to_string()))?
|
||||||
|
.clone();
|
||||||
|
drop(read_hosts);
|
||||||
|
|
||||||
|
// Try to find auth data
|
||||||
|
let id_auth = if let Some(cookies) = cookies {
|
||||||
|
if let Some(id_ck) = cookie::Cookie::split_parse(cookies)
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.find(|ck| ck.name() == id_keyname)
|
||||||
|
{
|
||||||
|
let id = id_ck.value().to_string();
|
||||||
|
let val = AUTH_MAP.get(&id);
|
||||||
|
Some((id, val))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let action = what_to_do(&req, id_auth);
|
||||||
|
if let A::CaptureBodyAndHijack(id) = action {
|
||||||
|
let mut body = body_over.to_vec();
|
||||||
|
if theres_body {
|
||||||
|
// not sure if this will append or overwrite, wanna append
|
||||||
|
io::copy(client, &mut body).map_err(|err| E::BufferError("body", err))?;
|
||||||
|
}
|
||||||
|
let c = A::capture_body(&req, id.as_ref(), &body[..])?;
|
||||||
|
A::hijack(&req, c, client)?;
|
||||||
|
} else {
|
||||||
|
let mut stream = TcpStream::connect(addr).map_err(E::BackendConnectFail)?;
|
||||||
|
// stream.write_all(body_head).map_err(E::ExchangeIoError)?;
|
||||||
|
stream
|
||||||
|
.write_all(
|
||||||
|
format!(
|
||||||
|
"{} {} HTTP/1.1\r\n",
|
||||||
|
req.method.unwrap_or("GET"),
|
||||||
|
req.path.unwrap_or("/")
|
||||||
|
)
|
||||||
|
.as_bytes(),
|
||||||
|
)
|
||||||
|
.map_err(E::ExchangeIoError)?;
|
||||||
|
req.headers
|
||||||
|
.iter()
|
||||||
|
.filter(|header| !header.name.eq_ignore_ascii_case("accept-encoding"))
|
||||||
|
.map(|header| {
|
||||||
|
stream.write_all(
|
||||||
|
&{
|
||||||
|
let mut all = Vec::new();
|
||||||
|
all.extend_from_slice(header.name.as_bytes());
|
||||||
|
all.extend_from_slice(b": ");
|
||||||
|
all.extend_from_slice(header.value);
|
||||||
|
all.extend_from_slice(b"\r\n");
|
||||||
|
all
|
||||||
|
}[..],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.try_fold((), |(), header| header)
|
||||||
|
.map_err(E::ExchangeIoError)?;
|
||||||
|
if let A::ContWithMaybeAuth(Some(ref auth)) = action {
|
||||||
|
auth.write_as_headers(&mut stream)?;
|
||||||
|
}
|
||||||
|
stream
|
||||||
|
.write_all(constants::HTTP_HEADERS_NEWLINE)
|
||||||
|
.map_err(E::ExchangeIoError)?;
|
||||||
|
|
||||||
|
// send our overhead
|
||||||
|
stream.write_all(body_over).map_err(E::ExchangeIoError)?;
|
||||||
|
if theres_body {
|
||||||
|
io::copy(client, &mut stream).map_err(E::ExchangeIoError)?;
|
||||||
|
}
|
||||||
|
if let A::CaptureResponse(id) = action {
|
||||||
|
http_bodies::parse_http!(stream_res, stream, Response, let (resp, _, res_body_over));
|
||||||
|
|
||||||
|
let buf = http_bodies::capture_body(
|
||||||
|
resp.headers,
|
||||||
|
res_body_over,
|
||||||
|
&mut stream,
|
||||||
|
"response buffer",
|
||||||
|
)?
|
||||||
|
.unwrap_or_else(Vec::new);
|
||||||
|
A::capture_response(&req, &resp, id, csrf_keyname.as_ref(), &buf[..])?;
|
||||||
|
|
||||||
|
client
|
||||||
|
.write_all(
|
||||||
|
format!(
|
||||||
|
"HTTP/1.1 {} {}\r\n",
|
||||||
|
resp.code.unwrap_or(200),
|
||||||
|
resp.reason.unwrap_or("OK")
|
||||||
|
)
|
||||||
|
.as_bytes(),
|
||||||
|
)
|
||||||
|
.map_err(E::ExchangeIoError)?;
|
||||||
|
resp.headers
|
||||||
|
.iter()
|
||||||
|
.filter(|header| !header.name.eq_ignore_ascii_case("transfer-encoding"))
|
||||||
|
.map(|header| {
|
||||||
|
client.write_all(
|
||||||
|
&{
|
||||||
|
let mut all = Vec::new();
|
||||||
|
all.extend_from_slice(header.name.as_bytes());
|
||||||
|
all.extend_from_slice(b": ");
|
||||||
|
all.extend_from_slice(header.value);
|
||||||
|
all.extend_from_slice(b"\r\n");
|
||||||
|
all
|
||||||
|
}[..],
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.try_fold((), |(), header| header)
|
||||||
|
.map_err(E::ExchangeIoError)?;
|
||||||
|
|
||||||
|
// FIXME: :grimacing: (append to headers or smth)
|
||||||
|
client
|
||||||
|
.write_all(format!("Content-Length: {}\r\n\r\n", buf.len()).as_bytes())
|
||||||
|
.map_err(E::ExchangeIoError)?;
|
||||||
|
client
|
||||||
|
.write_all(constants::HTTP_HEADERS_NEWLINE)
|
||||||
|
.map_err(E::ExchangeIoError)?;
|
||||||
|
|
||||||
|
client.write_all(&buf[..]).map_err(E::ExchangeIoError)?;
|
||||||
|
} else {
|
||||||
|
io::copy(&mut stream, client).map_err(E::ExchangeIoError)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
mod http_bodies {
|
||||||
|
use crate::{listener::ClientError, utils::headers::HeadersExt};
|
||||||
|
|
||||||
|
// pub trait HttpMessage<'h, 'b> {
|
||||||
|
// fn new(headers: &'h mut [httparse::Header<'b>]) -> Self;
|
||||||
|
// fn parse(&mut self, buf: &'b [u8]) -> httparse::Result<usize>;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl<'h, 'b> HttpMessage<'h, 'b> for httparse::Request<'h, 'b> {
|
||||||
|
// fn new(headers: &'h mut [httparse::Header<'b>]) -> Self {
|
||||||
|
// httparse::Request::new(headers)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn parse(&mut self, buf: &'b [u8]) -> httparse::Result<usize> {
|
||||||
|
// self.parse(buf)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// impl<'h, 'b> HttpMessage<'h, 'b> for httparse::Response<'h, 'b> {
|
||||||
|
// fn new(headers: &'h mut [httparse::Header<'b>]) -> Self {
|
||||||
|
// httparse::Response::new(headers)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// fn parse(&mut self, buf: &'b [u8]) -> httparse::Result<usize> {
|
||||||
|
// self.parse(buf)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
pub struct ChunckedTransferEncoding<'a, R: Read> {
|
||||||
|
rem: Vec<u8>,
|
||||||
|
reader: &'a mut R,
|
||||||
|
}
|
||||||
|
// This was a read impl, then tried to go into an interator and ended up being... this
|
||||||
|
impl<R: Read> ChunckedTransferEncoding<'_, R> {
|
||||||
|
fn read(&mut self, mut cb: impl FnMut(&[u8])) -> io::Result<()> {
|
||||||
|
loop {
|
||||||
|
let Some(busize_after_pos) =
|
||||||
|
self.rem.windows(b"\r\n".len()).position(|w| w == b"\r\n")
|
||||||
|
else {
|
||||||
|
let mut tmp = [0u8; 4096];
|
||||||
|
let n = self.reader.read(&mut tmp)?;
|
||||||
|
self.rem.extend_from_slice(&tmp[..n]);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let chunk_len = self.rem[0..busize_after_pos]
|
||||||
|
.iter()
|
||||||
|
.try_fold(0usize, |mut acc, i| {
|
||||||
|
acc <<= 4;
|
||||||
|
|
||||||
|
#[allow(clippy::manual_is_ascii_check)]
|
||||||
|
{
|
||||||
|
acc += (if (b'0'..=b'9').contains(i) {
|
||||||
|
i - b'0'
|
||||||
|
} else if (b'A'..=b'F').contains(i) {
|
||||||
|
i - b'A'
|
||||||
|
} else if (b'a'..=b'f').contains(i) {
|
||||||
|
i - b'f'
|
||||||
|
} else {
|
||||||
|
return None;
|
||||||
|
}) as usize;
|
||||||
|
}
|
||||||
|
Some(acc)
|
||||||
|
})
|
||||||
|
.ok_or(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
"invalid chunk size head contents",
|
||||||
|
))?;
|
||||||
|
|
||||||
|
if chunk_len == 0 {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
while self.rem.len() < busize_after_pos + chunk_len + (b"\r\n".len() * 2) {
|
||||||
|
let mut tmp = [0u8; 4096];
|
||||||
|
let n = self.reader.read(&mut tmp)?;
|
||||||
|
self.rem.extend_from_slice(&tmp[..n]);
|
||||||
|
}
|
||||||
|
cb(&self.rem[(busize_after_pos + b"\r\n".len())
|
||||||
|
..(busize_after_pos + b"\r\n".len() + chunk_len)]);
|
||||||
|
self.rem = self.rem
|
||||||
|
[(busize_after_pos + b"\r\n".len() + chunk_len + b"\r\n".len())..]
|
||||||
|
.to_vec();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn capture_body(
|
||||||
|
headers: &[httparse::Header<'_>],
|
||||||
|
body_over: &[u8],
|
||||||
|
reader: &mut impl Read,
|
||||||
|
debug_bufname: &'static str,
|
||||||
|
) -> Result<Option<Vec<u8>>, ClientError> {
|
||||||
|
Ok(if let Some(tenc) = headers.header("transfer-encoding") {
|
||||||
|
if tenc != "chunked" {
|
||||||
|
return Err(ClientError::InvalidHeader("transfer-encoding"));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
(ChunckedTransferEncoding {
|
||||||
|
rem: body_over.to_vec(),
|
||||||
|
reader,
|
||||||
|
})
|
||||||
|
.read(|chunk| {
|
||||||
|
buf.extend_from_slice(chunk);
|
||||||
|
})
|
||||||
|
.map_err(|err| ClientError::BufferError(debug_bufname, err))?;
|
||||||
|
Some(buf)
|
||||||
|
} else if let Some(clen) = headers.header("content-length") {
|
||||||
|
let n: usize = clen
|
||||||
|
.parse()
|
||||||
|
.map_err(|_| ClientError::InvalidHeader("content-length"))?;
|
||||||
|
let mut buf = Vec::with_capacity(n);
|
||||||
|
buf.extend_from_slice(body_over);
|
||||||
|
reader
|
||||||
|
.read_exact(&mut buf[body_over.len()..])
|
||||||
|
.map_err(|err| ClientError::BufferError(debug_bufname, err))?;
|
||||||
|
Some(buf)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// I hate it too
|
||||||
|
macro_rules! parse_http {
|
||||||
|
($trash_state:tt, $stream:expr, $msg_type:ty, $ret:stmt) => {
|
||||||
|
$crate::listener::http_bodies::parse_http!($trash_state, $stream, $msg_type, 16, {1024 * 8}, $ret);
|
||||||
|
};
|
||||||
|
($trash_state:tt, $stream:expr, $msg_type:ty, $headers_len:expr, $buf_size:expr, $ret:stmt) => {
|
||||||
|
paste::paste! {
|
||||||
|
let mut [<$trash_state _buf>] = [0u8; $buf_size];
|
||||||
|
let mut [<$trash_state _pos>] = 0;
|
||||||
|
let mut [<$trash_state _headers>];
|
||||||
|
|
||||||
|
$ret = loop {
|
||||||
|
[<$trash_state _headers>] = [httparse::EMPTY_HEADER; $headers_len];
|
||||||
|
let mut res = <$msg_type>::new(&mut [<$trash_state _headers>]);
|
||||||
|
|
||||||
|
let n = $stream
|
||||||
|
.read(&mut [<$trash_state _buf>][[<$trash_state _pos>]..])
|
||||||
|
.map_err(ClientError::HeadReadError)?;
|
||||||
|
[<$trash_state _pos>] += n;
|
||||||
|
|
||||||
|
if let Ok(httparse::Status::Complete(n)) = res.parse(&[<$trash_state _buf>][0..[<$trash_state _pos>]]) {
|
||||||
|
break (res, &[<$trash_state _buf>][0..(n - $crate::constants::HTTP_HEADERS_NEWLINE.len())], &[<$trash_state _buf>][n..[<$trash_state _pos>]]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
use std::io::{self, Read};
|
||||||
|
|
||||||
|
pub(crate) use parse_http;
|
||||||
|
}
|
221
src/main.rs
221
src/main.rs
@ -1,7 +1,11 @@
|
|||||||
//! # Tuxcord Reverse Proxy Header Authenthication
|
//! # Tuxcord Reverse Proxy Header Authenthication
|
||||||
|
#![allow(incomplete_features)]
|
||||||
#![feature(
|
#![feature(
|
||||||
anonymous_lifetime_in_impl_trait,
|
anonymous_lifetime_in_impl_trait,
|
||||||
iterator_try_collect,
|
iterator_try_collect,
|
||||||
|
inherent_associated_types,
|
||||||
|
stmt_expr_attributes,
|
||||||
|
read_buf,
|
||||||
rwlock_downgrade,
|
rwlock_downgrade,
|
||||||
try_blocks
|
try_blocks
|
||||||
)]
|
)]
|
||||||
@ -9,24 +13,20 @@
|
|||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
fs::File,
|
fs::File,
|
||||||
io::{self, Read, Write},
|
io::{Read, Write},
|
||||||
net::{self, Shutdown, TcpListener, TcpStream},
|
net::TcpListener,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
thread,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use dashmap::{DashMap, mapref::one::Ref};
|
|
||||||
use thiserror::Error;
|
|
||||||
|
|
||||||
use crate::utils::headers::HeadersExt;
|
|
||||||
|
|
||||||
pub mod args;
|
pub mod args;
|
||||||
pub mod config;
|
pub mod config;
|
||||||
pub mod constants;
|
pub mod constants;
|
||||||
#[cfg(feature = "ipc")]
|
#[cfg(feature = "ipc")]
|
||||||
pub mod ipc;
|
pub mod ipc;
|
||||||
|
pub mod listener;
|
||||||
pub mod utils;
|
pub mod utils;
|
||||||
|
|
||||||
use args::Args;
|
use args::Args;
|
||||||
@ -66,210 +66,5 @@ fn main() -> anyhow::Result<()> {
|
|||||||
#[cfg(feature = "ipc")]
|
#[cfg(feature = "ipc")]
|
||||||
ipc::handle_daemon(config_arc.clone());
|
ipc::handle_daemon(config_arc.clone());
|
||||||
|
|
||||||
for stream in listener.incoming() {
|
listener::start_listener_loop(&config_arc, &listener);
|
||||||
match stream {
|
|
||||||
Ok(mut client) => {
|
|
||||||
let config_arc = config_arc.clone();
|
|
||||||
thread::spawn(|| {
|
|
||||||
if let Err(err) = handle_client(&mut client, config_arc.as_ref()) {
|
|
||||||
eprintln!("err: invalid req head ({err}), closing...");
|
|
||||||
_ = client.shutdown(Shutdown::Both);
|
|
||||||
}
|
|
||||||
drop(client);
|
|
||||||
drop(config_arc);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(err) => eprintln!("error with an incoming listener {err:#?}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unreachable!("listener had to be killed unexpectedly");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Error, Debug)]
|
|
||||||
pub enum ClientError {
|
|
||||||
#[error("error reading request head: {0}")]
|
|
||||||
HeadReadError(io::Error),
|
|
||||||
#[error("failed to find \"host\" header")]
|
|
||||||
HostHeaderNotFound,
|
|
||||||
#[error("requested host {0:?}, but it's not registered")]
|
|
||||||
HostNotRegistered(String),
|
|
||||||
#[error("failed to connect to backend: {0}")]
|
|
||||||
BackendConnectFail(io::Error),
|
|
||||||
#[error("io error exchanging client and backend: {0}")]
|
|
||||||
ExchangeIoError(io::Error),
|
|
||||||
#[error("io error capturing {0} buffer: {0}")]
|
|
||||||
BufferError(&'static str, io::Error),
|
|
||||||
#[error("io error writing auth headers: {0}")]
|
|
||||||
AuthHeadersIoError(io::Error),
|
|
||||||
}
|
|
||||||
|
|
||||||
// fn(req, (adrr, etc)) -> Action
|
|
||||||
// Action {
|
|
||||||
// CaptureBody?
|
|
||||||
// CaptureResponse?
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// cases:
|
|
||||||
// * GET login -> get response to access csrf token
|
|
||||||
// * POST login -> get body to handle authentication
|
|
||||||
//
|
|
||||||
// auth headers are just added depending on the ID cookie soo, keep a hashmap of IDs and user
|
|
||||||
// data??
|
|
||||||
//
|
|
||||||
// check authentication first of everything as it changes decisions on everything and give it to fn
|
|
||||||
// too
|
|
||||||
// fn(req, Option<AuthData>, (adrr, etc)) -> Action
|
|
||||||
//
|
|
||||||
// and also add an action to... hook in the response without even letting backend know (redirect
|
|
||||||
// ppl out and such), also in no case we need multiple actions. NOPE, POST login should capture
|
|
||||||
// body and hijack the response, I'll also just make this flexible too.
|
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
|
||||||
struct ProxyAction<'a> {
|
|
||||||
// combining like this eases a lot the implementation
|
|
||||||
capture_body_and_hijack: Option<(
|
|
||||||
fn(&httparse::Request, &[u8]) -> Result<(), ClientError>,
|
|
||||||
fn(&httparse::Request, &mut TcpStream) -> Result<(), ClientError>,
|
|
||||||
)>,
|
|
||||||
capture_response: Option<fn(&httparse::Request, &[u8]) -> Result<(), ClientError>>,
|
|
||||||
auth: Option<Ref<'a, String, AuthData>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AuthData {
|
|
||||||
pub username: String,
|
|
||||||
pub email: String,
|
|
||||||
pub fullname: Option<String>,
|
|
||||||
}
|
|
||||||
impl AuthData {
|
|
||||||
pub fn write_as_headers(&self, into: &mut impl Write) -> Result<(), ClientError> {
|
|
||||||
use constants::HTTP_HEADERS_NEWLINE as NL;
|
|
||||||
let r: Result<_, io::Error> = try {
|
|
||||||
into.write_all(b"X-WEBAUTH-USER: ")?;
|
|
||||||
into.write_all(self.username.as_bytes())?;
|
|
||||||
into.write_all(NL)?;
|
|
||||||
into.write_all(b"X-WEBAUTH-EMAIL: ")?;
|
|
||||||
into.write_all(self.email.as_bytes())?;
|
|
||||||
into.write_all(NL)?;
|
|
||||||
if let Some(fullname) = self.fullname.as_ref() {
|
|
||||||
into.write_all(b"X-WEBAUTH-FULLNAME: ")?;
|
|
||||||
into.write_all(fullname.as_bytes())?;
|
|
||||||
into.write_all(NL)?;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
r.map_err(ClientError::AuthHeadersIoError)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn what_to_do<'a>(
|
|
||||||
req: &httparse::Request,
|
|
||||||
id_auth: Option<(String, Option<Ref<'a, String, AuthData>>)>,
|
|
||||||
_backend: (net::SocketAddr, String, String),
|
|
||||||
) -> ProxyAction<'a> {
|
|
||||||
let is_authd = id_auth.as_ref().is_some_and(|id_auth| id_auth.1.is_some());
|
|
||||||
match (req.path, req.method, is_authd) {
|
|
||||||
(Some("/user/login"), Some("GET"), false) => todo!(),
|
|
||||||
(Some("/user/login"), Some("POST"), false) => todo!(),
|
|
||||||
_ => ProxyAction {
|
|
||||||
capture_body_and_hijack: None,
|
|
||||||
capture_response: None,
|
|
||||||
auth: id_auth.and_then(|id_auth| id_auth.1),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: might wanna save this one between runtimes
|
|
||||||
static AUTH_MAP: std::sync::LazyLock<DashMap<String, AuthData>> =
|
|
||||||
std::sync::LazyLock::new(DashMap::new);
|
|
||||||
static _CSRF_MAP: std::sync::LazyLock<DashMap<String, String>> =
|
|
||||||
std::sync::LazyLock::new(DashMap::new);
|
|
||||||
|
|
||||||
fn handle_client(client: &mut TcpStream, config: &config::Schema) -> Result<(), ClientError> {
|
|
||||||
use ClientError as E;
|
|
||||||
use constants::DOUBLE_CRLF;
|
|
||||||
|
|
||||||
let mut header_buf = [0u8; 1024 * 8];
|
|
||||||
let mut read_pos = 0usize;
|
|
||||||
|
|
||||||
let mut headers;
|
|
||||||
let (pos, req) = loop {
|
|
||||||
headers = [httparse::EMPTY_HEADER; 16];
|
|
||||||
let mut req = httparse::Request::new(&mut headers);
|
|
||||||
|
|
||||||
let n = client
|
|
||||||
.read(&mut header_buf[read_pos..])
|
|
||||||
.map_err(E::HeadReadError)?;
|
|
||||||
read_pos += n;
|
|
||||||
|
|
||||||
if let Ok(httparse::Status::Complete(n)) = req.parse(&header_buf[0..read_pos]) {
|
|
||||||
break (n - DOUBLE_CRLF.len(), req);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let theres_body = req.headers.has_any(["conten-length", "transfer-encoding"]);
|
|
||||||
|
|
||||||
let host_header = req.headers.get("host").ok_or(E::HostHeaderNotFound)?;
|
|
||||||
let cookies = req.headers.get("cookie");
|
|
||||||
|
|
||||||
// Now find that header and pass everything
|
|
||||||
let read_hosts = config.hosts.read();
|
|
||||||
let (addr, id_keyname, csrf_keyname) = read_hosts
|
|
||||||
.get(host_header.as_ref())
|
|
||||||
.ok_or_else(|| E::HostNotRegistered(host_header.to_string()))?
|
|
||||||
.clone();
|
|
||||||
drop(read_hosts);
|
|
||||||
|
|
||||||
// Try to find auth data
|
|
||||||
let id_auth = if let Some(cookies) = cookies {
|
|
||||||
if let Some(id_ck) = cookie::Cookie::split_parse(cookies)
|
|
||||||
.filter_map(Result::ok)
|
|
||||||
.find(|ck| ck.name() == id_keyname)
|
|
||||||
{
|
|
||||||
let id = id_ck.value().to_string();
|
|
||||||
let val = AUTH_MAP.get(&id);
|
|
||||||
Some((id, val))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
let action = what_to_do(&req, id_auth, (addr, id_keyname, csrf_keyname));
|
|
||||||
if let Some((cb1, cb2)) = action.capture_body_and_hijack {
|
|
||||||
let mut body = header_buf[pos..read_pos].to_vec();
|
|
||||||
if theres_body {
|
|
||||||
io::copy(client, &mut body).map_err(|err| E::BufferError("body", err))?;
|
|
||||||
}
|
|
||||||
cb1(&req, &body[..])?;
|
|
||||||
cb2(&req, client)?;
|
|
||||||
} else {
|
|
||||||
let mut stream = TcpStream::connect(addr).map_err(E::BackendConnectFail)?;
|
|
||||||
stream
|
|
||||||
.write_all(&header_buf[0..pos])
|
|
||||||
.map_err(E::ExchangeIoError)?;
|
|
||||||
if let Some(auth) = action.auth {
|
|
||||||
auth.write_as_headers(&mut stream)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// send our overhead
|
|
||||||
stream
|
|
||||||
.write_all(&header_buf[pos..read_pos])
|
|
||||||
.map_err(E::ExchangeIoError)?;
|
|
||||||
if theres_body {
|
|
||||||
io::copy(client, &mut stream).map_err(E::ExchangeIoError)?;
|
|
||||||
}
|
|
||||||
if let Some(cb) = action.capture_response {
|
|
||||||
let mut buf = Vec::new();
|
|
||||||
stream
|
|
||||||
.read_to_end(&mut buf)
|
|
||||||
.map_err(|err| E::BufferError("response", err))?;
|
|
||||||
cb(&req, &buf[..])?;
|
|
||||||
client.write_all(&buf[..]).map_err(E::ExchangeIoError)?;
|
|
||||||
} else {
|
|
||||||
io::copy(&mut stream, client).map_err(E::ExchangeIoError)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
18
src/utils.rs
18
src/utils.rs
@ -1,7 +1,10 @@
|
|||||||
pub mod headers {
|
pub mod headers {
|
||||||
use std::borrow::Cow;
|
use std::borrow::{Borrow, Cow};
|
||||||
|
|
||||||
pub trait HeadersExt<'a> {
|
pub trait HeadersExt<'a> {
|
||||||
|
fn expect_body(&self) -> bool {
|
||||||
|
self.has_any(["conten-length", "transfer-encoding"])
|
||||||
|
}
|
||||||
fn has_any(&self, headers: impl IntoIterator<Item = &'_ str>) -> bool {
|
fn has_any(&self, headers: impl IntoIterator<Item = &'_ str>) -> bool {
|
||||||
headers.into_iter().any(|h| self.has(h))
|
headers.into_iter().any(|h| self.has(h))
|
||||||
}
|
}
|
||||||
@ -9,16 +12,19 @@ pub mod headers {
|
|||||||
headers.into_iter().all(|h| self.has(h))
|
headers.into_iter().all(|h| self.has(h))
|
||||||
}
|
}
|
||||||
fn has(&self, header: &str) -> bool;
|
fn has(&self, header: &str) -> bool;
|
||||||
fn get(&self, header: &str) -> Option<Cow<'a, str>>;
|
fn header(&self, header: &str) -> Option<Cow<'a, str>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> HeadersExt<'a> for &mut [httparse::Header<'a>] {
|
impl<'a, T: Borrow<[httparse::Header<'a>]>> HeadersExt<'a> for T {
|
||||||
fn has(&self, header: &str) -> bool {
|
fn has(&self, header: &str) -> bool {
|
||||||
self.iter().any(|h| h.name.eq_ignore_ascii_case(header))
|
self.borrow()
|
||||||
|
.iter()
|
||||||
|
.any(|h| h.name.eq_ignore_ascii_case(header))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, header: &str) -> Option<Cow<'a, str>> {
|
fn header(&self, header: &str) -> Option<Cow<'a, str>> {
|
||||||
self.iter()
|
self.borrow()
|
||||||
|
.iter()
|
||||||
.find(|h| h.name.eq_ignore_ascii_case(header))
|
.find(|h| h.name.eq_ignore_ascii_case(header))
|
||||||
.map(|h| String::from_utf8_lossy(h.value))
|
.map(|h| String::from_utf8_lossy(h.value))
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user