Implement http

repl
Ondřej Hruška 3 weeks ago
parent 8efdcab812
commit d79581530b
  1. 395
      Cargo.lock
  2. 9
      Cargo.toml
  3. 66
      lua/stdlib/http.lua
  4. 529
      src/stdlib/http.rs
  5. 2
      src/stdlib/mod.rs
  6. 4
      src/stdlib/utils.rs

395
Cargo.lock generated

@ -8,11 +8,13 @@ version = "0.1.0"
dependencies = [ dependencies = [
"chrono", "chrono",
"clap", "clap",
"digest_auth",
"env_logger", "env_logger",
"log", "log",
"mlua", "mlua",
"reqwest", "reqwest",
"rusqlite", "rusqlite",
"rustls",
"serde", "serde",
"serde_json", "serde_json",
"thiserror", "thiserror",
@ -117,28 +119,6 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "aws-lc-rs"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ec2f1fc3ec205783a5da9a7e6c1509cc69dedf09a1949e412c1e18469326d00"
dependencies = [
"aws-lc-sys",
"zeroize",
]
[[package]]
name = "aws-lc-sys"
version = "0.41.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a2f9779ce85b93ab6170dd940ad0169b5766ff848247aff13bb788b832fe3f4"
dependencies = [
"cc",
"cmake",
"dunce",
"fs_extra",
]
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.22.1" version = "0.22.1"
@ -157,6 +137,15 @@ version = "2.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8" checksum = "b4388bee8683e3d04af747c73422af53102d2bd24d9eadb6cbc100baef4b43f8"
[[package]]
name = "block-buffer"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
dependencies = [
"generic-array",
]
[[package]] [[package]]
name = "bstr" name = "bstr"
version = "1.12.1" version = "1.12.1"
@ -186,8 +175,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f" checksum = "dad887fd958be91b5098c0248def011f4523ab786cd411be668777e55063501f"
dependencies = [ dependencies = [
"find-msvc-tools", "find-msvc-tools",
"jobserver",
"libc",
"shlex", "shlex",
] ]
@ -197,12 +184,6 @@ version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.45" version = "0.4.45"
@ -256,15 +237,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
[[package]]
name = "cmake"
version = "0.1.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c0f78a02292a74a88ac736019ab962ece0bc380e3f977bf72e376c5d78ff0678"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "colorchoice" name = "colorchoice"
version = "1.0.5" version = "1.0.5"
@ -307,6 +279,25 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a"
dependencies = [
"generic-array",
"typenum",
]
[[package]] [[package]]
name = "defmt" name = "defmt"
version = "1.1.0" version = "1.1.0"
@ -339,6 +330,29 @@ dependencies = [
"thiserror", "thiserror",
] ]
[[package]]
name = "digest"
version = "0.10.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
dependencies = [
"block-buffer",
"crypto-common",
]
[[package]]
name = "digest_auth"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3054f4e81d395e50822796c5e99ca522e6ba7be98947d6d4b0e5e61640bdb894"
dependencies = [
"digest",
"hex",
"md-5",
"rand",
"sha2",
]
[[package]] [[package]]
name = "displaydoc" name = "displaydoc"
version = "0.2.6" version = "0.2.6"
@ -350,12 +364,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "dunce"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813"
[[package]] [[package]]
name = "either" name = "either"
version = "1.16.0" version = "1.16.0"
@ -454,12 +462,6 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "fs_extra"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42703706b716c37f96a77aea830392ad231f44c9e9a67872fa5548707e11b11c"
[[package]] [[package]]
name = "futures-channel" name = "futures-channel"
version = "0.3.32" version = "0.3.32"
@ -500,30 +502,24 @@ dependencies = [
] ]
[[package]] [[package]]
name = "getrandom" name = "generic-array"
version = "0.2.17" version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [ dependencies = [
"cfg-if", "typenum",
"js-sys", "version_check",
"libc",
"wasi",
"wasm-bindgen",
] ]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.3.4" version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys",
"libc", "libc",
"r-efi", "wasi",
"wasip2",
"wasm-bindgen",
] ]
[[package]] [[package]]
@ -575,6 +571,12 @@ 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 = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]] [[package]]
name = "http" name = "http"
version = "1.4.2" version = "1.4.2"
@ -904,16 +906,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "jobserver"
version = "0.1.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33"
dependencies = [
"getrandom 0.3.4",
"libc",
]
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.102" version = "0.3.102"
@ -963,12 +955,6 @@ version = "0.4.32"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a" checksum = "953f07c43838f8e6f9758cab68bf5bed85465e7587ebe0b823f1bcd81978ad3a"
[[package]]
name = "lru-slab"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "112b39cec0b298b6c1999fee3e31427f74f676e4cb9879ed1a121b43661a4154"
[[package]] [[package]]
name = "lua-src" name = "lua-src"
version = "550.0.0" version = "550.0.0"
@ -988,6 +974,16 @@ dependencies = [
"which", "which",
] ]
[[package]]
name = "md-5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest",
]
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.8.2" version = "2.8.2"
@ -1187,62 +1183,6 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "quinn"
version = "0.11.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20"
dependencies = [
"bytes",
"cfg_aliases",
"pin-project-lite",
"quinn-proto",
"quinn-udp",
"rustc-hash",
"rustls",
"socket2",
"thiserror",
"tokio",
"tracing",
"web-time",
]
[[package]]
name = "quinn-proto"
version = "0.11.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "434b42fec591c96ef50e21e886936e66d3cc3f737104fdb9b737c40ffb94c098"
dependencies = [
"aws-lc-rs",
"bytes",
"getrandom 0.3.4",
"lru-slab",
"rand",
"ring",
"rustc-hash",
"rustls",
"rustls-pki-types",
"slab",
"thiserror",
"tinyvec",
"tracing",
"web-time",
]
[[package]]
name = "quinn-udp"
version = "0.5.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "addec6a0dcad8a8d96a771f815f0eaf55f9d1805756410b39f5fa81332574cbd"
dependencies = [
"cfg_aliases",
"libc",
"once_cell",
"socket2",
"tracing",
"windows-sys 0.60.2",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.45" version = "1.0.45"
@ -1252,27 +1192,22 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "r-efi"
version = "5.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
[[package]] [[package]]
name = "rand" name = "rand"
version = "0.9.4" version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a"
dependencies = [ dependencies = [
"libc",
"rand_chacha", "rand_chacha",
"rand_core", "rand_core",
] ]
[[package]] [[package]]
name = "rand_chacha" name = "rand_chacha"
version = "0.9.0" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [ dependencies = [
"ppv-lite86", "ppv-lite86",
"rand_core", "rand_core",
@ -1280,11 +1215,11 @@ dependencies = [
[[package]] [[package]]
name = "rand_core" name = "rand_core"
version = "0.9.5" version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [ dependencies = [
"getrandom 0.3.4", "getrandom",
] ]
[[package]] [[package]]
@ -1347,7 +1282,6 @@ dependencies = [
"mime", "mime",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"quinn",
"rustls", "rustls",
"rustls-pki-types", "rustls-pki-types",
"rustls-platform-verifier", "rustls-platform-verifier",
@ -1371,7 +1305,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7"
dependencies = [ dependencies = [
"cc", "cc",
"cfg-if", "cfg-if",
"getrandom 0.2.17", "getrandom",
"libc", "libc",
"untrusted", "untrusted",
"windows-sys 0.52.0", "windows-sys 0.52.0",
@ -1412,8 +1346,8 @@ version = "0.23.40"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b" checksum = "ef86cd5876211988985292b91c96a8f2d298df24e75989a43a3c73f2d4d8168b"
dependencies = [ dependencies = [
"aws-lc-rs",
"once_cell", "once_cell",
"ring",
"rustls-pki-types", "rustls-pki-types",
"rustls-webpki", "rustls-webpki",
"subtle", "subtle",
@ -1438,7 +1372,6 @@ version = "1.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9" checksum = "30a7197ae7eb376e574fe940d068c30fe0462554a3ddbe4eca7838e049c937a9"
dependencies = [ dependencies = [
"web-time",
"zeroize", "zeroize",
] ]
@ -1475,7 +1408,6 @@ version = "0.103.13"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e"
dependencies = [ dependencies = [
"aws-lc-rs",
"ring", "ring",
"rustls-pki-types", "rustls-pki-types",
"untrusted", "untrusted",
@ -1593,6 +1525,17 @@ dependencies = [
"zmij", "zmij",
] ]
[[package]]
name = "sha2"
version = "0.10.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283"
dependencies = [
"cfg-if",
"cpufeatures",
"digest",
]
[[package]] [[package]]
name = "shlex" name = "shlex"
version = "2.0.1" version = "2.0.1"
@ -1747,21 +1690,6 @@ dependencies = [
"zerovec", "zerovec",
] ]
[[package]]
name = "tinyvec"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3"
dependencies = [
"tinyvec_macros",
]
[[package]]
name = "tinyvec_macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.52.3" version = "1.52.3"
@ -1889,6 +1817,12 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]]
name = "typenum"
version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.24" version = "1.0.24"
@ -1962,15 +1896,6 @@ version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]]
name = "wasip2"
version = "1.0.4+wasi-0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487"
dependencies = [
"wit-bindgen",
]
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.125" version = "0.2.125"
@ -2036,21 +1961,11 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "web-time"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb"
dependencies = [
"js-sys",
"wasm-bindgen",
]
[[package]] [[package]]
name = "webpki-root-certs" name = "webpki-root-certs"
version = "1.0.7" version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31141ce3fc3e300ae89b78c0dd67f9708061d1d2eda54b8209346fd6be9a92c" checksum = "0d46a5a140e6f7afeccd8eae97eff335163939eac8b929834875168b29b3d267"
dependencies = [ dependencies = [
"rustls-pki-types", "rustls-pki-types",
] ]
@ -2149,16 +2064,7 @@ version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [ dependencies = [
"windows-targets 0.52.6", "windows-targets",
]
[[package]]
name = "windows-sys"
version = "0.60.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2f500e4d28234f72040990ec9d39e3a6b950f9f22d3dba18416c35882612bcb"
dependencies = [
"windows-targets 0.53.5",
] ]
[[package]] [[package]]
@ -2176,31 +2082,14 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [ dependencies = [
"windows_aarch64_gnullvm 0.52.6", "windows_aarch64_gnullvm",
"windows_aarch64_msvc 0.52.6", "windows_aarch64_msvc",
"windows_i686_gnu 0.52.6", "windows_i686_gnu",
"windows_i686_gnullvm 0.52.6", "windows_i686_gnullvm",
"windows_i686_msvc 0.52.6", "windows_i686_msvc",
"windows_x86_64_gnu 0.52.6", "windows_x86_64_gnu",
"windows_x86_64_gnullvm 0.52.6", "windows_x86_64_gnullvm",
"windows_x86_64_msvc 0.52.6", "windows_x86_64_msvc",
]
[[package]]
name = "windows-targets"
version = "0.53.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4945f9f551b88e0d65f3db0bc25c33b8acea4d9e41163edf90dcd0b19f9069f3"
dependencies = [
"windows-link",
"windows_aarch64_gnullvm 0.53.1",
"windows_aarch64_msvc 0.53.1",
"windows_i686_gnu 0.53.1",
"windows_i686_gnullvm 0.53.1",
"windows_i686_msvc 0.53.1",
"windows_x86_64_gnu 0.53.1",
"windows_x86_64_gnullvm 0.53.1",
"windows_x86_64_msvc 0.53.1",
] ]
[[package]] [[package]]
@ -2209,102 +2098,48 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53"
[[package]] [[package]]
name = "windows_aarch64_msvc" name = "windows_aarch64_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_aarch64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006"
[[package]] [[package]]
name = "windows_i686_gnu" name = "windows_i686_gnu"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "960e6da069d81e09becb0ca57a65220ddff016ff2d6af6a223cf372a506593a3"
[[package]] [[package]]
name = "windows_i686_gnullvm" name = "windows_i686_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c"
[[package]] [[package]]
name = "windows_i686_msvc" name = "windows_i686_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_i686_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2"
[[package]] [[package]]
name = "windows_x86_64_gnu" name = "windows_x86_64_gnu"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnu"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499"
[[package]] [[package]]
name = "windows_x86_64_gnullvm" name = "windows_x86_64_gnullvm"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1"
[[package]] [[package]]
name = "windows_x86_64_msvc" name = "windows_x86_64_msvc"
version = "0.52.6" version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "windows_x86_64_msvc"
version = "0.53.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650"
[[package]]
name = "wit-bindgen"
version = "0.57.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
[[package]] [[package]]
name = "writeable" name = "writeable"
version = "0.6.3" version = "0.6.3"

@ -10,9 +10,16 @@ env_logger = "0.11"
log = "0.4" log = "0.4"
clap = { version = "4.6.1", features = ["derive"] } clap = { version = "4.6.1", features = ["derive"] }
mlua = { version = "0.11.6", features = ["lua55", "vendored", "serde", "async", "anyhow"]} mlua = { version = "0.11.6", features = ["lua55", "vendored", "serde", "async", "anyhow"]}
reqwest = "0.13.4" # reqwest's default rustls provider (aws-lc-rs) null-derefs during the TLS
# handshake in this environment, and native-tls would link system OpenSSL.
# Use rustls with the self-contained, vendored `ring` provider (installed as the
# process default in stdlib::http). `rustls-no-provider` stops reqwest from
# pulling aws-lc-rs back in.
reqwest = { version = "0.13.4", default-features = false, features = ["rustls-no-provider", "charset", "http2", "system-proxy"] }
rustls = { version = "0.23", default-features = false, features = ["ring", "std", "tls12"] }
rusqlite = { version = "0.32", features = ["bundled"] } rusqlite = { version = "0.32", features = ["bundled"] }
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.150" serde_json = "1.0.150"
thiserror = "2.0.18" thiserror = "2.0.18"
tokio = { version = "1.52.3", features = ["full"] } tokio = { version = "1.52.3", features = ["full"] }
digest_auth = "0.3"

@ -0,0 +1,66 @@
-- Lua-side of the http module.
-- http.request (stateless) and http.session (constructor) are provided by Rust
-- before this runs. This layer adds resp.json(), method shorthands, the
-- JSON/postJSON helpers, and a metatable for session objects.
-- Attach resp.json() to a response table: parses resp.body via utils.fromJSON.
local function wrap_resp(resp)
resp.json = function() return utils.fromJSON(resp.body) end
return resp
end
-- Wrap the stateless http.request so responses carry resp.json().
local _request = http.request
http.request = function(method, url, opts)
return wrap_resp(_request(method, url, opts))
end
-- Method shorthands for the stateless module.
for _, m in ipairs({ "get", "post", "put", "patch", "delete", "head" }) do
http[m] = function(url, opts) return http.request(m:upper(), url, opts) end
end
function http.getJSON(url, opts)
local resp = http.get(url, opts)
return resp.json(), resp
end
function http.postJSON(url, body, opts)
opts = opts or {}
opts.json = body
return http.post(url, opts)
end
-- Session metatable. http.session() returns a raw Rust table whose methods are
-- _request, save, load, clearCookies and cookies. The metatable adds the
-- request wrapper (for resp.json()) and the method shorthands on top.
local session_mt = {}
session_mt.__index = session_mt
function session_mt:request(method, url, opts)
return wrap_resp(self:_request(method, url, opts))
end
function session_mt:get(url, opts) return self:request("GET", url, opts) end
function session_mt:post(url, opts) return self:request("POST", url, opts) end
function session_mt:put(url, opts) return self:request("PUT", url, opts) end
function session_mt:patch(url, opts) return self:request("PATCH", url, opts) end
function session_mt:delete(url, opts) return self:request("DELETE", url, opts) end
function session_mt:head(url, opts) return self:request("HEAD", url, opts) end
function session_mt:getJSON(url, opts)
local resp = self:get(url, opts)
return resp.json(), resp
end
function session_mt:postJSON(url, body, opts)
opts = opts or {}
opts.json = body
return self:post(url, opts)
end
-- Wrap the Rust session constructor to install the metatable.
local _session = http.session
http.session = function(path)
return setmetatable(_session(path), session_mt)
end

@ -0,0 +1,529 @@
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use mlua::prelude::LuaResult;
use mlua::{Lua, Table as LuaTable, Value as LuaValue};
use crate::stdlib::utils::lua_to_json;
const HTTP_LUA: &str = include_str!("../../lua/stdlib/http.lua");
// ---------------------------------------------------------------------------
// Cookie jar
// ---------------------------------------------------------------------------
/// A minimal in-memory cookie jar: bare domain (no leading dot) → name → value.
/// Deliberately simple — it covers the >95% case of `Set-Cookie` flows without
/// pulling in the `cookie_store` crate, and serializes cleanly to JSONL.
#[derive(Default)]
struct CookieJar {
cookies: HashMap<String, HashMap<String, String>>,
}
impl CookieJar {
/// All cookies (name, value) whose stored domain matches `host`: either an
/// exact match or `host` being a subdomain of the stored domain.
fn cookies_for(&self, host: &str) -> Vec<(String, String)> {
let mut out = Vec::new();
for (domain, names) in &self.cookies {
let suffix = format!(".{domain}");
if host == domain || host.ends_with(&suffix) {
for (name, value) in names {
out.push((name.clone(), value.clone()));
}
}
}
out
}
/// Parse a single `Set-Cookie` header value and store the cookie. Extracts
/// `name=value` (first segment) and an optional `Domain=` attribute, falling
/// back to the request host. Malformed headers are ignored.
fn set_from_header(&mut self, host: &str, header: &str) {
let mut segments = header.split(';');
let first = match segments.next() {
Some(s) => s.trim(),
None => return,
};
let (name, value) = match first.split_once('=') {
Some((n, v)) => (n.trim(), v.trim()),
None => return,
};
if name.is_empty() {
return;
}
let mut domain = host.to_ascii_lowercase();
for seg in segments {
if let Some((k, v)) = seg.split_once('=')
&& k.trim().eq_ignore_ascii_case("domain")
{
let d = v.trim().trim_start_matches('.').to_ascii_lowercase();
if !d.is_empty() {
domain = d;
}
}
}
self.cookies
.entry(domain)
.or_default()
.insert(name.to_string(), value.to_string());
}
/// Serialize as JSONL — one `{"domain","name","value"}` object per line.
fn to_jsonl(&self) -> String {
let mut out = String::new();
for (domain, names) in &self.cookies {
for (name, value) in names {
let obj = serde_json::json!({
"domain": domain,
"name": name,
"value": value,
});
out.push_str(&obj.to_string());
out.push('\n');
}
}
out
}
/// Merge cookies from JSONL produced by `to_jsonl`. Bad lines are skipped.
fn merge_jsonl(&mut self, src: &str) {
for line in src.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
let Ok(v) = serde_json::from_str::<serde_json::Value>(line) else {
continue;
};
let domain = v.get("domain").and_then(|x| x.as_str());
let name = v.get("name").and_then(|x| x.as_str());
let value = v.get("value").and_then(|x| x.as_str());
if let (Some(d), Some(n), Some(val)) = (domain, name, value) {
self.cookies
.entry(d.to_string())
.or_default()
.insert(n.to_string(), val.to_string());
}
}
}
}
// ---------------------------------------------------------------------------
// Request execution
// ---------------------------------------------------------------------------
/// `application/x-www-form-urlencoded` body from key/value pairs. Implemented
/// inline so the build needs no optional reqwest features.
fn form_urlencode(pairs: &[(String, String)]) -> String {
fn encode(s: &str) -> String {
let mut out = String::new();
for b in s.bytes() {
match b {
b'A'..=b'Z' | b'a'..=b'z' | b'0'..=b'9' | b'-' | b'_' | b'.' | b'~' => {
out.push(b as char)
}
b' ' => out.push('+'),
_ => out.push_str(&format!("%{b:02X}")),
}
}
out
}
pairs
.iter()
.map(|(k, v)| format!("{}={}", encode(k), encode(v)))
.collect::<Vec<_>>()
.join("&")
}
const MAX_REDIRECTS: u32 = 10;
/// Shared by the stateless `http.request` and a session's `:request`. When `jar`
/// is `Some`, the matching jar cookies are sent and any `Set-Cookie` responses
/// are stored back.
///
/// Redirects are followed manually (the client is built with
/// `redirect::Policy::none`) so that `Set-Cookie` headers on 30x responses —
/// the common login → redirect → dashboard pattern — are captured into the jar,
/// which reqwest's transparent redirect following would otherwise hide.
async fn execute_request(
lua: Lua,
client: reqwest::Client,
jar: Option<Arc<Mutex<CookieJar>>>,
method: String,
url: String,
opts: Option<LuaTable>,
) -> LuaResult<LuaTable> {
let mut method = reqwest::Method::from_bytes(method.to_ascii_uppercase().as_bytes())
.map_err(|e| mlua::Error::external(format!("http: invalid method '{method}': {e}")))?;
// Parse opts once into owned pieces so each redirect hop can rebuild the
// request (a reqwest RequestBuilder is single-use).
let mut timeout: Option<Duration> = None;
let mut custom_headers: Vec<(String, String)> = Vec::new();
let mut opts_cookies: Vec<(String, String)> = Vec::new();
// Body, with the Content-Type it implies (None for a raw body).
let mut body: Option<(Vec<u8>, Option<&'static str>)> = None;
// Auth: (is_digest, username, password).
let mut auth: Option<(bool, String, String)> = None;
if let Some(opts) = opts.as_ref() {
if let Some(t) = opts.get::<Option<f64>>("timeout")? {
timeout = Some(Duration::try_from_secs_f64(t).map_err(|_| {
mlua::Error::external(
"http: timeout must be a non-negative finite number of seconds",
)
})?);
}
if let Some(headers) = opts.get::<Option<LuaTable>>("headers")? {
for pair in headers.pairs::<String, String>() {
custom_headers.push(pair?);
}
}
if let Some(cookies) = opts.get::<Option<LuaTable>>("cookies")? {
for pair in cookies.pairs::<String, String>() {
opts_cookies.push(pair?);
}
}
// Body: json > form > raw body (first one present wins).
if let Some(json_val) = opts.get::<Option<LuaValue>>("json")? {
let json = lua_to_json(json_val, 0)?;
let s = serde_json::to_string(&json).map_err(mlua::Error::external)?;
body = Some((s.into_bytes(), Some("application/json")));
} else if let Some(form) = opts.get::<Option<LuaTable>>("form")? {
let mut pairs = Vec::new();
for pair in form.pairs::<String, String>() {
pairs.push(pair?);
}
body = Some((
form_urlencode(&pairs).into_bytes(),
Some("application/x-www-form-urlencoded"),
));
} else if let Some(b) = opts.get::<Option<mlua::String>>("body")? {
body = Some((b.as_bytes().to_vec(), None));
}
// Auth: { username, password, scheme = "basic" (default) | "digest" }.
if let Some(auth_tbl) = opts.get::<Option<LuaTable>>("auth")? {
let username = auth_tbl
.get::<Option<String>>("username")?
.ok_or_else(|| mlua::Error::external("http: auth.username is required"))?;
let password = auth_tbl
.get::<Option<String>>("password")?
.ok_or_else(|| mlua::Error::external("http: auth.password is required"))?;
let is_digest = match auth_tbl.get::<Option<String>>("scheme")?.as_deref() {
None | Some("basic") => false,
Some("digest") => true,
Some(other) => {
return Err(mlua::Error::external(format!(
"http: auth.scheme must be \"basic\" or \"digest\", got \"{other}\""
)));
}
};
auth = Some((is_digest, username, password));
}
}
let is_digest = matches!(auth.as_ref(), Some((true, _, _)));
let mut digest_header: Option<String> = None;
let mut digest_tried = false;
let mut url = url;
let mut redirects_left = MAX_REDIRECTS;
let resp = loop {
// Host used for cookie matching and as the Set-Cookie domain fallback;
// recomputed each hop since a redirect may cross hosts.
let host = reqwest::Url::parse(&url)
.ok()
.and_then(|u| u.host_str().map(|h| h.to_ascii_lowercase()));
let mut req = client.request(method.clone(), &url);
if let Some(t) = timeout {
req = req.timeout(t);
}
for (k, v) in &custom_headers {
req = req.header(k, v);
}
// Cookie header: jar cookies for this host, then per-request cookies
// which override on name collision.
let mut cookie_map: HashMap<String, String> = HashMap::new();
if let (Some(jar), Some(host)) = (jar.as_ref(), host.as_ref()) {
for (n, v) in jar.lock().unwrap().cookies_for(host) {
cookie_map.insert(n, v);
}
}
for (k, v) in &opts_cookies {
cookie_map.insert(k.clone(), v.clone());
}
if !cookie_map.is_empty() {
let header = cookie_map
.iter()
.map(|(k, v)| format!("{k}={v}"))
.collect::<Vec<_>>()
.join("; ");
req = req.header(reqwest::header::COOKIE, header);
}
if let Some((bytes, ct)) = body.as_ref() {
if let Some(ct) = ct {
req = req.header(reqwest::header::CONTENT_TYPE, *ct);
}
req = req.body(bytes.clone());
}
// Auth. Basic goes out on every hop; digest's Authorization is set only
// after the 401 challenge below has been answered (digest_header).
if let Some((digest, username, password)) = auth.as_ref() {
if *digest {
if let Some(h) = digest_header.as_ref() {
req = req.header(reqwest::header::AUTHORIZATION, h);
}
} else {
req = req.basic_auth(username, Some(password));
}
}
let resp = req
.send()
.await
.map_err(|e| mlua::Error::external(format!("http: {e}")))?;
let status = resp.status();
// Store Set-Cookie from this hop into the jar.
if let (Some(jar), Some(host)) = (jar.as_ref(), host.as_ref()) {
let mut j = jar.lock().unwrap();
for value in resp.headers().get_all(reqwest::header::SET_COOKIE).iter() {
if let Ok(s) = value.to_str() {
j.set_from_header(host, s);
}
}
}
// Digest auth: answer a 401 challenge once, then retry the same request
// with the computed Authorization header.
if is_digest && !digest_tried && status == reqwest::StatusCode::UNAUTHORIZED {
let challenge = resp
.headers()
.get(reqwest::header::WWW_AUTHENTICATE)
.and_then(|v| v.to_str().ok())
.map(str::to_string);
if let Some((_, username, password)) = auth.as_ref()
&& let Some(challenge) = challenge
&& let Ok(mut prompt) = digest_auth::parse(&challenge)
{
let uri = reqwest::Url::parse(&url)
.map(|u| match u.query() {
Some(q) => format!("{}?{}", u.path(), q),
None => u.path().to_string(),
})
.unwrap_or_else(|_| url.clone());
let ctx = digest_auth::AuthContext::new_with_method(
username.as_str(),
password.as_str(),
uri,
body.as_ref().map(|(b, _)| b.as_slice()),
digest_auth::HttpMethod::from(method.as_str()),
);
if let Ok(answer) = prompt.respond(&ctx) {
digest_header = Some(answer.to_header_string());
digest_tried = true;
continue;
}
}
}
// Follow a redirect if there is one to follow.
if status.is_redirection()
&& redirects_left > 0
&& let Some(next) = resp
.headers()
.get(reqwest::header::LOCATION)
.and_then(|l| l.to_str().ok())
.and_then(|loc| reqwest::Url::parse(&url).and_then(|base| base.join(loc)).ok())
{
redirects_left -= 1;
// 303, and 301/302 on a POST, degrade to a bodyless GET — the
// behaviour browsers and reqwest's own redirect policy apply.
let code = status.as_u16();
if code == 303 || ((code == 301 || code == 302) && method == reqwest::Method::POST) {
method = reqwest::Method::GET;
body = None;
}
url = next.to_string();
continue;
}
break resp;
};
let status = resp.status().as_u16();
// Response headers: lowercase names, first value wins.
let headers_tbl = lua.create_table()?;
for (name, value) in resp.headers().iter() {
let lname = name.as_str().to_ascii_lowercase();
if !headers_tbl.contains_key(lname.as_str())? {
headers_tbl.raw_set(lname.as_str(), lua.create_string(value.as_bytes())?)?;
}
}
let bytes = resp
.bytes()
.await
.map_err(|e| mlua::Error::external(format!("http: {e}")))?;
let out = lua.create_table()?;
out.raw_set("status", status)?;
out.raw_set("ok", (200..=299).contains(&status))?;
out.raw_set("headers", headers_tbl)?;
out.raw_set("body", lua.create_string(&bytes)?)?;
Ok(out)
}
// ---------------------------------------------------------------------------
// Session
// ---------------------------------------------------------------------------
/// Build the plain Lua table that backs a session. State (the cookie jar) lives
/// in the closures; the Lua layer applies a metatable for the method shorthands.
/// Methods are called as `s:method(...)`, so each closure receives the session
/// table as a leading `_this` argument that it ignores.
fn make_session(
lua: &Lua,
client: reqwest::Client,
jar: Arc<Mutex<CookieJar>>,
) -> LuaResult<LuaTable> {
let tbl = lua.create_table()?;
tbl.raw_set(
"_request",
lua.create_async_function({
let client = client.clone();
let jar = jar.clone();
move |lua, (_this, method, url, opts): (LuaTable, String, String, Option<LuaTable>)| {
let client = client.clone();
let jar = jar.clone();
async move {
execute_request(lua, client, Some(jar), method, url, opts).await
}
}
})?,
)?;
tbl.raw_set("save", {
let jar = jar.clone();
lua.create_function(move |_, (_this, path): (LuaTable, String)| {
let jsonl = jar.lock().unwrap().to_jsonl();
std::fs::write(&path, jsonl)
.map_err(|e| mlua::Error::external(format!("session:save: {e}")))
})?
})?;
tbl.raw_set("load", {
let jar = jar.clone();
lua.create_function(move |_, (_this, path): (LuaTable, String)| {
let src = std::fs::read_to_string(&path)
.map_err(|e| mlua::Error::external(format!("session:load: {e}")))?;
jar.lock().unwrap().merge_jsonl(&src);
Ok(())
})?
})?;
tbl.raw_set("clearCookies", {
let jar = jar.clone();
lua.create_function(move |_, _this: LuaTable| {
jar.lock().unwrap().cookies.clear();
Ok(())
})?
})?;
tbl.raw_set("cookies", {
let jar = jar.clone();
lua.create_function(move |lua, _this: LuaTable| {
let outer = lua.create_table()?;
let j = jar.lock().unwrap();
for (domain, names) in j.cookies.iter() {
let inner = lua.create_table()?;
for (name, value) in names.iter() {
inner.raw_set(name.as_str(), value.as_str())?;
}
outer.raw_set(domain.as_str(), inner)?;
}
Ok(outer)
})?
})?;
Ok(tbl)
}
// ---------------------------------------------------------------------------
// Installation
// ---------------------------------------------------------------------------
pub(super) fn install(lua: &Lua) -> LuaResult<()> {
// reqwest is built with `rustls-no-provider`, so it has no crypto provider
// of its own and panics ("No provider set") unless one is installed as the
// process default before the client is built. `ring` is self-contained
// (compiled in, no system OpenSSL). Idempotent across Lua states — only the
// first install in the process wins, and they would all install ring.
let _ = rustls::crypto::ring::default_provider().install_default();
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
// reqwest sends no User-Agent by default; some edges/CDNs reject
// empty-UA requests outright. A per-request `headers` entry overrides it.
.user_agent(concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")))
// Redirects are followed manually in execute_request so Set-Cookie
// headers on 30x responses can be captured into the cookie jar.
.redirect(reqwest::redirect::Policy::none())
.build()
.map_err(mlua::Error::external)?;
let http = lua.create_table()?;
// Stateless http.request — no cookie jar.
http.raw_set(
"request",
lua.create_async_function({
let client = client.clone();
move |lua, (method, url, opts): (String, String, Option<LuaTable>)| {
let client = client.clone();
async move { execute_request(lua, client, None, method, url, opts).await }
}
})?,
)?;
// http.session(path?) — constructor returning the raw session table.
http.raw_set(
"session",
lua.create_async_function({
let client = client.clone();
move |lua, path: Option<String>| {
let client = client.clone();
async move {
let mut jar = CookieJar::default();
if let Some(p) = path {
let src = tokio::fs::read_to_string(&p)
.await
.map_err(|e| mlua::Error::external(format!("http.session: {e}")))?;
jar.merge_jsonl(&src);
}
make_session(&lua, client, Arc::new(Mutex::new(jar)))
}
}
})?,
)?;
lua.globals().raw_set("http", http)?;
// Lua side adds: resp.json(), method shorthands, getJSON/postJSON, and the
// session metatable wrapping http.session().
lua.load(HTTP_LUA).set_name("@[stdlib/http]").exec()
}

@ -1,3 +1,4 @@
mod http;
mod logging; mod logging;
mod math; mod math;
mod os_ext; mod os_ext;
@ -12,5 +13,6 @@ pub(crate) fn install(lua: &mlua::Lua) -> mlua::Result<()> {
os_ext::install(lua)?; os_ext::install(lua)?;
logging::install(lua)?; logging::install(lua)?;
sqlite::install(lua)?; sqlite::install(lua)?;
http::install(lua)?;
Ok(()) Ok(())
} }

@ -21,7 +21,7 @@ pub(crate) fn is_null(val: &LuaValue) -> bool {
// Lua → JSON // Lua → JSON
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
fn lua_to_json(val: LuaValue, depth: u32) -> LuaResult<serde_json::Value> { pub(crate) fn lua_to_json(val: LuaValue, depth: u32) -> LuaResult<serde_json::Value> {
const MAX_DEPTH: u32 = 64; const MAX_DEPTH: u32 = 64;
if depth > MAX_DEPTH { if depth > MAX_DEPTH {
return Err(mlua::Error::external("utils.toJSON: nesting too deep (max 64 levels)")); return Err(mlua::Error::external("utils.toJSON: nesting too deep (max 64 levels)"));
@ -47,7 +47,7 @@ fn lua_to_json(val: LuaValue, depth: u32) -> LuaResult<serde_json::Value> {
} }
} }
fn table_to_json(t: mlua::Table, depth: u32) -> LuaResult<serde_json::Value> { pub(crate) fn table_to_json(t: mlua::Table, depth: u32) -> LuaResult<serde_json::Value> {
let seq_len = t.raw_len() as usize; let seq_len = t.raw_len() as usize;
// Try array serialization: every key must be a sequential integer in 1..=seq_len. // Try array serialization: every key must be a sequential integer in 1..=seq_len.

Loading…
Cancel
Save