Compare commits

..

No commits in common. "main" and "0.1.3" have entirely different histories.
main ... 0.1.3

13 changed files with 881 additions and 740 deletions

420
Cargo.lock generated
View File

@ -2,6 +2,21 @@
# It is not intended for manual editing.
version = 4
[[package]]
name = "addr2line"
version = "0.24.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfbe277e56a376000877090da837660b4427aad530e3028d44e0bffe4f89a1c1"
dependencies = [
"gimli",
]
[[package]]
name = "adler2"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
[[package]]
name = "aho-corasick"
version = "1.1.3"
@ -13,9 +28,9 @@ dependencies = [
[[package]]
name = "anstream"
version = "0.6.18"
version = "0.6.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526"
dependencies = [
"anstyle",
"anstyle-parse",
@ -28,44 +43,49 @@ dependencies = [
[[package]]
name = "anstyle"
version = "1.0.10"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1"
[[package]]
name = "anstyle-parse"
version = "0.2.6"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.2"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.7"
version = "3.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8"
dependencies = [
"anstyle",
"once_cell",
"windows-sys 0.59.0",
"windows-sys 0.52.0",
]
[[package]]
name = "anyhow"
version = "1.0.97"
version = "1.0.88"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcfed56ad506cb2c684a14971b8861fdc3baaaae314b9e5f9bb532cbe3ba7a4f"
checksum = "4e1496f8fb1fbf272686b8d37f523dab3e4a7443300055e74cdaa449f3114356"
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "aws-lc-rs"
@ -90,6 +110,21 @@ dependencies = [
"fs_extra",
]
[[package]]
name = "backtrace"
version = "0.3.74"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a"
dependencies = [
"addr2line",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
"windows-targets 0.52.6",
]
[[package]]
name = "bindgen"
version = "0.69.5"
@ -115,9 +150,15 @@ dependencies = [
[[package]]
name = "bitflags"
version = "2.9.0"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "bytes"
version = "1.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a"
[[package]]
name = "cc"
@ -178,18 +219,18 @@ dependencies = [
[[package]]
name = "colorchoice"
version = "1.0.3"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
[[package]]
name = "colored"
version = "2.2.0"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8"
dependencies = [
"lazy_static",
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]
@ -206,9 +247,9 @@ checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "env_filter"
version = "0.1.3"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab"
dependencies = [
"log",
"regex",
@ -229,9 +270,9 @@ dependencies = [
[[package]]
name = "equivalent"
version = "1.0.2"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]]
name = "errno"
@ -251,8 +292,11 @@ dependencies = [
"ignore-result",
"log",
"rustls",
"serde_json",
"serde_yml",
"threadpool",
"tokio",
"tokio-io-timeout",
"tokio-rustls",
"wildmatch",
]
@ -285,6 +329,12 @@ dependencies = [
"wasi 0.14.2+wasi-0.2.4",
]
[[package]]
name = "gimli"
version = "0.31.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f"
[[package]]
name = "glob"
version = "0.3.2"
@ -293,15 +343,9 @@ checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2"
[[package]]
name = "hashbrown"
version = "0.15.2"
version = "0.14.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
[[package]]
name = "home"
@ -320,9 +364,9 @@ checksum = "665ff4dce8edd10d490641ccb78949832f1ddbff02c584fb1f85ab888fe0e50c"
[[package]]
name = "indexmap"
version = "2.9.0"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e"
checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5"
dependencies = [
"equivalent",
"hashbrown",
@ -345,15 +389,15 @@ dependencies = [
[[package]]
name = "itoa"
version = "1.0.15"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "jiff"
version = "0.2.6"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f33145a5cbea837164362c7bd596106eb7c5198f97d1ba6f6ebb3223952e488"
checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260"
dependencies = [
"jiff-static",
"log",
@ -364,9 +408,9 @@ dependencies = [
[[package]]
name = "jiff-static"
version = "0.2.6"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43ce13c40ec6956157a3635d97a1ee2df323b263f09ea14165131289cb0f5c19"
checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c"
dependencies = [
"proc-macro2",
"quote",
@ -408,7 +452,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34"
dependencies = [
"cfg-if",
"windows-targets",
"windows-targets 0.52.6",
]
[[package]]
@ -427,6 +471,16 @@ version = "0.4.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.27"
@ -445,6 +499,26 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff70ce3e48ae43fa075863cef62e8b43b71a4f2382229920e0df362592919430"
dependencies = [
"adler2",
]
[[package]]
name = "mio"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd"
dependencies = [
"libc",
"wasi 0.11.0+wasi-snapshot-preview1",
"windows-sys 0.52.0",
]
[[package]]
name = "nom"
version = "7.1.3"
@ -456,13 +530,12 @@ dependencies = [
]
[[package]]
name = "num_cpus"
version = "1.16.0"
name = "object"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
checksum = "62948e14d923ea95ea2c7c86c71013138b66525b86bdc08d2dcc262bdb497b87"
dependencies = [
"hermit-abi",
"libc",
"memchr",
]
[[package]]
@ -471,6 +544,35 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets 0.52.6",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "portable-atomic"
version = "1.11.0"
@ -488,9 +590,9 @@ dependencies = [
[[package]]
name = "prettyplease"
version = "0.2.32"
version = "0.2.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "664ec5419c51e34154eec046ebcba56312d5a2fc3b09a06da188e1ad21afadf6"
checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033"
dependencies = [
"proc-macro2",
"syn",
@ -521,10 +623,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
[[package]]
name = "regex"
version = "1.11.1"
name = "redox_syscall"
version = "0.5.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619"
dependencies = [
"aho-corasick",
"memchr",
@ -534,9 +645,9 @@ dependencies = [
[[package]]
name = "regex-automata"
version = "0.4.9"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
@ -545,9 +656,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
version = "0.8.5"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "ring"
@ -563,6 +674,12 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustc-hash"
version = "1.1.0"
@ -584,9 +701,9 @@ dependencies = [
[[package]]
name = "rustls"
version = "0.23.26"
version = "0.23.25"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0"
checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c"
dependencies = [
"aws-lc-rs",
"log",
@ -617,9 +734,15 @@ dependencies = [
[[package]]
name = "ryu"
version = "1.0.20"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
@ -641,6 +764,18 @@ dependencies = [
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.140"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "serde_yml"
version = "0.0.12"
@ -662,6 +797,31 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
[[package]]
name = "socket2"
version = "0.5.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef"
dependencies = [
"libc",
"windows-sys 0.52.0",
]
[[package]]
name = "subtle"
version = "2.6.1"
@ -680,19 +840,59 @@ dependencies = [
]
[[package]]
name = "threadpool"
version = "1.8.1"
name = "tokio"
version = "1.44.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48"
dependencies = [
"num_cpus",
"backtrace",
"bytes",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.52.0",
]
[[package]]
name = "tokio-io-timeout"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30b74022ada614a1b4834de765f9bb43877f910cc8ce4be40e89042c9223a8bf"
dependencies = [
"pin-project-lite",
"tokio",
]
[[package]]
name = "tokio-macros"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio-rustls"
version = "0.26.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b"
dependencies = [
"rustls",
"tokio",
]
[[package]]
name = "unicode-ident"
version = "1.0.18"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
[[package]]
name = "untrusted"
@ -745,13 +945,22 @@ version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68ce1ab1f8c62655ebe1350f589c61e505cf94d385bc6a12899442d9081e71fd"
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets 0.48.5",
]
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
"windows-targets 0.52.6",
]
[[package]]
@ -760,7 +969,22 @@ version = "0.59.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
dependencies = [
"windows-targets",
"windows-targets 0.52.6",
]
[[package]]
name = "windows-targets"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
dependencies = [
"windows_aarch64_gnullvm 0.48.5",
"windows_aarch64_msvc 0.48.5",
"windows_i686_gnu 0.48.5",
"windows_i686_msvc 0.48.5",
"windows_x86_64_gnu 0.48.5",
"windows_x86_64_gnullvm 0.48.5",
"windows_x86_64_msvc 0.48.5",
]
[[package]]
@ -769,28 +993,46 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_aarch64_gnullvm 0.52.6",
"windows_aarch64_msvc 0.52.6",
"windows_i686_gnu 0.52.6",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"windows_i686_msvc 0.52.6",
"windows_x86_64_gnu 0.52.6",
"windows_x86_64_gnullvm 0.52.6",
"windows_x86_64_msvc 0.52.6",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
@ -803,24 +1045,48 @@ version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"

View File

@ -1,13 +1,16 @@
[package]
name = "flowgate"
version = "0.1.4"
version = "0.1.3"
edition = "2021"
[dependencies]
tokio = { version = "1.44.2", features = ["full"] }
tokio-io-timeout = "1.2.0"
tokio-rustls = "0.26.2"
rustls = "0.23.25"
wildmatch = "2.4.0"
serde_yml = "0.0.12"
serde_json = "1.0.140"
log = "0.4.27"
colog = "1.3.0"
ignore-result = "0.2.0"
threadpool = "1.8.1"
ignore-result = "0.2.0"

View File

@ -3,32 +3,22 @@ HTTP requests redirection system
Features:
- Request redirection
- TLS support (via Rustls!)
- TLS support
- Keep-alive connections
- Sending IP in header (X-Real-IP)
- Multiple ip forwarding methods
- Accepts incoming ip forwarding
TODO:
- Remove panics
- Creating trees of flowgate
- Filter by headers
- Modify response headers
- HTTP/3 full support (quic/udp)
- HTTP/3 full support
## How to use
Firstly, download it from releases. or build from sources (read BUILD.md) \
Just run it and configure in `conf.yml` file.
### Logging
To get all logs (with debug ones), set this env var:
```
RUST_LOG=debug
```
Read more: [env_logger](https://docs.rs/env_logger/latest/env_logger/#enabling-logging)
## Configuration
### IP forwarding methods

View File

@ -1,16 +1,15 @@
http_host: localhost:80 # Http server host (optional)
https_host: localhost:443 # Https server host (optional)
http_host: localhost:80 # Http server host
https_host: localhost:443 # Https server host
connection_timeout: 10 # Read and write timeout of connections in seconds (optional, default - 10)
incoming_ip_forwarding: none # Read IP forwarding on incoming connections (optional, default - none)
threadpool_size: 10 # Size of the global threadpool (optional, default - 10)
sites:
- domain: localhost # Site domain (use wildcard matching)
host: localhost:8080 # Http server host
ip_forwarding: simple # IP forwarding method type (optional, default - header)
enable_keep_alive: true # Enable keep-alive connections (optional, default - true)
support_keep_alive: true # Does server supports keep-alive connections (optional, default - true)
# ssl_cert: "/path/to/public/certificate.txt" # Ssl public certificate file (optional)
# ssl_key: "/path/to/private/key.txt" # Ssl private key file (optional)
replace_host: "meex.lol" # Replace Host header in requests to server (optional)
- domain: localhost # Site domain (use wildcard matching)
host: localhost:8080 # Http server host
ip_forwarding: simple # IP forwarding method type (optional, default - header)
enable_keep_alive: true # Enable keep-alive connections (optional, default - true)
support_keep_alive: true # Does server supports keep-alive connections (optional, default - true)
# ssl_cert: "/path/to/public/certificate.txt" # Ssl public certificate file (optional)
# ssl_key: "/path/to/private/key.txt" # Ssl private key file (optional)
replace_host: "meex.lol" # Replace Host header in requests to server (optional)

3
src/flowgate.rs Executable file
View File

@ -0,0 +1,3 @@
pub mod config;
pub mod server;
pub mod tls;

View File

@ -1,6 +1,6 @@
use std::{fs, time::Duration};
use std::net::TcpStream;
use tokio::net::TcpStream;
use serde_yml::{Number, Value};
use wildmatch::WildMatch;
@ -19,8 +19,8 @@ pub struct SiteConfig {
}
impl SiteConfig {
pub fn connect(&self) -> Option<TcpStream> {
TcpStream::connect(self.host.clone()).ok()
pub async fn connect(&self) -> Option<TcpStream> {
TcpStream::connect(self.host.clone()).await.ok()
}
}
@ -51,11 +51,11 @@ impl IpForwarding {
#[derive(Clone, Debug)]
pub struct Config {
pub sites: Vec<SiteConfig>,
pub http_host: Option<String>,
pub https_host: Option<String>,
pub http_host: String,
pub https_host: String,
pub threadpool_size: usize,
pub connection_timeout: Duration,
pub incoming_ip_forwarding: IpForwarding,
pub threadpool_size: usize
pub incoming_ip_forwarding: IpForwarding
}
impl Config {
@ -63,11 +63,11 @@ impl Config {
let file_content = fs::read_to_string(filename).ok()?;
let doc = serde_yml::from_str::<Value>(file_content.as_str()).ok()?;
let http_host = doc.get("http_host").and_then(|o| Some(o.as_str()?.to_string()));
let https_host = doc.get("https_host").and_then(|o| Some(o.as_str()?.to_string()));
let threadpool_size = doc.get("threadpool_size").and_then(|o| Some(o.as_u64()? as usize + 2)).unwrap_or(12);
let http_host = doc["http_host"].as_str()?.to_string();
let https_host = doc["https_host"].as_str()?.to_string();
let threadpool_size = doc.get("threadpool_size")
.unwrap_or(&Value::Number(Number::from(10))).as_u64()? as usize;
let connection_timeout = Duration::from_secs(doc.get("connection_timeout")
.unwrap_or(&Value::Number(Number::from(10))).as_u64()?);
let incoming_ip_forwarding = doc.get("incoming_ip_forwarding")
@ -117,9 +117,9 @@ impl Config {
sites,
http_host,
https_host,
threadpool_size,
connection_timeout,
incoming_ip_forwarding,
threadpool_size
incoming_ip_forwarding
}.clone())
}

492
src/flowgate/server.rs Executable file
View File

@ -0,0 +1,492 @@
use std::{
error::Error,
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6},
str::FromStr,
sync::Arc
};
use ignore_result::Ignore;
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::{TcpListener, TcpStream}
};
use log::info;
use tokio_io_timeout::TimeoutStream;
use tokio_rustls::TlsAcceptor;
use crate::tls::create_server_config;
use super::config::{
Config,
SiteConfig,
IpForwarding
};
pub struct FlowgateServer {
config: Arc<Config>,
}
struct Connection {
stream: TcpStream,
config: SiteConfig,
keep_alive: bool,
host: String,
}
impl FlowgateServer {
pub fn new(config: Arc<Config>) -> Self {
FlowgateServer { config }
}
pub async fn start(self) {
let local_self = Arc::new(self);
tokio::spawn({
let local_self = local_self.clone();
async move { local_self.run_http().await.ignore(); }
});
tokio::spawn({
let local_self = local_self.clone();
async move { local_self.run_https().await.ignore(); }
});
}
pub async fn run_http(self: Arc<Self>) -> Result<(), Box<dyn Error>> {
let listener = TcpListener::bind(&self.config.http_host).await?;
info!("HTTP server runned on {}", &self.config.http_host);
loop {
let Ok((stream, addr)) = listener.accept().await else { break };
let local_self = self.clone();
tokio::spawn(async move {
let mut stream = TimeoutStream::new(stream);
stream.set_write_timeout(Some(local_self.config.connection_timeout));
stream.set_read_timeout(Some(local_self.config.connection_timeout));
let mut stream = Box::pin(stream);
local_self.accept_stream(
&mut stream,
addr,
false
).await;
});
}
Ok(())
}
pub async fn run_https(self: Arc<Self>) -> Result<(), Box<dyn Error>> {
let listener = TcpListener::bind(&self.config.https_host).await?;
let acceptor = TlsAcceptor::from(Arc::new(create_server_config(self.config.clone()).await));
info!("HTTPS server runned on {}", &self.config.https_host);
loop {
let Ok((stream, addr)) = listener.accept().await else { break };
let local_self = self.clone();
let acceptor = acceptor.clone();
tokio::spawn(async move {
let mut stream = TimeoutStream::new(stream);
stream.set_write_timeout(Some(local_self.config.connection_timeout));
stream.set_read_timeout(Some(local_self.config.connection_timeout));
let Ok(mut stream) = acceptor.accept(Box::pin(stream)).await else { return };
local_self.accept_stream(
&mut stream,
addr,
true
).await;
});
}
Ok(())
}
async fn accept_stream(
self: Arc<Self>,
stream: &mut (impl AsyncReadExt + AsyncWriteExt + Unpin),
addr: SocketAddr,
https: bool
) -> Option<()> {
let mut conn = self.clone().read_request(stream, addr, https, None).await?;
if conn.keep_alive && conn.config.enable_keep_alive {
loop {
if !conn.config.support_keep_alive {
conn.stream.shutdown().await.ignore();
conn.stream = conn.config.connect().await?;
}
conn = self.clone().read_request(stream, addr, https, Some(conn)).await?;
}
}
conn.stream.shutdown().await.ignore();
stream.shutdown().await.ok()?;
Some(())
}
async fn read_request(
self: Arc<Self>,
stream: &mut (impl AsyncReadExt + AsyncWriteExt + Unpin),
addr: SocketAddr,
https: bool,
conn: Option<Connection>
) -> Option<Connection> {
let mut addr = addr;
match &self.config.incoming_ip_forwarding {
IpForwarding::Simple => {
let mut header = Vec::new();
{
let mut buf = [0; 1];
while let Ok(1) = stream.read(&mut buf).await {
let byte = buf[0];
if byte == b'\n' { break }
header.push(byte);
}
}
addr = SocketAddr::from_str(&String::from_utf8(header).ok()?).ok()?;
},
IpForwarding::Modern => {
let mut ipver = [0; 1];
stream.read(&mut ipver).await.ok()?;
addr = match ipver[0] {
0x01 => {
let mut octets = [0; 4];
stream.read(&mut octets).await.ok()?;
let mut port = [0; 2];
stream.read(&mut port).await.ok()?;
let port = u16::from_be_bytes(port);
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from(octets), port))
}, 0x02 => {
let mut octets = [0; 16];
stream.read(&mut octets).await.ok()?;
let mut port = [0; 2];
stream.read(&mut port).await.ok()?;
let port = u16::from_be_bytes(port);
SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::from(octets), port, 0, 0))
}, _ => { return None },
};
},
_ => { }
}
let mut head = Vec::new();
{
let mut buf = [0; 1];
let mut counter = 0;
while let Ok(1) = stream.read(&mut buf).await {
let byte = buf[0];
head.push(byte);
counter = match (counter, byte) {
(0, b'\r') => 1,
(1, b'\n') => 2,
(2, b'\r') => 3,
(3, b'\n') => break,
_ => 0,
};
}
head.truncate(head.len() - 4);
}
if head.is_empty() { return None; }
let head_str = String::from_utf8(head.clone()).ok()?;
let head_str = head_str.trim_matches(char::from(0)).to_string();
let mut head_lines = head_str.split("\r\n");
let status = head_lines.next()?;
let status_seq: Vec<&str> = status.split(" ").collect();
let headers: Vec<(&str, &str)> = head_lines
.filter(|l| l.contains(": "))
.map(|l| l.split_once(": ").unwrap())
.collect();
let is_chunked = headers.iter()
.find(|o| o.0.to_lowercase() == "transfer-encoding")
.map(|o| o.1.split(",").map(|x| x.trim_matches(' ').to_string()).collect::<Vec<String>>())
.map(|o| o.contains(&"chunked".to_string()))
.unwrap_or(false);
if let IpForwarding::Header(header) = &self.config.incoming_ip_forwarding {
if let Some(ip) = headers.iter().find(|o| o.0 == header).map(|o| o.1) {
addr = SocketAddr::from_str(ip).ok()?;
}
}
let mut conn: Connection = if conn.is_none() {
let mut host = String::new();
let mut keep_alive = false;
for (key, value) in &headers {
match key.to_lowercase().as_str() {
"host" => host = value.to_string(),
"connection" => keep_alive = *value == "keep-alive",
_ => {}
}
}
let site = self.config.get_site(&host)?.clone();
Connection {
stream: site.connect().await?,
config: site,
keep_alive,
host
}
} else {
conn?
};
let content_length = headers
.iter()
.filter(|(k, _)| k.to_lowercase() == "content-length")
.next()
.map(|o| o.1.parse().ok())
.flatten()
.unwrap_or(0usize);
let mut reqbuf: Vec<u8> = Vec::new();
if let Some(replace_host) = conn.config.replace_host.clone() {
let mut new_head = Vec::new();
let mut is_status = true;
for line in head_str.split("\r\n") {
if is_status {
new_head.append(&mut line.as_bytes().to_vec());
is_status = false;
} else {
new_head.append(&mut b"\r\n".to_vec());
let (key, _) = line.split_once(": ")?;
if key.to_lowercase() == "host" {
new_head.append(&mut key.as_bytes().to_vec());
new_head.append(&mut b": ".to_vec());
new_head.append(&mut replace_host.as_bytes().to_vec());
} else {
new_head.append(&mut line.as_bytes().to_vec());
}
}
}
head = new_head;
}
match &conn.config.ip_forwarding {
IpForwarding::Header(header) => {
reqbuf.append(&mut status.to_string().as_bytes().to_vec());
reqbuf.append(&mut b"\r\n".to_vec());
for (key, value) in String::from_utf8(head.clone()).ok()?
.split("\r\n")
.skip(1)
.filter_map(|o| o.split_once(": ")) {
if *key.to_lowercase() == header.to_lowercase() { continue }
reqbuf.append(&mut key.to_string().as_bytes().to_vec());
reqbuf.append(&mut b": ".to_vec());
reqbuf.append(&mut value.to_string().as_bytes().to_vec());
reqbuf.append(&mut b"\r\n".to_vec());
}
reqbuf.append(&mut header.as_bytes().to_vec());
reqbuf.append(&mut b": ".to_vec());
reqbuf.append(&mut addr.to_string().as_bytes().to_vec());
reqbuf.append(&mut b"\r\n\r\n".to_vec());
},
IpForwarding::Simple => {
reqbuf.append(&mut addr.to_string().as_bytes().to_vec());
reqbuf.push(b'\n');
reqbuf.append(&mut head.clone());
reqbuf.append(&mut b"\r\n\r\n".to_vec());
},
IpForwarding::Modern => {
reqbuf.push(if addr.is_ipv4() { 0x01 } else { 0x02 });
match addr.ip() {
IpAddr::V4(ip) => {
reqbuf.append(&mut ip.octets().to_vec());
}, IpAddr::V6(ip) => {
reqbuf.append(&mut ip.octets().to_vec());
}
}
reqbuf.append(&mut addr.port().to_be_bytes().to_vec());
reqbuf.append(&mut head.clone());
reqbuf.append(&mut b"\r\n\r\n".to_vec());
},
IpForwarding::None => {
reqbuf.append(&mut head.clone());
reqbuf.append(&mut b"\r\n\r\n".to_vec());
}
}
conn.stream.write_all(&reqbuf).await.ok()?;
if content_length > 0 {
let mut read = 0usize;
let mut buf = vec![0; 4096];
while let Ok(size) = stream.read(&mut buf).await {
if size == 0 { break }
read += size;
buf.truncate(size);
conn.stream.write_all(&buf).await.ok()?;
buf = vec![0; 4096];
if read >= content_length { break }
}
} else if is_chunked {
loop {
let mut length = Vec::new();
{
let mut buf = [0; 1];
let mut counter = 0;
while let Ok(1) = stream.read(&mut buf).await {
let byte = buf[0];
length.push(byte);
counter = match (counter, byte) {
(0, b'\r') => 1,
(1, b'\n') => break,
_ => 0,
};
}
conn.stream.write_all(&length).await.ok()?;
length.truncate(length.len() - 2);
}
let length = String::from_utf8(length).ok()?;
let length = usize::from_str_radix(length.as_str(), 16).ok()?;
let mut data = vec![0u8; length+2];
stream.read_exact(&mut data).await.ok()?;
conn.stream.write_all(&data).await.ok()?;
if length == 0 {
break;
}
}
}
if conn.config.support_keep_alive {
let mut head = Vec::new();
{
let mut buf = [0; 1];
let mut counter = 0;
while let Ok(1) = conn.stream.read(&mut buf).await {
let byte = buf[0];
head.push(byte);
stream.write_all(&buf).await.ok()?;
counter = match (counter, byte) {
(0, b'\r') => 1,
(1, b'\n') => 2,
(2, b'\r') => 3,
(3, b'\n') => break,
_ => 0,
};
}
head.truncate(head.len() - 4);
}
if head.is_empty() { return None; }
let head_str = String::from_utf8(head.clone()).ok()?;
let head_str = head_str.trim_matches(char::from(0));
let headers = head_str.split("\r\n")
.skip(1)
.filter(|l| l.contains(": "))
.map(|l| l.split_once(": ").unwrap())
.map(|(k,v)| (k.to_lowercase(),v.to_string()))
.collect::<Vec<(String,String)>>();
let content_length = headers.iter()
.find(|(k, _)| k == "content-length")
.map(|o| o.1.parse().ok())
.flatten()
.unwrap_or(0usize);
let is_chunked = headers.iter()
.find(|o| o.0.to_lowercase() == "transfer-encoding")
.map(|o| o.1.split(",").map(|x| x.trim_matches(' ').to_string()).collect::<Vec<String>>())
.map(|o| o.contains(&"chunked".to_string()))
.unwrap_or(false);
if content_length > 0 {
let mut read = 0usize;
let mut buf = vec![0; 4096];
while let Ok(size) = conn.stream.read(&mut buf).await {
if size == 0 { break }
read += size;
buf.truncate(size);
stream.write_all(&buf).await.ok()?;
buf = vec![0; 4096];
if read == content_length { break }
}
} else if is_chunked {
loop {
let mut length = Vec::new();
{
let mut buf = [0; 1];
let mut counter = 0;
while let Ok(1) = conn.stream.read(&mut buf).await {
let byte = buf[0];
length.push(byte);
counter = match (counter, byte) {
(0, b'\r') => 1,
(1, b'\n') => break,
_ => 0,
};
}
stream.write_all(&length).await.ok()?;
length.truncate(length.len() - 2);
}
let length = String::from_utf8(length).ok()?;
let length = usize::from_str_radix(length.as_str(), 16).ok()?;
let mut data = vec![0u8; length+2];
conn.stream.read_exact(&mut data).await.ok()?;
stream.write_all(&data).await.ok()?;
if length == 0 {
break;
}
}
}
} else {
let mut buf = vec![0;1024];
while let Ok(n) = conn.stream.read(&mut buf).await {
if n == 0 { break }
buf.truncate(n);
stream.write_all(&buf).await.ok()?;
buf = vec![0;1024];
}
}
info!("{addr} > {} {}://{}{}", status_seq[0], if https { "https" } else { "http" }, conn.host, status_seq[1]);
Some(conn)
}
}

View File

@ -39,7 +39,7 @@ struct ResolvesServerCertWildcard {
}
impl ResolvesServerCertWildcard {
pub fn new(config: Arc<Config>) -> Self {
pub async fn new(config: Arc<Config>) -> Self {
Self { config }
}
}
@ -56,8 +56,8 @@ impl ResolvesServerCert for ResolvesServerCertWildcard {
}
}
pub fn create_server_config(config: Arc<Config>) -> ServerConfig {
pub async fn create_server_config(config: Arc<Config>) -> ServerConfig {
ServerConfig::builder()
.with_no_client_auth()
.with_cert_resolver(Arc::new(ResolvesServerCertWildcard::new(config)))
.with_cert_resolver(Arc::new(ResolvesServerCertWildcard::new(config).await))
}

View File

@ -1,7 +1,3 @@
pub mod config;
pub mod server;
pub mod tls;
pub mod flowgate;
pub use config::*;
pub use server::*;
pub use tls::*;
pub use flowgate::*;

View File

@ -3,7 +3,8 @@ use std::{fs, path::Path, sync::Arc};
use flowgate::{config::Config, server::FlowgateServer};
use ignore_result::Ignore;
fn main() {
#[tokio::main]
async fn main() {
colog::init();
if !Path::new("conf.yml").exists() {
@ -13,5 +14,7 @@ fn main() {
let config = Arc::new(Config::parse("conf.yml").unwrap());
let server = FlowgateServer::new(config.clone());
server.run();
server.start().await;
loop {}
}

View File

@ -1,539 +0,0 @@
use std::{
error::Error, io::{BufRead, BufReader, Read, Write},
net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, TcpListener, TcpStream},
str::FromStr, sync::Arc
};
use ignore_result::Ignore;
use log::{debug, info};
use rustls::{ServerConnection, StreamOwned};
use threadpool::ThreadPool;
use super::{
tls::create_server_config,
config::{
Config,
SiteConfig,
IpForwarding
}
};
pub struct FlowgateServer {
config: Arc<Config>,
}
struct Connection {
stream: BufReader<TcpStream>,
config: SiteConfig,
keep_alive: bool,
host: String,
}
impl FlowgateServer {
pub fn new(config: Arc<Config>) -> Self {
FlowgateServer { config }
}
pub fn run(self) {
self.start().join();
}
pub fn start(self) -> ThreadPool {
let local_self = Arc::new(self);
let threadpool = ThreadPool::new(local_self.config.threadpool_size);
let mut handles = Vec::new();
handles.push(local_self.clone().start_http(&threadpool));
handles.push(local_self.clone().start_https(&threadpool));
threadpool
}
pub fn start_http(self: Arc<Self>, threadpool: &ThreadPool) {
threadpool.execute({
let local_self = self.clone();
let threadpool = threadpool.clone();
move || { local_self.run_http(&threadpool).ignore(); }
})
}
pub fn start_https(self: Arc<Self>, threadpool: &ThreadPool) {
threadpool.execute({
let local_self = self.clone();
let threadpool = threadpool.clone();
move || { local_self.run_https(&threadpool).ignore(); }
})
}
pub fn run_http(self: Arc<Self>, threadpool: &ThreadPool) -> Result<(), Box<dyn Error>> {
if let Some(host) = self.config.http_host.clone() {
let listener = TcpListener::bind(&host)?;
info!("HTTP server runned on {}", &host);
for stream in listener.incoming() {
if let Ok(mut stream) = stream {
let local_self = self.clone();
let Ok(addr) = stream.peer_addr() else { continue };
let Ok(_) = stream.set_write_timeout(Some(local_self.config.connection_timeout)) else { continue };
let Ok(_) = stream.set_read_timeout(Some(local_self.config.connection_timeout)) else { continue };
threadpool.execute(move || {
debug!("{} open connection", addr);
local_self.accept_stream(
&mut stream,
addr,
false
);
debug!("{} close connection", addr);
});
}
}
}
Ok(())
}
pub fn run_https(self: Arc<Self>, threadpool: &ThreadPool) -> Result<(), Box<dyn Error>> {
if let Some(host) = self.config.https_host.clone() {
let listener = TcpListener::bind(&host)?;
info!("HTTPS server runned on {}", &host);
let config = Arc::new(create_server_config(self.config.clone()));
for stream in listener.incoming() {
if let Ok(stream) = stream {
let local_self = self.clone();
let config = config.clone();
let Ok(addr) = stream.peer_addr() else { continue };
let Ok(_) = stream.set_write_timeout(Some(local_self.config.connection_timeout)) else { continue };
let Ok(_) = stream.set_read_timeout(Some(local_self.config.connection_timeout)) else { continue };
threadpool.execute(move || {
let Ok(connection) = ServerConnection::new(config) else { return };
debug!("{} open connection", addr);
let mut stream = StreamOwned::new(connection, stream);
while stream.conn.is_handshaking() {
let Ok(_) = stream.conn.complete_io(&mut stream.sock) else { debug!("{} close connection", addr);return };
}
local_self.accept_stream(
&mut stream,
addr,
true
);
debug!("{} close connection", addr);
});
}
}
}
Ok(())
}
fn accept_stream(
self: Arc<Self>,
stream: &mut (impl Read + Write + Shutdown),
addr: SocketAddr,
https: bool
) -> Option<()> {
let mut conn = self.clone().read_request(stream, addr, https, None)?;
if conn.keep_alive && conn.config.enable_keep_alive {
loop {
if !conn.config.support_keep_alive {
conn.stream.shutdown();
conn.stream = BufReader::new(conn.config.connect()?);
}
conn = self.clone().read_request(stream, addr, https, Some(conn))?;
}
}
conn.stream.shutdown();
stream.shutdown();
Some(())
}
fn read_request(
self: Arc<Self>,
stream: &mut (impl Read + Write + Shutdown),
addr: SocketAddr,
https: bool,
conn: Option<Connection>
) -> Option<Connection> {
let mut addr = addr;
let mut stream = BufReader::new(stream);
match &self.config.incoming_ip_forwarding {
IpForwarding::Simple => {
let mut header = Vec::new();
stream.read_until(b'\n', &mut header).ok()?;
header.truncate(header.len()-1);
addr = SocketAddr::from_str(&String::from_utf8(header).ok()?).ok()?;
},
IpForwarding::Modern => {
let mut header = [0];
stream.read(&mut header).ok()?;
addr = match header[0] {
0x01 => {
let mut octets = [0; 4];
stream.read(&mut octets).ok()?;
let mut port = [0; 2];
stream.read(&mut port).ok()?;
let port = u16::from_be_bytes(port);
SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from(octets), port))
}, 0x02 => {
let mut octets = [0; 16];
stream.read(&mut octets).ok()?;
let mut port = [0; 2];
stream.read(&mut port).ok()?;
let port = u16::from_be_bytes(port);
SocketAddr::V6(SocketAddrV6::new(Ipv6Addr::from(octets), port, 0, 0))
}, _ => { return None },
};
},
_ => {}
}
let mut raw_status = read_until(&mut stream, b"\r\n")?;
let mut request = Vec::new();
request.append(&mut raw_status.clone());
raw_status.truncate(raw_status.len() - 2);
let status = String::from_utf8(raw_status.clone()).ok()?;
let status = status.split(" ").collect::<Vec<&str>>();
debug!("{} {} read status", addr, status[1]);
let mut content_length = 0;
let mut host = None;
let mut is_chunked = false;
let mut keep_alive = false;
let mut headers = Vec::new();
loop {
let mut header = read_until(&mut stream, b"\r\n")?;
header.truncate(header.len() - 2);
if header.is_empty() {
break;
}
let header = String::from_utf8(header).ok()?;
let (key, value) = header.split_once(": ")?;
headers.push((key.to_string(), value.to_string()));
match key.to_lowercase().as_str() {
"transfer-encoding" => {
if value.contains("chunked") {
is_chunked = true;
}
},
"host" => {
host = Some(value.to_string());
},
"connection" => {
keep_alive = value.to_lowercase().contains("keep-alive");
},
"content-length" => {
content_length = value.parse::<usize>().ok()?;
},
_ => {
if let IpForwarding::Header(header) = &self.config.incoming_ip_forwarding {
if key.to_lowercase() == header.to_lowercase() {
addr = SocketAddr::from_str(value).ok()?;
}
}
},
}
}
debug!("{} {} read headers", addr, status[1]);
let mut conn: Connection = if conn.is_none() {
let host = host?;
let site = self.config.get_site(&host)?.clone();
Connection {
stream: BufReader::new(site.connect()?),
config: site,
keep_alive,
host
}
} else {
conn?
};
debug!("{} {} got connection", addr, status[1]);
match &conn.config.ip_forwarding {
IpForwarding::Simple => {
request.append(&mut addr.to_string().as_bytes().to_vec());
request.push(b'\n');
},
IpForwarding::Modern => {
match addr.ip() {
IpAddr::V4(ip) => {
request.push(0x01);
request.append(&mut ip.octets().to_vec());
}, IpAddr::V6(ip) => {
request.push(0x02);
request.append(&mut ip.octets().to_vec());
}
}
request.append(&mut addr.port().to_be_bytes().to_vec());
},
_ => {}
}
for (key, value) in headers {
let mut value = value.to_string();
match key.to_lowercase().as_str() {
"host" => {
if let Some(replace_host) = conn.config.replace_host.clone() {
value = replace_host;
}
},
_ => {}
}
if let IpForwarding::Header(header) = &conn.config.ip_forwarding {
if key.to_lowercase() == header.to_lowercase() {
continue;
}
}
request.append(&mut key.as_bytes().to_vec());
request.append(&mut b": ".to_vec());
request.append(&mut value.as_bytes().to_vec());
request.append(&mut b"\r\n".to_vec());
}
if let IpForwarding::Header(header) = &conn.config.ip_forwarding {
request.append(&mut header.as_bytes().to_vec());
request.append(&mut b": ".to_vec());
request.append(&mut addr.to_string().as_bytes().to_vec());
request.append(&mut b"\r\n".to_vec());
}
request.append(&mut b"\r\n".to_vec());
debug!("{:?}", String::from_utf8_lossy(&request));
conn.stream.get_mut().write_all(&request).ok()?;
debug!("{} {} sent request to server", addr, status[1]);
if content_length > 0 {
let buffer = stream.buffer().to_vec();
conn.stream.get_mut().write_all(&buffer).ok()?;
stream.consume(buffer.len());
let mut read = buffer.len();
debug!("{} {} send part of body to server", addr, status[1]);
while read < content_length {
let mut buf = vec![0; 4096];
let size = conn.stream.get_mut().read(&mut buf).ok()?;
if size == 0 { break }
buf.truncate(size);
read += size;
debug!("{} {} send response body part {} to clientr", addr, status[1], size);
stream.get_mut().write_all(&buf).ok()?;
}
} else if is_chunked {
transfer_chunked(&mut stream, conn.stream.get_mut())?;
} else {
let buffer = stream.buffer().to_vec();
conn.stream.get_mut().write_all(&buffer).ok()?;
stream.consume(buffer.len());
}
debug!("{} {} send body to server", addr, status[1]);
if conn.config.support_keep_alive {
let mut response = Vec::new();
let raw_status = read_until(&mut conn.stream, b"\r\n")?;
response.append(&mut raw_status.clone());
let mut content_length = 0;
let mut is_chunked = false;
loop {
let mut header = read_until(&mut conn.stream, b"\r\n")?;
response.append(&mut header.clone());
if header.len() == 2 {
break;
}
header.truncate(header.len() - 2);
let header = String::from_utf8(header).ok()?;
let (key, value) = header.split_once(": ")?;
match key.to_lowercase().as_str() {
"transfer-encoding" => {
if value.contains("chunked") {
is_chunked = true;
}
},
"content-length" => {
content_length = value.parse::<usize>().ok()?;
},
_ => {}
}
}
stream.get_mut().write_all(&response).ok()?;
debug!("{} {} send response header to clientr", addr, status[1]);
if content_length > 0 {
let buffer = conn.stream.buffer().to_vec();
stream.get_mut().write_all(&buffer).ok()?;
conn.stream.consume(buffer.len());
debug!("{} {} send response body part {} to clientr", addr, status[1], buffer.len());
let mut read = buffer.len();
while read < content_length {
let mut buf = vec![0; 4096];
let size = conn.stream.get_mut().read(&mut buf).ok()?;
if size == 0 { break }
buf.truncate(size);
read += size;
debug!("{} {} send response body part {} to clientr", addr, status[1], size);
stream.get_mut().write_all(&buf).ok()?;
}
} else if is_chunked {
transfer_chunked(&mut conn.stream, stream.get_mut())?;
} else {
let buffer = conn.stream.buffer().to_vec();
stream.get_mut().write_all(&buffer).ok()?;
conn.stream.consume(buffer.len());
}
debug!("{} {} send response body to clientr", addr, status[1]);
} else {
let buffer = conn.stream.buffer();
stream.get_mut().write_all(buffer).ok()?;
conn.stream.consume(buffer.len());
let mut buf = vec![0;4096];
while let Ok(n) = conn.stream.get_mut().read(&mut buf) {
if n == 0 { break }
buf.truncate(n);
stream.get_mut().write_all(&buf).ok()?;
buf = vec![0;4096];
}
}
info!("{addr} > {} {}://{}{}", status[0], if https { "https" } else { "http" }, conn.host, status[1]);
Some(conn)
}
}
fn read_until(stream: &mut impl BufRead, delimiter: &[u8]) -> Option<Vec<u8>> {
let mut data = Vec::new();
let last_byte = *delimiter.last()?;
loop {
let mut buf = Vec::new();
let buf_len = stream.read_until(last_byte, &mut buf).ok()?;
debug!("read buf len until {} {:?}", buf_len, String::from_utf8_lossy(&buf));
if buf_len == 0 {
return None
}
data.append(&mut buf);
if data.ends_with(delimiter) {
break;
}
}
Some(data)
}
fn transfer_chunked(src: &mut impl BufRead, dest: &mut impl Write) -> Option<()> {
loop {
let mut length = read_until(src, b"\r\n")?;
dest.write_all(&length).ok()?;
length.truncate(length.len()-2);
let length = String::from_utf8(length).ok()?;
let length = usize::from_str_radix(length.as_str(), 16).ok()?;
let mut data = vec![0u8; length+2];
src.read_exact(&mut data).ok()?;
dest.write_all(&data).ok()?;
if length == 0 {
break;
}
}
Some(())
}
pub trait Shutdown {
fn shutdown(&self);
}
impl Shutdown for TcpStream {
fn shutdown(&self) {
TcpStream::shutdown(self, std::net::Shutdown::Both).ignore();
}
}
impl <T: Shutdown> Shutdown for BufReader<T> {
fn shutdown(&self) {
self.get_ref().shutdown();
}
}
impl <C, T: Read + Write + Shutdown> Shutdown for StreamOwned<C, T> {
fn shutdown(&self) {
self.sock.shutdown();
}
}

View File

@ -1,32 +0,0 @@
import socket
HOST = '172.16.1.32'
PORT = 80
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
s.send(b"""GET / HTTP/1.1\r
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r
Accept-Encoding: gzip, deflate, br, zstd\r
Accept-Language: en-US,en;q=0.9\r
Cache-Control: no-cache\r
Connection: keep-alive\r
Host: git.meex.lol\r
Pragma: no-cache\r
Sec-Fetch-Dest: document\r
Sec-Fetch-Mode: navigate\r
Sec-Fetch-Site: none\r
Sec-Fetch-User: ?1\r
Upgrade-Insecure-Requests: 1\r
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36\r
sec-ch-ua: \"Chromium\";v=\"135\", \"Not-A.Brand\";v=\"8\"\r
sec-ch-ua-mobile: ?0\r
sec-ch-ua-platform: \"Linux\"\r
\r
""")
content_length = 0
while True:
data = s.recv(1024)
print(data)

View File

@ -1,40 +0,0 @@
import socket
HOST = '172.16.1.32'
PORT = 80
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
i = 0
while True:
s.send(b"""GET / HTTP/1.1\r
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7\r
Accept-Encoding: gzip, deflate, br, zstd\r
Accept-Language: en-US,en;q=0.9\r
Cache-Control: no-cache\r
Connection: keep-alive\r
Host: git.meex.lol\r
Pragma: no-cache\r
Sec-Fetch-Dest: document\r
Sec-Fetch-Mode: navigate\r
Sec-Fetch-Site: none\r
Sec-Fetch-User: ?1\r
Upgrade-Insecure-Requests: 1\r
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36\r
sec-ch-ua: \"Chromium\";v=\"135\", \"Not-A.Brand\";v=\"8\"\r
sec-ch-ua-mobile: ?0\r
sec-ch-ua-platform: \"Linux\"\r
\r
""")
content_length = 0
while content_length < 9392:
data = s.recv(1024)
if data:
content_length += len(data)
# print(data.decode("utf8", errors="ignore"), end="")
else:
print("sdfsdf")
i += 1
print(i)