Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • codecraft/webserver
  • sorcaMriete/webserver
  • opnonmicgo/webserver
  • loposuezo/webserver
  • diufeYmike/webserver
  • contbuspecmi/webserver
  • mogamuboun/webserver
  • glabalwelre/webserver
8 results
Show changes
Commits on Source (66)
Showing
with 2215 additions and 0 deletions
GOOD HABITS
1. If you collect an iterator which you don't index in, you don't need to collect it
2. avoid temporary hashmaps
3. rewrite POST request stuff TICK
4. Client struct
5. Mime-Display new implemented
6. Remove unwraps
7. Reusable allocations
TODO:
1. Uri structs everywhere
2. Cookie From String fix
API design 3. No decisions for the caller
...@@ -2,6 +2,723 @@ ...@@ -2,6 +2,723 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytes"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "core-foundation"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "194a7a9e6de53fa55116934067c844d9d749312f75c6f6d0980e8c252f8c2146"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e496a50fda8aacccc86d7529e2c1e0892dbd0f898a6b5645b5561b89c3210efa"
[[package]]
name = "errno"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bcfec3a70f97c962c307b2d2c56e358cf1d00b558d74262b5f929ee8cc7e73a"
dependencies = [
"errno-dragonfly",
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "fastrand"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
dependencies = [
"instant",
]
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "443144c8cdadd93ebf52ddb4056d257f5b52c04d3c804e657d19eb73fc33668b"
[[package]] [[package]]
name = "http" name = "http"
version = "0.1.0" version = "0.1.0"
dependencies = [
"phf",
"tokio",
"tokio-native-tls",
]
[[package]]
name = "instant"
version = "0.1.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
dependencies = [
"cfg-if",
]
[[package]]
name = "io-lifetimes"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2"
dependencies = [
"hermit-abi 0.3.2",
"libc",
"windows-sys 0.48.0",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.144"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
[[package]]
name = "linux-raw-sys"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519"
[[package]]
name = "lock_api"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "435011366fe56583b16cf956f9df0095b405b82d76425bc8981c0e22e60ec4df"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "mio"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9"
dependencies = [
"libc",
"log",
"wasi",
"windows-sys 0.45.0",
]
[[package]]
name = "native-tls"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi 0.2.6",
"libc",
]
[[package]]
name = "once_cell"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
[[package]]
name = "openssl"
version = "0.10.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "345df152bc43501c5eb9e4654ff05f794effb78d4efe3d53abc158baddc0703d"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-macros",
"openssl-sys",
]
[[package]]
name = "openssl-macros"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "openssl-probe"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf"
[[package]]
name = "openssl-sys"
version = "0.9.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374533b0e45f3a7ced10fcaeccca020e66656bc03dac384f852e4e5a7a8104a6"
dependencies = [
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9069cbb9f99e3a5083476ccb29ceb1de18b9118cafa53e90c9551235de2b9521"
dependencies = [
"cfg-if",
"libc",
"redox_syscall 0.2.16",
"smallvec",
"windows-sys 0.45.0",
]
[[package]]
name = "phf"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c"
dependencies = [
"phf_macros",
"phf_shared",
]
[[package]]
name = "phf_generator"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf"
dependencies = [
"phf_shared",
"rand",
]
[[package]]
name = "phf_macros"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92aacdc5f16768709a569e913f7451034034178b05bdc8acda226659a3dccc66"
dependencies = [
"phf_generator",
"phf_shared",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "phf_shared"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676"
dependencies = [
"siphasher",
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "pkg-config"
version = "0.3.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964"
[[package]]
name = "proc-macro2"
version = "1.0.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f4f29d145265ec1c483c7c654450edde0bfe043d3938d6972630663356d9500"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "redox_syscall"
version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
"bitflags",
]
[[package]]
name = "rustix"
version = "0.37.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d69718bf81c6127a49dc64e44a742e8bb9213c0ff8869a22c308f84c1d4ab06"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys 0.48.0",
]
[[package]]
name = "schannel"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c3733bf4cf7ea0880754e19cb5a462007c4a8c1914bff372ccc95b464f1df88"
dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "security-framework"
version = "2.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fc758eb7bffce5b308734e9b0c1468893cae9ff70ebf13e7090be8dcbcc83a8"
dependencies = [
"bitflags",
"core-foundation",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f51d0c0d83bec45f16480d0ce0058397a69e48fcdc52d1dc8855fb68acbd31a7"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1"
dependencies = [
"libc",
]
[[package]]
name = "siphasher"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "socket2"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "syn"
version = "1.0.109"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "syn"
version = "2.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a34fcf3e8b60f57e6a14301a2e916d323af98b0ea63c599441eec8558660c822"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "tempfile"
version = "3.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "31c0432476357e58790aaa47a8efb0c5138f137343f3b5f23bd36a27e3b0a6d6"
dependencies = [
"autocfg",
"cfg-if",
"fastrand",
"redox_syscall 0.3.5",
"rustix",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio"
version = "1.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2"
dependencies = [
"autocfg",
"bytes",
"libc",
"mio",
"num_cpus",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys 0.48.0",
]
[[package]]
name = "tokio-macros"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.15",
]
[[package]]
name = "tokio-native-tls"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2"
dependencies = [
"native-tls",
"tokio",
]
[[package]]
name = "unicode-ident"
version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.45.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
dependencies = [
"windows-targets 0.42.2",
]
[[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.0",
]
[[package]]
name = "windows-targets"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071"
dependencies = [
"windows_aarch64_gnullvm 0.42.2",
"windows_aarch64_msvc 0.42.2",
"windows_i686_gnu 0.42.2",
"windows_i686_msvc 0.42.2",
"windows_x86_64_gnu 0.42.2",
"windows_x86_64_gnullvm 0.42.2",
"windows_x86_64_msvc 0.42.2",
]
[[package]]
name = "windows-targets"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"windows_aarch64_gnullvm 0.48.0",
"windows_aarch64_msvc 0.48.0",
"windows_i686_gnu 0.48.0",
"windows_i686_msvc 0.48.0",
"windows_x86_64_gnu 0.48.0",
"windows_x86_64_gnullvm 0.48.0",
"windows_x86_64_msvc 0.48.0",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_i686_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
...@@ -3,6 +3,13 @@ name = "http" ...@@ -3,6 +3,13 @@ name = "http"
version = "0.1.0" version = "0.1.0"
edition = "2021" edition = "2021"
[features]
secure = []
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
tokio = { version = "1.28.2", features = ["full"] }
phf = { version = "0.11", features = ["macros"] }
tokio-native-tls = "0.3.0"
-----BEGIN CERTIFICATE-----
MIIE+zCCAuOgAwIBAgIUduzhfNiMYBDh6wuv3MjdxK2C6bYwDQYJKoZIhvcNAQEL
BQAwDTELMAkGA1UEBhMCREUwHhcNMjMwNzE5MTEyNzE3WhcNMjQwNzE4MTEyNzE3
WjANMQswCQYDVQQGEwJERTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
AKKTQ3sMq9yYNST56n9NbNkycJ1QQORl6DtqqFB3iVRrNOzs0o1RAqXtVCWIiyJ2
cvC/nPz9V+QRM9ZwovYGt7I2IGm3Wu15wWJH71QakPTElsoM+twVHUU+E8yNaDpY
vibP+6MIz7O1DD/uSqA5779n0/eZuC6Li1LYiaJNLHst9sqo1f5UccG66HdanT6+
oCbvUyS8tiVnepM0rTku7k/7XVkKVa71VAojoVfNhSpF/tWQJC5PD8Sp9X5E31QK
BMjMckdS/ev4qruPGBOF10eilt+nJAxUpbJ87UamG/VxI3DDNchZ+ssckgDRcd44
Td3Z2SsOAcHebCtrgOL4hFZQ+DsBMIDCWuj/BcKmN+RsnE40X7I01qBjbUABU/kp
cfJxd3YtkBysQ8VuMz00/jLzpUcTvbVjXq9ktCGBFbAUd+qqxNNA0Tqxw8TlE5pY
is2DzVpZvtuDscZAevMFRmi0aYN/RPGhcggeAUDAE4pHDgUdyZx8DKPZeDrPHRg+
q+ESDB1UI23D4xkg97dhMFCClUute5YItVv21uGlK8HVa9BpXNgEmqOWEbrAyaFk
AKIsdibdo7G7DZxbC0NSP4MFrBxsdRl4MrCWKcipoWJ78LDQ5MXWtPEPotBQ+wsB
zK56WUFkMKwyRXA8/tcS3DZ+GNugavMaSewJ2zgjgkLjAgMBAAGjUzBRMB0GA1Ud
DgQWBBQpS4RUpA6uFkfMDlfH1AG8e8JyejAfBgNVHSMEGDAWgBQpS4RUpA6uFkfM
DlfH1AG8e8JyejAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4ICAQAW
9EW7neVsyntMqUWYHfg7hALxd800gF8t3jpBXOkg1j4Dg+XewsJsix8k96+zA0RF
wXR2iyQGSaGE5wENdArbg0KJsTcm5R754jyPmOdUUxoC8e96+vJAcHRHPCZLb+7q
meD/hqT22221bM517PUBYHzLvTEtUzRDOZkoG90QKBxyi934TWmr3hXVqp5YqLvu
V50AOTy0p38q4oFCdocyq2LpLst8IrLZphAV5uAdNzk94hjxfACRSWdQjWefUEJl
Qb/W5EdOIAZhXK+mMPskLxQcsqET70jWlVneuCocF11JzE5ouzefzUK9X/ofcaHI
C0Vfien5Haq6xHEw1Lyq47dVcd0ztqVDaolA7TzTnVzTwLT7REyvOOlb1eaq6ZoC
QpIiyLiJjytK1bof/H0HoJqHFEJtaPhL6no7HjhDMusKTXTOrWjG68Kwa6NgMzAa
XjUluyXPlIGs/M4qFZYlwWEo9lK5txNFiAfvu5DnrqMK4jvlpDpktCcIBAxBaN+2
c5X8prRGN+u6w0CgYrwV5+tzHaTho6FWQvA+XnuWlmFR2UJjkYXD5IQNDkbejWL4
6XWC6X2fEGXfTKR4f6mkXOyg2YtaBLI6cxgxqYVhjMpiRu00ncRKohZjqDXVtOll
u1HYrp5q7wYq/m9NVuZv8XYviTGSO/1oUR4n1Dng6g==
-----END CERTIFICATE-----
File added
-----BEGIN ENCRYPTED PRIVATE KEY-----
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
+C+hItN7Lj2S0FM6dEa+1g==
-----END ENCRYPTED PRIVATE KEY-----
use std::{io, path::PathBuf};
use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader, AsyncWrite, AsyncRead};
use crate::{handling::{
file_handlers::NamedFile,
methods::Method,
request::Request,
response::{Outcome, Response, ResponseBody, Status},
routes::{Body, Data},
}, utils::urlencoded::UrlEncodeData};
use crate::setup::MountPoint;
/// The Maximal size of the Body of an HTTP-Message in bytes
static MAX_HTTP_MESSAGE_SIZE: u16 = 4196;
/// Function which handles a TCP Connection according to http-Standards by taking in a
/// [tokio::net::TcpStream] and a [`Vec<MountPoint<'_>>`].
///
/// Firstly validates the headers and body, Aborts with error messages if it finds faults.
///
/// Secondly matches the request with a route in the mountpoint Vector and executes its handler
/// function. If it fails, checks for another, if nothing is found writes back a
/// [Status::NotFound]. If the handler function could respond it uses the [Response] of the handler
///
/// # Panics
/// No Panics
pub async fn handle_connection<T: AsyncRead + AsyncWrite + std::marker::Unpin>(mut stream: T, mountpoints: Vec<MountPoint<'_>>) {
let mut buf_reader = BufReader::new(&mut stream);
let mut http_request: Vec<String> = Vec::with_capacity(10);
loop {
let mut buffer = String::new();
if buf_reader.read_line(&mut buffer).await.is_err() {
eprintln!("\x1b[31mAborting due to invalid UTF-8 in request header\x1b[0m");
return;
}
if buffer == "\r\n" {
break;
}
http_request.push(buffer);
}
let request_status_line = if let Some(status_line) = http_request.get(0).cloned() {
status_line
} else {
eprintln!("\x1b[31mAborting due to missing headers\x1b[0m");
return;
};
let mut request = Request {
uri: if let Some(uri) = &request_status_line.split(' ').nth(1) {
if let Ok(uri) = UrlEncodeData::from_encoded(uri) {
uri
} else {
eprintln!("\x1b[31mAborting due to invalid uri\x1b[0m");
return;
}
} else {
eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m");
return;
},
cookies: Request::extract_cookies_from_vec(&mut http_request),
headers: http_request,
mime_type: None,
method: if let Some(method) = request_status_line.split(' ').next() {
if let Ok(ok) = method.parse() {
ok
} else {
eprintln!("\x1b[31mAborting due to invalid request method\x1b[0m");
return;
}
} else {
eprintln!("\x1b[31mAborting due to invalid status line\x1b[0m");
return;
},
};
let mut data = Data {
is_complete: false,
buffer: vec![],
};
if request.can_have_body() {
let length = if let Some(len) = request
.headers
.iter()
.filter(|header| header.starts_with("Content-Length: "))
.map(|header| {
let header = header.strip_prefix("Content-Length: ").unwrap();
header.trim().parse::<usize>()
})
.next()
{
if let Ok(size) = len {
size
} else {
eprintln!(
"\x1b[31m`{}` must have a `Content-Length` header\x1b[0m",
request.method
);
if let Err(e) = len_not_defined(stream, Status::LengthRequired).await {
error_occured_when_writing(e)
};
return;
}
} else {
if request.mandatory_body() {
eprintln!(
"\x1b[31m`{}` must have a `Content-Length` header\x1b[0m",
request.method
);
if let Err(e) = len_not_defined(stream, Status::LengthRequired).await {
error_occured_when_writing(e)
};
return;
}
0
};
if length != 0 {
request.mime_from_headers();
let mut buffer = vec![0u8; MAX_HTTP_MESSAGE_SIZE.into()];
let read = buf_reader.read(&mut buffer).await.unwrap();
if read != length {
if let Err(e) = len_not_defined(stream, Status::LengthRequired).await {
error_occured_when_writing(e)
};
return;
}
data.is_complete = true;
data.buffer = buffer;
}
}
let mut handled_response: Option<Outcome<Response, Status, Data>> = None;
for mountpoint in mountpoints {
if request.uri.raw_string().is_none() {
return;
}
if !request.uri.raw_string().unwrap().starts_with(mountpoint.mountpoint) {
continue;
}
let mounted_request_uri = request.uri.raw_string().unwrap().strip_prefix(mountpoint.mountpoint).unwrap();
for route in mountpoint.routes {
if (route.method != request.method)
&& ((route.method != Method::Get) && (request.method == Method::Head))
{
continue;
}
if !route.compare_uri(mounted_request_uri) {
continue;
}
handled_response = Some((route.handler)(
Request {
uri: UrlEncodeData::from_raw(mounted_request_uri),
..request.clone()
},
data.clone(),
));
if let Some(Outcome::Forward(_)) = handled_response {
continue;
}
break;
}
}
let response = match handled_response {
Some(val) => match val {
Outcome::Success(success) => success,
Outcome::Failure(error) => failure_handler(error),
Outcome::Forward(_) => failure_handler(Status::NotFound),
},
None => failure_handler(Status::NotFound),
};
if let Err(e) = response.write(stream, Some(request)).await {
eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m");
}
}
/// Dumb function that renders a 404 page from any status given. but still writes that status code.
fn failure_handler(status: Status) -> Response {
let page_404 = NamedFile::open(PathBuf::from("404.html")).unwrap();
Response {
cookies: None,
headers: vec![],
status: Some(status),
body: Box::new(Body::new(page_404.get_data(), page_404.get_mime())),
}
}
/// Handler for len_not_defined errors. writes back directly
async fn len_not_defined<T: AsyncRead + AsyncWrite + std::marker::Unpin>(stream: T, status: Status) -> io::Result<()> {
let page_411 = NamedFile::open(PathBuf::from("411.html")).unwrap();
Response {
cookies: None,
headers: vec![],
status: Some(status),
body: Box::new(Body::new(page_411.get_data(), page_411.get_mime())),
}
.write(stream, None)
.await?;
Ok(())
}
/// takes in an io error and writes it back in the server console to the user if writing failed
fn error_occured_when_writing(e: io::Error) {
eprintln!("\x1b[31mError {e} occured when trying to write answer to TCP-Stream for Client, aborting\x1b[0m");
}
pub mod handler;
use std::{fs, path::PathBuf};
use crate::{
handling::response::{ResponseBody, Status},
utils::mime::Mime,
};
#[derive(Debug)]
/// Struct to handle files on the server side.
/// Validates paths ignores actions like `..`
pub struct NamedFile {
/// The length of the file in bytes, format: [usize]
pub content_len: usize,
/// The Mime Type of the file as a [Mime]
pub content_type: Mime,
/// The content of the file as a [`Vec<u8>`]
pub content: Vec<u8>,
}
impl ResponseBody for NamedFile {
fn get_data(&self) -> Vec<u8> {
self.content.clone()
}
fn get_mime(&self) -> Mime {
self.content_type
}
fn get_len(&self) -> usize {
self.content_len
}
}
impl NamedFile {
/// Reads in a file as a [NamedFile]. Ignores seqences like `..`
///
/// # Panics
///
/// Panics if a [PathBuf] can't be convertet to a [str]
///
/// # Errors
///
/// Can give a [Status::NotFound] if it can't find the file
///
/// This function will return an error if .
pub fn open(path: PathBuf) -> Result<NamedFile, Status> {
let path = proove_path(path);
let data = fs::read(&path);
let data = match data {
Ok(dat) => dat,
Err(_) => {
return Err(Status::NotFound);
}
};
Ok(NamedFile {
content_len: data.len(),
content_type: Mime::from_filename(path.to_str().unwrap()),
content: data,
})
}
}
/// Validates a path so that seqences like `//` and `..` are omitted
///
/// # Panics
///
/// Panics if it can't convert a [PathBuf] to a [str]
fn proove_path(path: PathBuf) -> PathBuf {
PathBuf::from(
path.to_str()
.unwrap()
.split('/')
.filter(|&val| val != ".." && !val.is_empty())
.collect::<Vec<&str>>()
.join("/"),
)
}
use std::{fmt::Display, str::FromStr};
#[derive(PartialEq, Eq, Clone, Debug, Copy, PartialOrd, Ord)]
/// All HTTP Methods
pub enum Method {
Get,
Head,
Post,
Put,
Delete,
Connect,
Options,
Trace,
Patch,
}
impl Display for Method {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Get => write!(f, "GET"),
Self::Head => write!(f, "HEAD"),
Self::Post => write!(f, "POST"),
Self::Put => write!(f, "PUT"),
Self::Delete => write!(f, "DELETE"),
Self::Connect => write!(f, "CONNECT"),
Self::Options => write!(f, "OPTIONS"),
Self::Trace => write!(f, "TRACE"),
Self::Patch => write!(f, "PATCH"),
}
}
}
impl FromStr for Method {
type Err = &'static str;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"GET" => Ok(Self::Get),
"HEAD" => Ok(Self::Head),
"POST" => Ok(Self::Post),
"PUT" => Ok(Self::Put),
"DELETE" => Ok(Self::Delete),
"CONNECT" => Ok(Self::Connect),
"OPTIONS" => Ok(Self::Options),
"TRACE" => Ok(Self::Trace),
"PATCH" => Ok(Self::Patch),
_ => Err("Not a Method"),
}
}
}
pub mod file_handlers;
pub mod methods;
pub mod request;
pub mod response;
pub mod routes;
use std::collections::HashMap;
use super::Request;
impl Request {
/// Extracts the cookies from a Vector and gives back an optional HashMap of Strings
///
/// Returns none if there are no cookies or there is a problem with the cookies, for example
/// missing a value.
///
/// Removes the `Cookie: ` Header from the Vector of headers
///
/// # Examples
/// ```
/// use http::handling::request::Request;
///
///
/// let mut request_vec = vec![
/// "GET / HTTP/1.1".to_string(),
/// "Accept: sdf".to_string(),
/// "Cookie: io=23; f=as".to_string(),
/// "Format: gzip".to_string(),
/// ];
/// let right_finished_vec = vec![
/// "GET / HTTP/1.1".to_string(),
/// "Accept: sdf".to_string(),
/// "Format: gzip".to_string(),
/// ];
///
/// let mut wrong = vec!["GET / HTTP/1.1".to_string()];
///
/// assert_eq!(None, Request::extract_cookies_from_vec(&mut wrong));
/// let cookies = Request::extract_cookies_from_vec(&mut request_vec);
/// assert_eq!(right_finished_vec, request_vec);
/// assert_eq!("23", cookies.clone().unwrap().get("io").unwrap());
/// assert_eq!("as", cookies.unwrap().get("f").unwrap());
/// ```
///
/// # Panics
/// No Panics
pub fn extract_cookies_from_vec(headers: &mut Vec<String>) -> Option<HashMap<String, String>> {
let mut cookies: HashMap<String, String> = HashMap::new();
let mut cookies_string = if let Some(index) = headers
.iter()
.position(|header| header.starts_with("Cookie: "))
{
headers.remove(index)
} else {
return None;
};
cookies_string = cookies_string
.strip_prefix("Cookie: ")
.unwrap()
.trim()
.trim_matches('"')
.to_string();
for cookie in cookies_string.split(';') {
let Some((name, cookie)) = cookie.split_once('=') else {
return None;
};
cookies
.entry(name.trim().to_string())
.or_insert(cookie.trim().to_string());
}
Some(cookies)
}
}
#[cfg(test)]
mod test {
use crate::handling::request::Request;
#[test]
fn test_cookies() {
let mut request_vec = vec![
"GET / HTTP/1.1".to_string(),
"Accept: sdf".to_string(),
"Cookie: io=23; f=as".to_string(),
"Format: gzip".to_string(),
];
let right_finished_vec = vec![
"GET / HTTP/1.1".to_string(),
"Accept: sdf".to_string(),
"Format: gzip".to_string(),
];
let mut wrong = vec!["GET / HTTP/1.1".to_string()];
assert_eq!(None, Request::extract_cookies_from_vec(&mut wrong));
let cookies = Request::extract_cookies_from_vec(&mut request_vec);
assert_eq!(right_finished_vec, request_vec);
assert_eq!("23", cookies.clone().unwrap().get("io").unwrap());
assert_eq!("as", cookies.unwrap().get("f").unwrap());
}
}
use std::{collections::HashMap, error::Error, fmt::Display};
use crate::{
handling::methods::Method,
utils::{mime::Mime, urlencoded::UrlEncodeData},
};
type HeaderMap = Vec<String>;
/// A struct to handle Requests
///
#[derive(Clone)]
pub struct Request {
/// The requested Uri
pub uri: UrlEncodeData,
/// All headers of the request that haven't been parsed
pub headers: HeaderMap,
/// The methods Request represented with the [Method]
pub method: Method,
/// An optional HashMap representation of all Cookies of the request
pub cookies: Option<HashMap<String, String>>,
/// If the has a body it represents the [Mime]-type of the body
pub mime_type: Option<(Mime, String)>,
// pub connection: ConnectionMeta,
}
// struct ConnectionMeta {
// remote: Option<SocketAddr>,
// // certificates
// }
#[derive(Clone, Debug, Copy, PartialEq, Eq, PartialOrd, Ord)]
/// Media Types in which a Route can be requested to ansewr, optional for routes
pub enum MediaType {
/// Json Data
Json,
/// Plain Text
Plain,
/// HTML Text
Html,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
pub enum ParseErrors {
NoData,
BadData,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
/// Errors that Occur when a Form can't be parsed
pub struct ParseFormError {
pub error: ParseErrors,
}
impl Display for ParseFormError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.error)
}
}
impl Display for ParseErrors {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ParseErrors::NoData => write!(f, "No Data at key"),
ParseErrors::BadData => write!(f, "Bad Data at key"),
}
}
}
impl Error for ParseFormError {}
use std::collections::HashMap;
use crate::{
handling::routes::Data,
utils::{mime::Mime, urlencoded::DeCodable},
};
use super::{datatypes::ParseErrors, ParseFormError, Request};
static TWO_NEWLINES: u8 = 3;
impl Request {
/// # Gets data from a get_form as a HashMap
///
/// # Errors
/// Gives back a [ParseFormError], top level, if there is lacking data
///
/// If everything is fine on the top level it gives back a HashMap of keys and Results, that
/// indicate wether the key exists with the [ParseFormError] with an error of
/// [ParseErrors::NoData] or wether the key is corrupt with the [ParseErrors::BadData]-Variant
///
/// # Examples
/// ```
/// use http::handling::{request::{Request, ParseFormError, ParseErrors}, methods::Method};
/// use http::utils::urlencoded::UrlEncodeData;
///
///
/// let request = Request {
/// uri: UrlEncodeData::from_encoded("/form?name=Name&age=Age").unwrap(),
/// headers: vec![],
/// method: Method::Get,
/// cookies: None,
/// mime_type: None,
/// };
/// let right = request.get_get_form_keys(&["name", "age"]).unwrap();
/// assert_eq!(&"Name", right.get("name").unwrap().as_ref().unwrap());
/// assert_eq!(&"Age", right.get("age").unwrap().as_ref().unwrap());
///
/// let wrong_request = Request {
/// uri: UrlEncodeData::from_encoded("/form").unwrap(),
/// ..request.clone()
/// };
/// assert_eq!(
/// Err(ParseFormError {
/// error: ParseErrors::NoData
/// }),
/// wrong_request.get_get_form_keys(&["name", "age"])
/// );
///
/// let bad_data = Request {
/// uri: UrlEncodeData::from_encoded("/form?age=").unwrap(),
/// ..request.clone()
/// };
/// let wrong = bad_data.get_get_form_keys(&["name", "age"]).unwrap();
/// assert_eq!(
/// &Err(ParseFormError {
/// error: ParseErrors::NoData
/// }),
/// wrong.get("name").unwrap()
/// );
/// assert_eq!(&Ok(""), wrong.get("age").unwrap());
/// ```
///
/// # Panics
/// No Panics
pub fn get_get_form_keys<'a>(
&'a self,
keys: &'a [&str],
) -> Result<HashMap<&str, Result<&str, ParseFormError>>, ParseFormError> {
let Some(uri) = self.uri.raw_string() else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
let data = if let Some(val) = uri.split_once('?') {
val
} else {
return Err(ParseFormError {
error: ParseErrors::NoData,
});
};
let data = data
.1
.split('&')
.map(|kvp| kvp.split_once('='))
.collect::<Vec<Option<(&str, &str)>>>();
let mut values: HashMap<&str, &str> = HashMap::new();
for kvp in data {
let kvp = if let Some(kvp) = kvp {
kvp
} else {
continue;
};
values.insert(kvp.0, kvp.1);
}
let mut response = HashMap::new();
for key in keys {
let entry = if let Some(val) = values.get(key) {
Ok(*val)
} else {
Err(ParseFormError {
error: ParseErrors::NoData,
})
};
response.insert(*key, entry);
}
Ok(response)
}
pub fn get_post_data<'a>(
&'a self,
keys: &[&'a str],
data: &Data,
) -> Result<HashMap<String, Result<Vec<u8>, ParseFormError>>, ParseFormError> {
let data = data.buffer.as_slice();
let mut keymap: HashMap<String, Result<Vec<u8>, ParseFormError>> =
HashMap::with_capacity(keys.len());
for key in keys {
keymap.entry(key.to_string()).or_insert(Err(ParseFormError {
error: ParseErrors::NoData,
}));
}
let Some(ref mime_type) = self.mime_type else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
match mime_type.0 {
Mime::ApplicationXWwwFormUrlencoded => {
let Ok(data) = String::from_utf8(data.to_vec()) else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
for kvp in data.split('&') {
let Some(kvp) = kvp.split_once('=') else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
let Ok(key) = kvp.0.decode() else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
let Ok(key) = String::from_utf8(key) else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
let Ok(value) = kvp.1.trim_end_matches('\0').decode() else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
let Some(thing) = keymap.get_mut(&key) else {
continue;
};
*thing = Ok(value);
}
}
Mime::MultipartFormData => {
let Some(mut boundary) = mime_type.1.split(';').find(|element| element.trim().starts_with("boundary=")) else {
return Err(ParseFormError { error: ParseErrors::BadData });
};
boundary = boundary
.trim()
.strip_prefix("boundary=")
.unwrap()
.trim_matches('"');
let mut temp_bound = "--".to_string();
temp_bound.push_str(boundary);
let end_boundary: Vec<u8> = format!("{temp_bound}--\r").into();
temp_bound.push('\r');
let boundary = temp_bound.as_bytes();
Self::get_multipart_data(data, boundary, &end_boundary, &mut keymap);
}
_ => {
return Err(ParseFormError {
error: ParseErrors::BadData,
})
}
};
Ok(keymap)
}
fn get_multipart_data(
data: &[u8],
boundary: &[u8],
end_boundary: &[u8],
map: &mut HashMap<String, Result<Vec<u8>, ParseFormError>>,
) {
let mut current_part: Vec<&[u8]> = vec![];
let mut current_key: Option<String> = None;
let mut ignore_line = 0;
for part in data.split(|byte| byte == &b'\n') {
if part == [b'\r'] {
if current_key.is_some() {
if ignore_line >= TWO_NEWLINES {
current_part.push(&[b'\n']);
continue;
}
ignore_line += 1;
}
continue;
}
if part == end_boundary {
if let Some(key) = &current_key {
let mut part = current_part.join(&b'\n');
if part.ends_with(&[b'\r']) {
part.pop();
}
map.insert(key.to_string(), Ok(part));
}
break;
}
if part == boundary {
if let Some(key) = &current_key {
let mut part = current_part.join(&b'\n');
if part.ends_with(&[b'\r']) {
part.pop();
}
map.insert(key.to_string(), Ok(part));
}
current_part = vec![];
current_key = None;
ignore_line = 0;
continue;
}
if part.starts_with(b"Content-Disposition: form-data; name=") {
let headers = part
.split(|byte| byte == &b';')
.filter(|header| !header.is_empty())
.collect::<Vec<_>>();
if headers.len() < 2 {
continue;
}
let name = headers[1].split(|byte| byte == &b'=').collect::<Vec<_>>();
if name.len() != 2 {
continue;
}
let mkey = String::from_utf8_lossy(name[1])
.as_ref()
.trim_end()
.trim_matches('"')
.to_owned();
if map.contains_key::<str>(&mkey) {
current_key = Some(mkey.to_owned());
}
continue;
} else if current_key.is_some() {
current_part.push(part);
}
}
}
}
#[cfg(test)]
mod test {
use crate::{
handling::{
methods::Method,
request::{datatypes::ParseErrors, ParseFormError},
routes::Data,
},
utils::{
mime::Mime::{ApplicationXWwwFormUrlencoded, MultipartFormData},
urlencoded::UrlEncodeData,
},
};
use super::Request;
#[test]
fn try_get_test() {
let request = Request {
uri: UrlEncodeData::from_encoded("/form?name=Name&age=Age").unwrap(),
headers: vec![],
method: Method::Get,
cookies: None,
mime_type: None,
};
let right = request.get_get_form_keys(&["name", "age"]).unwrap();
assert_eq!(&"Name", right.get("name").unwrap().as_ref().unwrap());
assert_eq!(&"Age", right.get("age").unwrap().as_ref().unwrap());
let wrong_request = Request {
uri: UrlEncodeData::from_encoded("/form").unwrap(),
..request.clone()
};
assert_eq!(
Err(ParseFormError {
error: ParseErrors::NoData
}),
wrong_request.get_get_form_keys(&["name", "age"])
);
let bad_data = Request {
uri: UrlEncodeData::from_encoded("/form?age=").unwrap(),
..request.clone()
};
let wrong = bad_data.get_get_form_keys(&["name", "age"]).unwrap();
assert_eq!(
&Err(ParseFormError {
error: ParseErrors::NoData
}),
wrong.get("name").unwrap()
);
assert_eq!(&Ok(""), wrong.get("age").unwrap());
}
#[test]
fn try_post_text() {
let req = Request {
uri: UrlEncodeData::from_encoded("").unwrap(),
headers: vec!["Content-Type: application/x-www-form-urlencoded".to_string()],
method: Method::Post,
cookies: None,
mime_type: Some((ApplicationXWwwFormUrlencoded, "".into())),
};
let data = Data {
buffer: b"message=23&message1=24".to_vec(),
is_complete: true,
};
let map = req.get_post_data(&["message", "message1"], &data).unwrap();
assert_eq!(
&b"23".to_vec(),
map.get("message").unwrap().as_ref().unwrap()
);
assert_eq!(
&b"24".to_vec(),
map.get("message1").unwrap().as_ref().unwrap()
);
let req = Request {
uri: UrlEncodeData::from_encoded("").unwrap(),
headers: vec!["Content-Type: multipart/form-data; boundary=\"boundary\"".to_string()],
method: Method::Post,
cookies: None,
mime_type: Some((
MultipartFormData,
"charset=UTF-8; boundary=\"boundary\"".into(),
)),
};
let data = Data {
buffer: b"--boundary\r
Content-Disposition: form-data; name=\"field1\"\r
\r
value1\r
--boundary\r
Content-Disposition: form-data; name=\"field2\"; filename=\"example.txt\"\r
\r
va\nlue2\r
--boundary--\r
"
.to_vec(),
is_complete: true,
};
let map = req.get_post_data(&["field1", "field2"], &data).unwrap();
assert_eq!(
&b"value1".to_vec(),
map.get("field1").unwrap().as_ref().unwrap()
);
assert_eq!(
&b"va\nlue2".to_vec(),
map.get("field2").unwrap().as_ref().unwrap()
);
}
}
mod cookies;
mod datatypes;
mod form_utils;
mod request_impl;
mod request_mime;
pub use datatypes::{MediaType, ParseErrors, ParseFormError, Request};
use crate::handling::methods::Method;
use super::Request;
impl Request {
/// Checks if the request can have a body
pub fn can_have_body(&self) -> bool {
matches!(
self.method,
Method::Post | Method::Put | Method::Patch | Method::Delete
)
}
/// Checks if a body is mandatory for the Request
pub fn mandatory_body(&self) -> bool {
matches!(self.method, Method::Post | Method::Put | Method::Patch)
}
}
use super::datatypes::Request;
impl Request {
/// Sets the `mime_type` of this [`Request`] from the headers.
///
/// The mime_type can remain none if there isn't a `Content-Type: ` header
///
/// # Example
/// ```
/// use http::{
/// handling::{methods::Method, request::Request},
/// utils::{mime::Mime, urlencoded::UrlEncodeData},
/// };
///
/// let mut request = Request {
/// uri: UrlEncodeData::from_encoded("thing").unwrap(),
/// headers: vec![
/// "GET / 23".to_string(),
/// "SDF:LKJSD:F".to_string(),
/// "Content-Type: text/plain; charset=UTF-8".to_string(),
/// "SDF".to_string(),
/// ],
/// method: Method::Get,
/// cookies: None,
/// mime_type: None,
/// };
/// let mut wrong = Request {
/// uri: UrlEncodeData::from_encoded("thing").unwrap(),
/// headers: vec![
/// "GET / 23".to_string(),
/// "SDF:LKJSD:F".to_string(),
/// "SDF".to_string(),
/// ],
/// method: Method::Get,
/// cookies: None,
/// mime_type: None,
/// };
/// request.mime_from_headers();
/// wrong.mime_from_headers();
/// assert_eq!(None, wrong.mime_type);
/// assert_eq!((Mime::TextPlain, " charset=UTF-8".to_string()), request.mime_type.unwrap());
/// ```
/// # Panics
/// No Panics
pub fn mime_from_headers(&mut self) {
let Some(content_type_header) = self.headers
.iter()
.find(|header| header.starts_with("Content-Type: ")) else {
return;
};
let content_type_string: &str = content_type_header
.strip_prefix("Content-Type: ")
.unwrap()
.trim();
self.mime_type = if let Some(content_type) = content_type_string.split_once(';') {
let Ok(mime) = content_type.0.trim().parse() else {
return;
};
Some((mime, content_type.1.to_owned()))
} else {
let Ok(mime) = content_type_string.parse() else {
return;
};
Some((mime, "".to_owned()))
};
}
}
#[cfg(test)]
mod test {
use crate::{
handling::{methods::Method, request::Request},
utils::{mime::Mime, urlencoded::UrlEncodeData},
};
#[test]
pub fn test_mime_parse_from_header_vec() {
let mut request = Request {
uri: UrlEncodeData::from_raw("thing"),
headers: vec![
"GET / 23".to_string(),
"SDF:LKJSD:F".to_string(),
"Content-Type: text/plain".to_string(),
"SDF".to_string(),
],
method: Method::Get,
cookies: None,
mime_type: None,
};
let mut wrong = Request {
uri: UrlEncodeData::from_raw("thing"),
headers: vec![
"GET / 23".to_string(),
"SDF:LKJSD:F".to_string(),
"SDF".to_string(),
],
method: Method::Get,
cookies: None,
mime_type: None,
};
request.mime_from_headers();
wrong.mime_from_headers();
assert_eq!(None, wrong.mime_type);
assert_eq!((Mime::TextPlain, "".to_owned()), request.mime_type.unwrap());
}
}
use std::io::Result;
use tokio::io::{AsyncWriteExt, AsyncRead, AsyncWrite};
use crate::handling::{methods::Method, request::Request, response::Status};
use super::Response;
impl Response {
/// Builds a [`Vec<u8>`] valid http response from a [Response] and consumes it. Optionally
/// takes in a request for things like [Method::Head]
pub fn build(self, request: Option<Request>) -> Vec<u8> {
let compiled_headers = format!(
"HTTP/1.1 {}\r\nContent-Length: {}\r\nContent-Type: {}\r\n",
self.status.unwrap_or(Status::Ok),
self.body.get_len(),
self.body.get_mime()
) + &self.headers.join("\r\n")
+ "\r\n";
let is_head = if let Some(req) = request {
req.method == Method::Head
} else {
false
};
let compiled_body = if is_head {
vec![]
} else {
self.body.get_data()
};
let mut compiled_out: Vec<u8> = compiled_headers.into();
compiled_out.extend_from_slice(&compiled_body);
compiled_out
}
/// Builds and writes The http-Response, consumes the [tokio::net::TcpStream] [Request] and [Response]
pub async fn write<T: AsyncRead + AsyncWrite + std::marker::Unpin>(self, mut stream: T, request: Option<Request>) -> Result<()> {
let resp = self.build(request);
stream.write_all(&resp).await?;
Ok(())
}
}
use std::{collections::HashMap, str::FromStr, time::Duration};
use crate::{
handling::response::{cookie_management::error_types::CookieError, CookieBuilder},
utils::{url_utils::Uri, urlencoded::DeCodable},
};
macro_rules! update_map {
($map:expr, $key:expr) => {
*$map.get_mut($key).unwrap() = true;
};
}
use super::error_types::{ParseCookieError, ParseSameSiteError, SameSiteError};
/// Structure representing a Cookie
/// # Creating a Cookie:
/// ```
/// use http::handling::response::Cookie;
/// use http::handling::response::CookieBuilder;
///
/// let cookie = CookieBuilder::build("name", "value").finish();
/// ```
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
pub struct Cookie {
/// Storage for the cookie string. Only used if this structure was derived
/// from a string that was subsequently parsed.
pub(crate) cookie_string: Option<String>,
pub(crate) name: String,
pub(crate) value: String,
/// Whether this cookie was marked Secure.
pub(crate) secure: bool,
/// Whether this cookie was marked HttpOnly.
pub(crate) http_only: bool,
pub(crate) partitioned: bool,
// expires: Option<Tm>,
pub(crate) max_age: Option<Duration>,
/// The draft `SameSite` attribute.
pub(crate) same_site: Option<SameSite>,
/// The cookie's domain, if any.
pub(crate) domain: Option<String>,
/// The cookie's path domain, if any.
pub(crate) path: Option<Uri>,
pub(crate) expires: Option<String>,
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
/// SameSite Paremeters
pub enum SameSite {
/// Requires Secure
None,
Lax,
Strict,
}
impl std::fmt::Display for SameSite {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::None => write!(f, "SameSite=None"),
Self::Lax => write!(f, "SameSite=Lax"),
Self::Strict => write!(f, "SameSite=Strict"),
}
}
}
impl FromStr for SameSite {
type Err = ParseSameSiteError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"None" => Ok(SameSite::None),
"Lax" => Ok(SameSite::Lax),
"Strict" => Ok(SameSite::Strict),
_ => Err(Self::Err {
inner: SameSiteError::NotAValidVariant,
}),
}
}
}
impl std::fmt::Display for Cookie {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let mut appendix = String::from("");
if self.secure {
appendix += "; Secure";
}
if self.http_only {
appendix += "; HttpOnly";
}
if self.partitioned {
appendix += "; Partitioned";
}
if let Some(max_age) = &self.max_age {
appendix += &format!("; Max-Age={}", max_age.as_secs());
}
if let Some(domain) = &self.domain {
appendix += &format!("; Domain={}", domain);
}
if let Some(path) = &self.path {
appendix += &format!("; Path={}", path);
}
if let Some(same_site) = &self.same_site {
appendix += &format!("; {}", same_site);
if !self.secure && *same_site == SameSite::None {
appendix += "; Secure";
}
}
if let Some(expires) = &self.expires {
appendix += &format!("; Expires={}", expires)
}
write!(f, "Set-Cookie: {}={}{}", self.name, self.value, appendix)
}
}
impl FromStr for Cookie {
type Err = ParseCookieError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut map = HashMap::with_capacity(8);
map.insert("Partitioned", false);
map.insert("HttpOnly", false);
map.insert("Secure", false);
map.insert("SameSite", false);
map.insert("Max-Age", false);
map.insert("Domain", false);
map.insert("Path", false);
map.insert("Expires", false);
let mut final_result = CookieBuilder::build("", "");
let mut first = true;
let stripped = s.strip_prefix("Set-Cookie: ").ok_or(Self::Err {
inner: CookieError::NoSetCookieHeader,
})?;
for part in stripped.split(';') {
let trimmed_part = part.trim();
if first {
let Some(name_val) = part.split_once('=') else {
return Err(Self::Err { inner: CookieError::MissingEqual });
};
unsafe {
final_result = CookieBuilder::build(
&String::from_utf8_unchecked(if let Ok(name) = name_val.0.decode() {
name
} else {
name_val.0.into()
}),
&String::from_utf8_unchecked(if let Ok(value) = name_val.1.decode() {
value
} else {
name_val.1.into()
}),
);
}
first = false;
continue;
}
if !map.get("Max-Age").unwrap() && trimmed_part.starts_with("Max-Age=") {
final_result = final_result.max_age(Duration::from_secs(
trimmed_part
.strip_prefix("Max-Age=")
.unwrap()
.parse()
.map_err(|_| Self::Err {
inner: CookieError::InvalidMaxAge,
})?,
));
update_map!(map, "Max-Age");
continue;
}
if !map.get("Expires").unwrap() && trimmed_part.starts_with("Expires=") {
final_result = final_result.expires(trimmed_part.strip_prefix("Expires=").unwrap());
update_map!(map, "Expires");
continue;
}
if !map.get("HttpOnly").unwrap() && trimmed_part == "HttpOnly" {
final_result = final_result.http_only(true);
update_map!(map, "HttpOnly");
continue;
}
if !map.get("SameSite").unwrap() && trimmed_part.starts_with("SameSite=") {
final_result = final_result.same_site(
trimmed_part
.strip_prefix("SameSite=")
.unwrap()
.parse::<SameSite>()
.map_err(|err| Self::Err {
inner: CookieError::InvilidSameSite(err.inner),
})?,
);
update_map!(map, "SameSite");
continue;
}
if !map.get("Path").unwrap() && trimmed_part.starts_with("Path=") {
final_result = final_result.path(
trimmed_part
.strip_prefix("Path=")
.unwrap()
.parse::<Uri>()
.map_err(|err| Self::Err {
inner: CookieError::PathError(err.error),
})?,
);
update_map!(map, "Path");
continue;
}
if !map.get("Domain").unwrap() && trimmed_part.starts_with("Domain=") {
final_result = final_result.domain(trimmed_part.strip_prefix("Domain=").unwrap());
update_map!(map, "Domain");
continue;
}
if !map.get("Secure").unwrap() && trimmed_part == "Secure" {
final_result = final_result.secure(true);
update_map!(map, "Secure");
continue;
}
if !map.get("Partitioned").unwrap() && trimmed_part == "Partitioned" {
final_result = final_result.partitioned(true);
update_map!(map, "Partitioned");
continue;
}
}
Ok(final_result.finish())
}
}
#[cfg(test)]
mod test {
use std::time::Duration;
use crate::handling::response::{Cookie, CookieBuilder};
use super::SameSite;
#[test]
fn test_cookie_to_string() {
let test_cookie1 = CookieBuilder::build("a", "cookie").finish().to_string();
let test_cookie1_res = "Set-Cookie: a=cookie";
let test_cookie2 = CookieBuilder::build("a", "secure_cookie")
.secure(true)
.finish()
.to_string();
let test_cookie2_res = "Set-Cookie: a=secure_cookie; Secure";
let test_cookie3 = CookieBuilder::build("ab", "ss")
.max_age(Duration::from_secs(24))
.domain("codecraft.com")
.path("/".parse().unwrap())
.same_site(SameSite::None)
.http_only(true)
.partitioned(true)
.expires("Monday")
.finish();
let test_cookie3_res = "Set-Cookie: ab=ss; Secure; HttpOnly; Partitioned; \
Max-Age=24; Domain=codecraft.com; Path=/; SameSite=None; Expires=Monday";
assert_eq!(test_cookie1_res, test_cookie1);
assert_eq!(test_cookie2_res, test_cookie2);
assert_eq!(test_cookie3_res, test_cookie3.to_string());
}
#[test]
fn cookie_from_string() {
let test_cookie3_res = "Set-Cookie: ab=ss; HttpOnly; Partitioned; \
Max-Age=24; Domain=codecraft.com; Path=/; SameSite=None; Secure; Expires=Monday";
let test_cookie3 = CookieBuilder::build("ab", "ss")
.max_age(Duration::from_secs(24))
.domain("codecraft.com")
.path("/".parse().unwrap())
.same_site(SameSite::None)
.http_only(true)
.partitioned(true)
.expires("Monday")
.finish();
assert_eq!(test_cookie3, test_cookie3_res.parse::<Cookie>().unwrap());
}
}
use std::time::Duration;
use crate::utils::{url_utils::Uri, urlencoded::EnCodable};
use super::{Cookie, SameSite};
/// Builder wrapper for a Cookie
///
/// # Example
/// ```
/// use http::handling::response::Cookie;
/// use http::handling::response::CookieBuilder;
///
/// let cookie = CookieBuilder::build("name", "value").path("/".parse().unwrap()).finish();
/// ```
#[derive(Debug)]
pub struct CookieBuilder {
/// Cookie under the hood
inner: Cookie,
}
impl CookieBuilder {
/// Builds a basic CookieBuilder from a name and a value
pub fn build(name: &str, value: &str) -> Self {
CookieBuilder {
inner: Cookie {
cookie_string: None,
name: name.encode(),
value: value.encode(),
max_age: None,
domain: None,
path: None,
secure: false,
http_only: false,
same_site: None,
expires: None,
partitioned: false,
},
}
}
pub fn finish(self) -> Cookie {
self.inner
}
pub fn max_age(mut self, duration: Duration) -> Self {
self.inner.max_age = Some(duration);
self
}
pub fn domain(mut self, domain: &str) -> Self {
self.inner.domain = Some(domain.encode());
self
}
pub fn path(mut self, path: Uri) -> Self {
self.inner.path = Some(path);
self
}
pub fn secure(mut self, secure: bool) -> Self {
self.inner.secure = secure;
self
}
pub fn http_only(mut self, http_only: bool) -> Self {
self.inner.http_only = http_only;
self
}
pub fn same_site(mut self, same_site: SameSite) -> Self {
if same_site == SameSite::None {
self.inner.secure = true;
}
self.inner.same_site = Some(same_site);
self
}
pub fn expires(mut self, expire: &str) -> Self {
self.inner.expires = Some(expire.encode());
self
}
pub fn partitioned(mut self, partitioned: bool) -> Self {
self.inner.partitioned = partitioned;
self
}
pub fn name(mut self, name: &str) -> Self {
self.inner.name = name.encode();
self
}
pub fn value(mut self, value: &str) -> Self {
self.inner.value = value.encode();
self
}
}