async-h1-2.3.4/.cargo_vcs_info.json0000644000000001360000000000100124650ustar { "git": { "sha1": "5e824ff0f1a6e94f5ab7ffc5b71b20beff2473db" }, "path_in_vcs": "" }async-h1-2.3.4/.github/CODE_OF_CONDUCT.md000064400000000000000000000061521046102023000154200ustar 00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: - Using welcoming and inclusive language - Being respectful of differing viewpoints and experiences - Gracefully accepting constructive criticism - Focusing on what is best for the community - Showing empathy towards other community members Examples of unacceptable behavior by participants include: - The use of sexualized language or imagery and unwelcome sexual attention or advances - Trolling, insulting/derogatory comments, and personal or political attacks - Public or private harassment - Publishing others' private information, such as a physical or electronic address, without explicit permission - Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at yoshuawuyts@gmail.com, or through IRC. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html async-h1-2.3.4/.github/CONTRIBUTING.md000064400000000000000000000051771046102023000150600ustar 00000000000000# Contributing Contributions include code, documentation, answering user questions, running the project's infrastructure, and advocating for all types of users. The project welcomes all contributions from anyone willing to work in good faith with other contributors and the community. No contribution is too small and all contributions are valued. This guide explains the process for contributing to the project's GitHub Repository. - [Code of Conduct](#code-of-conduct) - [Bad Actors](#bad-actors) ## Code of Conduct The project has a [Code of Conduct](./CODE_OF_CONDUCT.md) that *all* contributors are expected to follow. This code describes the *minimum* behavior expectations for all contributors. As a contributor, how you choose to act and interact towards your fellow contributors, as well as to the community, will reflect back not only on yourself but on the project as a whole. The Code of Conduct is designed and intended, above all else, to help establish a culture within the project that allows anyone and everyone who wants to contribute to feel safe doing so. Should any individual act in any way that is considered in violation of the [Code of Conduct](./CODE_OF_CONDUCT.md), corrective actions will be taken. It is possible, however, for any individual to *act* in such a manner that is not in violation of the strict letter of the Code of Conduct guidelines while still going completely against the spirit of what that Code is intended to accomplish. Open, diverse, and inclusive communities live and die on the basis of trust. Contributors can disagree with one another so long as they trust that those disagreements are in good faith and everyone is working towards a common goal. ## Bad Actors All contributors to tacitly agree to abide by both the letter and spirit of the [Code of Conduct](./CODE_OF_CONDUCT.md). Failure, or unwillingness, to do so will result in contributions being respectfully declined. A *bad actor* is someone who repeatedly violates the *spirit* of the Code of Conduct through consistent failure to self-regulate the way in which they interact with other contributors in the project. In doing so, bad actors alienate other contributors, discourage collaboration, and generally reflect poorly on the project as a whole. Being a bad actor may be intentional or unintentional. Typically, unintentional bad behavior can be easily corrected by being quick to apologize and correct course *even if you are not entirely convinced you need to*. Giving other contributors the benefit of the doubt and having a sincere willingness to admit that you *might* be wrong is critical for any successful open collaboration. Don't be a bad actor. async-h1-2.3.4/.github/workflows/ci.yaml000064400000000000000000000024451046102023000161360ustar 00000000000000name: CI on: pull_request: push: branches: - staging - trying env: RUSTFLAGS: -Dwarnings jobs: build_and_test: name: Build and test runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macOS-latest] rust: [nightly] steps: - uses: actions/checkout@master - name: Install ${{ matrix.rust }} uses: actions-rs/toolchain@v1 with: toolchain: ${{ matrix.rust }} override: true - name: check uses: actions-rs/cargo@v1 with: command: check args: --all --bins --examples - name: check unstable uses: actions-rs/cargo@v1 with: command: check args: --all --benches --bins --examples --tests - name: tests uses: actions-rs/cargo@v1 with: command: test args: --all check_fmt_and_docs: name: Checking fmt, clippy, and docs runs-on: ubuntu-latest steps: - uses: actions/checkout@master - uses: actions-rs/toolchain@v1 with: toolchain: nightly components: rustfmt, clippy override: true - name: clippy run: cargo clippy --tests --examples -- -D warnings - name: fmt run: cargo fmt --all -- --check - name: Docs run: cargo doc async-h1-2.3.4/.gitignore000064400000000000000000000001011046102023000132350ustar 00000000000000coverage/ target/ tmp/ dist/ npm-debug.log* Cargo.lock .DS_Store async-h1-2.3.4/.travis.yml000064400000000000000000000004051046102023000133650ustar 00000000000000language: rust rust: - stable before_script: | rustup component add rustfmt-preview && rustup component add clippy-preview script: | cargo fmt -- --check && cargo clippy -- -D clippy && cargo build --verbose && cargo test --verbose cache: cargo async-h1-2.3.4/Cargo.lock0000644000000671260000000000100104540ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "ansi_term" version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" dependencies = [ "winapi", ] [[package]] name = "anyhow" version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" [[package]] name = "async-attributes" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "efd3d156917d94862e779f356c5acae312b08fd3121e792c857d7928c8088423" dependencies = [ "quote", "syn", ] [[package]] name = "async-channel" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" dependencies = [ "concurrent-queue 1.2.2", "event-listener", "futures-core", ] [[package]] name = "async-dup" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7427a12b8dc09291528cfb1da2447059adb4a257388c2acd6497a79d55cf6f7c" dependencies = [ "futures-io", "simple-mutex", ] [[package]] name = "async-executor" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb877970c7b440ead138f6321a3b5395d6061183af779340b65e20c0fede9146" dependencies = [ "async-task", "concurrent-queue 1.2.2", "fastrand 1.4.0", "futures-lite", "once_cell", "vec-arena", ] [[package]] name = "async-global-executor" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1b6f5d7df27bd294849f8eec66ecfc63d11814df7a4f5d74168a2394467b776" dependencies = [ "async-channel", "async-executor", "async-io", "async-lock", "blocking", "futures-lite", "once_cell", ] [[package]] name = "async-h1" version = "2.3.4" dependencies = [ "async-channel", "async-dup", "async-global-executor", "async-io", "async-std", "futures-lite", "http-types", "httparse", "log", "pin-project", "pretty_assertions", ] [[package]] name = "async-io" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fc5b45d93ef0529756f812ca52e44c221b35341892d3dcc34132ac02f3dd2af" dependencies = [ "async-lock", "autocfg", "cfg-if 1.0.0", "concurrent-queue 2.3.0", "futures-lite", "log", "parking", "polling", "rustix", "slab", "socket2", "waker-fn", ] [[package]] name = "async-lock" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "287272293e9d8c41773cec55e365490fe034813a2f172f502d6ddcf75b2f582b" dependencies = [ "event-listener", ] [[package]] name = "async-std" version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8056f1455169ab86dd47b47391e4ab0cbd25410a70e9fe675544f49bafaf952" dependencies = [ "async-attributes", "async-channel", "async-global-executor", "async-io", "async-lock", "crossbeam-utils", "futures-channel", "futures-core", "futures-io", "futures-lite", "gloo-timers", "kv-log-macro", "log", "memchr", "num_cpus", "once_cell", "pin-project-lite", "pin-utils", "slab", "wasm-bindgen-futures", ] [[package]] name = "async-task" version = "4.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91831deabf0d6d7ec49552e489aed63b7456a7a3c46cff62adad428110b0af0" [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] name = "base64" version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "blocking" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8c36a4d0d48574b3dd360b4b7d95cc651d2b6557b6402848a27d4b228a473e2a" dependencies = [ "async-channel", "async-lock", "async-task", "fastrand 2.0.1", "futures-io", "futures-lite", "piper", "tracing", ] [[package]] name = "bumpalo" version = "3.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820" [[package]] name = "cache-padded" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" [[package]] name = "cc" version = "1.0.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" [[package]] name = "cfg-if" version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "concurrent-queue" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" dependencies = [ "cache-padded", ] [[package]] name = "concurrent-queue" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f057a694a54f12365049b0958a1685bb52d567f5593b355fbf685838e873d400" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" dependencies = [ "cfg-if 1.0.0", ] [[package]] name = "ctor" version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "10bcb9d7dcbf7002aaffbb53eac22906b64cdcc127971dcc387d8eb7c95d5560" dependencies = [ "quote", "syn", ] [[package]] name = "difference" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "524cbf6897b527295dff137cec09ecf3a05f4fddffd7dfcd1585403449e74198" [[package]] name = "errno" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" dependencies = [ "libc", "windows-sys", ] [[package]] name = "event-listener" version = "2.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" [[package]] name = "fastrand" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca5faf057445ce5c9d4329e382b2ce7ca38550ef3b73a5348362d5f24e0c7fe3" dependencies = [ "instant", ] [[package]] name = "fastrand" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "25cbce373ec4653f1a01a31e8a5e5ec0c622dc27ff9c4e6606eefef5cbbed4a5" [[package]] name = "form_urlencoded" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ece68d15c92e84fa4f19d3780f1294e5ca82a78a6d515f1efaabcc144688be00" dependencies = [ "matches", "percent-encoding", ] [[package]] name = "futures-channel" version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2d31b7ec7efab6eefc7c57233bb10b847986139d88cc2f5a02a1ae6871a1846" dependencies = [ "futures-core", ] [[package]] name = "futures-core" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "88d1c26957f23603395cd326b0ffe64124b818f4449552f960d815cfba83a53d" [[package]] name = "futures-io" version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" [[package]] name = "futures-lite" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49a9d51ce47660b1e808d3c990b4709f2f415d928835a17dfd16991515c46bce" dependencies = [ "fastrand 1.4.0", "futures-core", "futures-io", "memchr", "parking", "pin-project-lite", "waker-fn", ] [[package]] name = "getrandom" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" dependencies = [ "cfg-if 1.0.0", "libc", "wasi", ] [[package]] name = "gloo-timers" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47204a46aaff920a1ea58b11d03dec6f704287d27561724a4631e450654a891f" dependencies = [ "futures-channel", "futures-core", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "hermit-abi" version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aca5565f760fb5b220e499d72710ed156fdb74e631659e99377d9ebfbd13ae8" dependencies = [ "libc", ] [[package]] name = "hermit-abi" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" [[package]] name = "http-types" version = "2.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e9b187a72d63adbfba487f48095306ac823049cb504ee195541e91c7775f5ad" dependencies = [ "anyhow", "async-channel", "base64", "futures-lite", "infer", "pin-project-lite", "rand", "serde", "serde_json", "serde_qs", "serde_urlencoded", "url", ] [[package]] name = "httparse" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" [[package]] name = "idna" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9" dependencies = [ "matches", "unicode-bidi", "unicode-normalization", ] [[package]] name = "infer" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64e9829a50b42bb782c1df523f78d332fe371b10c661e78b7a3c34b0198e9fac" [[package]] name = "instant" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61124eeebbd69b8190558df225adf7e4caafce0d743919e5d6b19652314ec5ec" dependencies = [ "cfg-if 1.0.0", ] [[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.3", "libc", "windows-sys", ] [[package]] name = "itoa" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd25036021b0de88a0aff6b850051563c6516d0bf53f8638938edbb9de732736" [[package]] name = "js-sys" version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf3d7383929f7c9c7c2d0fa596f325832df98c3704f2c60553080f7127a58175" dependencies = [ "wasm-bindgen", ] [[package]] name = "kv-log-macro" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0de8b303297635ad57c9f5059fd9cee7a47f8e8daa09df0fcd07dd39fb22977f" dependencies = [ "log", ] [[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.149" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" [[package]] name = "linux-raw-sys" version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef53942eb7bf7ff43a617b3e2c1c4a5ecf5944a7c1bc12d7ee39bbb15e5c1519" [[package]] name = "log" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if 1.0.0", "value-bag", ] [[package]] name = "matches" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] name = "memchr" version = "2.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" [[package]] name = "num_cpus" version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" dependencies = [ "hermit-abi 0.1.17", "libc", ] [[package]] name = "once_cell" version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" [[package]] name = "output_vt100" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53cdc5b785b7a58c5aad8216b3dfa114df64b0b06ae6e1501cef91df2fbdf8f9" dependencies = [ "winapi", ] [[package]] name = "parking" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" [[package]] name = "percent-encoding" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "pin-project-lite" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8afb450f006bf6385ca15ef45d71d2288452bc3683ce2e2cacc0d18e4be60b58" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "piper" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "668d31b1c4eba19242f2088b2bf3316b82ca31082a8335764db4e083db7485d4" dependencies = [ "atomic-waker", "fastrand 2.0.1", "futures-io", ] [[package]] name = "polling" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2a7bc6b2a29e632e45451c941832803a18cce6781db04de8a04696cdca8bde4" dependencies = [ "cfg-if 0.1.10", "libc", "log", "wepoll-sys", "winapi", ] [[package]] name = "ppv-lite86" version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" [[package]] name = "pretty_assertions" version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f81e1644e1b54f5a68959a29aa86cde704219254669da328ecfdf6a1f09d427" dependencies = [ "ansi_term", "ctor", "difference", "output_vt100", ] [[package]] name = "proc-macro2" version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ "unicode-xid", ] [[package]] name = "quote" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" dependencies = [ "proc-macro2", ] [[package]] name = "rand" version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ "getrandom", "libc", "rand_chacha", "rand_core", "rand_hc", ] [[package]] name = "rand_chacha" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" dependencies = [ "ppv-lite86", "rand_core", ] [[package]] name = "rand_core" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ "getrandom", ] [[package]] name = "rand_hc" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" dependencies = [ "rand_core", ] [[package]] name = "rustix" version = "0.37.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4279d76516df406a8bd37e7dff53fd37d1a093f997a3c34a5c21658c126db06d" dependencies = [ "bitflags", "errno", "io-lifetimes", "libc", "linux-raw-sys", "windows-sys", ] [[package]] name = "ryu" version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] name = "serde" version = "1.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9bdd36f49e35b61d49efd8aa7fc068fd295961fd2286d0b2ee9a4c7a14e99cc3" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.119" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "552954ce79a059ddd5fd68c271592374bd15cab2274970380c000118aeffe1cd" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "serde_json" version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a" dependencies = [ "itoa", "ryu", "serde", ] [[package]] name = "serde_qs" version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7715380eec75f029a4ef7de39a9200e0a63823176b759d055b613f5a87df6a6" dependencies = [ "percent-encoding", "serde", "thiserror", ] [[package]] name = "serde_urlencoded" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "simple-mutex" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38aabbeafa6f6dead8cebf246fe9fae1f9215c8d29b3a69f93bd62a9e4a3dcd6" dependencies = [ "event-listener", ] [[package]] name = "slab" version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" [[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.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cc60a3d73ea6594cd712d830cc1f0390fd71542d8c8cd24e70cc54cdfd5e05d5" dependencies = [ "proc-macro2", "quote", "unicode-xid", ] [[package]] name = "thiserror" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76cc616c6abf8c8928e2fdcc0dbfab37175edd8fb49a4641066ad1364fdab146" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9be73a2caec27583d0046ef3796c3794f868a5bc813db689eed00c7631275cd1" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tinyvec" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ccf8dbc19eb42fba10e8feaaec282fb50e2c14b2726d6301dbfeed0f73306a6f" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tracing" version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" [[package]] name = "unicode-bidi" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" dependencies = [ "matches", ] [[package]] name = "unicode-normalization" version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a13e63ab62dbe32aeee58d1c5408d35c36c392bba5d9d3142287219721afe606" dependencies = [ "tinyvec", ] [[package]] name = "unicode-xid" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" [[package]] name = "url" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5909f2b0817350449ed73e8bcd81c8c3c8d9a7a5d8acba4b27db277f1868976e" dependencies = [ "form_urlencoded", "idna", "matches", "percent-encoding", "serde", ] [[package]] name = "value-bag" version = "1.0.0-alpha.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79923f7731dc61ebfba3633098bf3ac533bbd35ccd8c57e7088d9a5eebe0263f" dependencies = [ "ctor", "version_check", ] [[package]] name = "vec-arena" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eafc1b9b2dfc6f5529177b62cf806484db55b32dc7c9658a118e11bbeb33061d" [[package]] name = "version_check" version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" [[package]] name = "waker-fn" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" [[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" [[package]] name = "wasm-bindgen" version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cd364751395ca0f68cafb17666eee36b63077fb5ecd972bbcd74c90c4bf736e" dependencies = [ "cfg-if 1.0.0", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1114f89ab1f4106e5b55e688b828c0ab0ea593a1ea7c094b141b14cbaaec2d62" dependencies = [ "bumpalo", "lazy_static", "log", "proc-macro2", "quote", "syn", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fe9756085a84584ee9457a002b7cdfe0bfff169f45d2591d8be1345a6780e35" dependencies = [ "cfg-if 1.0.0", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6ac8995ead1f084a8dea1e65f194d0973800c7f571f6edd70adf06ecf77084" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b5a48c72f299d80557c7c62e37e7225369ecc0c963964059509fbafe917c7549" dependencies = [ "proc-macro2", "quote", "syn", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e7811dd7f9398f14cc76efd356f98f03aa30419dea46aa810d71e819fc97158" [[package]] name = "web-sys" version = "0.3.46" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "222b1ef9334f92a21d3fb53dc3fd80f30836959a90f9274a626d7e06315ba3c3" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "wepoll-sys" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcb14dea929042224824779fbc82d9fab8d2e6d3cbc0ac404de8edf489e77ff" dependencies = [ "cc", ] [[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.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[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_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[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_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[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_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[package]] name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" async-h1-2.3.4/Cargo.toml0000644000000030570000000000100104700ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2018" name = "async-h1" version = "2.3.4" authors = ["Yoshua Wuyts "] description = "Asynchronous HTTP 1.1 parser." documentation = "https://docs.rs/async-h1" readme = "README.md" keywords = [ "async", "http", "stream", "parser", "http1", ] categories = [ "asynchronous", "parser-implementations", "web-programming", "web-programming::http-client", "web-programming::http-server", ] license = "MIT OR Apache-2.0" repository = "https://github.com/http-rs/async-h1" [dependencies.async-channel] version = "1.5.1" [dependencies.async-dup] version = "1.2.2" [dependencies.async-global-executor] version = "2.3.1" [dependencies.async-io] version = "1.13.0" [dependencies.futures-lite] version = "1.13.0" [dependencies.http-types] version = "2.9.0" default-features = false [dependencies.httparse] version = "1.3.4" [dependencies.log] version = "0.4.11" [dependencies.pin-project] version = "1.0.2" [dev-dependencies.async-std] version = "1.7.0" features = ["attributes"] [dev-dependencies.pretty_assertions] version = "0.6.1" async-h1-2.3.4/Cargo.toml.orig000064400000000000000000000015541046102023000141510ustar 00000000000000[package] name = "async-h1" version = "2.3.4" license = "MIT OR Apache-2.0" repository = "https://github.com/http-rs/async-h1" documentation = "https://docs.rs/async-h1" description = "Asynchronous HTTP 1.1 parser." keywords = ["async", "http", "stream", "parser", "http1"] categories = [ "asynchronous", "parser-implementations", "web-programming", "web-programming::http-client", "web-programming::http-server" ] authors = ["Yoshua Wuyts "] readme = "README.md" edition = "2018" [dependencies] async-channel = "1.5.1" async-dup = "1.2.2" async-global-executor = "2.3.1" async-io = "1.13.0" futures-lite = "1.13.0" http-types = { version = "2.9.0", default-features = false } httparse = "1.3.4" log = "0.4.11" pin-project = "1.0.2" [dev-dependencies] async-std = { version = "1.7.0", features = ["attributes"] } pretty_assertions = "0.6.1" async-h1-2.3.4/LICENSE-APACHE000064400000000000000000000251161046102023000132060ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS Copyright 2019 Yoshua Wuyts Copyright 2016-2018 Michael Tilli (Pyfisch) & `httpdate` contributors Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. async-h1-2.3.4/LICENSE-MIT000064400000000000000000000022011046102023000127040ustar 00000000000000The MIT License (MIT) Copyright (c) 2019 Yoshua Wuyts Copyright (c) 2016-2018 Michael Tilli (Pyfisch) & `httpdate` contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. async-h1-2.3.4/README.md000064400000000000000000000047061046102023000125430ustar 00000000000000

async-h1

Asynchronous HTTP/1.1 parser.

Crates.io version Download docs.rs docs

API Docs | Releases | Contributing

## Installation ```sh $ cargo add async-h1 ``` ## Safety This crate uses ``#![forbid(unsafe_code)]`` to ensure everything is implemented in 100% Safe Rust. ## Minimum Supported Rust Version Given the rapidly-improving nature of async Rust, `async-h1` only guarantees it will work on the latest stable Rust compiler. Currently `async-h1` compiles on `rustc 1.40.0` and above, but we reserve the right to upgrade the minimum Rust version outside of major releases. If upgrading stable compiler versions is an issue we recommend pinning the version of `async-h1`. ## Contributing Want to join us? Check out our ["Contributing" guide][contributing] and take a look at some of these issues: - [Issues labeled "good first issue"][good-first-issue] - [Issues labeled "help wanted"][help-wanted] [contributing]: https://github.com/http-rs/async-h1/blob/main/.github/CONTRIBUTING.md [good-first-issue]: https://github.com/http-rs/async-h1/labels/good%20first%20issue [help-wanted]: https://github.com/http-rs/async-h1/labels/help%20wanted ## License Licensed under either of Apache License, Version 2.0 or MIT license at your option.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this crate by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions. async-h1-2.3.4/examples/client.rs000064400000000000000000000011551046102023000147210ustar 00000000000000use async_h1::client; use async_std::net::TcpStream; use http_types::{Error, Method, Request, Url}; #[async_std::main] async fn main() -> Result<(), Error> { let stream = TcpStream::connect("127.0.0.1:8080").await?; let peer_addr = stream.peer_addr()?; println!("connecting to {}", peer_addr); for i in 0usize..2 { println!("making request {}/2", i + 1); let url = Url::parse(&format!("http://{}/foo", peer_addr)).unwrap(); let req = Request::new(Method::Get, url); let res = client::connect(stream.clone(), req).await?; println!("{:?}", res); } Ok(()) } async-h1-2.3.4/examples/server.rs000064400000000000000000000023421046102023000147500ustar 00000000000000use async_std::net::{TcpListener, TcpStream}; use async_std::prelude::*; use async_std::task; use http_types::{Response, StatusCode}; #[async_std::main] async fn main() -> http_types::Result<()> { // Open up a TCP connection and create a URL. let listener = TcpListener::bind(("127.0.0.1", 8080)).await?; let addr = format!("http://{}", listener.local_addr()?); println!("listening on {}", addr); // For each incoming TCP connection, spawn a task and call `accept`. let mut incoming = listener.incoming(); while let Some(stream) = incoming.next().await { let stream = stream?; task::spawn(async { if let Err(err) = accept(stream).await { eprintln!("{}", err); } }); } Ok(()) } // Take a TCP stream, and convert it into sequential HTTP request / response pairs. async fn accept(stream: TcpStream) -> http_types::Result<()> { println!("starting new connection from {}", stream.peer_addr()?); async_h1::accept(stream.clone(), |_req| async move { let mut res = Response::new(StatusCode::Ok); res.insert_header("Content-Type", "text/plain"); res.set_body("Hello world"); Ok(res) }) .await?; Ok(()) } async-h1-2.3.4/run-fuzzer.sh000075500000000000000000000005121046102023000137410ustar 00000000000000#!/bin/sh set -e TARGET_NAME="$1" if [ -z "$TARGET_NAME" ]; then echo "$0: target name required" >&2 exit 1 fi mkdir -p "./fuzz/corpus/${TARGET_NAME}/" cargo +nightly fuzz run "${TARGET_NAME}" \ "./fuzz/corpus/${TARGET_NAME}/" "./fuzz/init_corpus/${TARGET_NAME}/" -- \ -dict="./fuzz/dicts/${TARGET_NAME}" \ -timeout=3 async-h1-2.3.4/rustfmt.toml000064400000000000000000000000501046102023000136510ustar 00000000000000edition = "2018" newline_style = "Unix" async-h1-2.3.4/src/body_encoder.rs000064400000000000000000000016571046102023000150570ustar 00000000000000use std::io; use std::pin::Pin; use std::task::{Context, Poll}; use futures_lite::io::AsyncRead as Read; use http_types::Body; use pin_project::pin_project; use crate::chunked::ChunkedEncoder; #[pin_project(project=BodyEncoderProjection)] #[derive(Debug)] pub(crate) enum BodyEncoder { Chunked(#[pin] ChunkedEncoder), Fixed(#[pin] Body), } impl BodyEncoder { pub(crate) fn new(body: Body) -> Self { match body.len() { Some(_) => Self::Fixed(body), None => Self::Chunked(ChunkedEncoder::new(body)), } } } impl Read for BodyEncoder { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { match self.project() { BodyEncoderProjection::Chunked(encoder) => encoder.poll_read(cx, buf), BodyEncoderProjection::Fixed(body) => body.poll_read(cx, buf), } } } async-h1-2.3.4/src/chunked/decoder.rs000064400000000000000000000275531046102023000154540ustar 00000000000000use std::fmt; use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; use futures_lite::io::{self, AsyncRead as Read}; use futures_lite::ready; use http_types::trailers::{Sender, Trailers}; /// Decodes a chunked body according to /// https://tools.ietf.org/html/rfc7230#section-4.1 #[derive(Debug)] pub struct ChunkedDecoder { /// The underlying stream inner: R, /// Current state. state: State, /// Current chunk size (increased while parsing size, decreased while reading chunk) chunk_size: u64, /// Trailer channel sender. trailer_sender: Option, } impl ChunkedDecoder { pub(crate) fn new(inner: R, trailer_sender: Sender) -> Self { ChunkedDecoder { inner, state: State::ChunkSize, chunk_size: 0, trailer_sender: Some(trailer_sender), } } } /// Decoder state. enum State { /// Parsing bytes from a chunk size ChunkSize, /// Expecting the \n at the end of a chunk size ChunkSizeExpectLf, /// Parsing the chunk body ChunkBody, /// Expecting the \r at the end of a chunk body ChunkBodyExpectCr, /// Expecting the \n at the end of a chunk body ChunkBodyExpectLf, /// Parsing trailers. Trailers(usize, Box<[u8; 8192]>), /// Sending trailers over the channel. TrailerSending(Pin + 'static + Send + Sync>>), /// All is said and done. Done, } impl fmt::Debug for State { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { State::ChunkSize => write!(f, "State::ChunkSize"), State::ChunkSizeExpectLf => write!(f, "State::ChunkSizeExpectLf"), State::ChunkBody => write!(f, "State::ChunkBody"), State::ChunkBodyExpectCr => write!(f, "State::ChunkBodyExpectCr"), State::ChunkBodyExpectLf => write!(f, "State::ChunkBodyExpectLf"), State::Trailers(len, _) => write!(f, "State::Trailers({}, _)", len), State::TrailerSending(_) => write!(f, "State::TrailerSending"), State::Done => write!(f, "State::Done"), } } } impl ChunkedDecoder { fn poll_read_byte(&mut self, cx: &mut Context<'_>) -> Poll> { let mut byte = [0u8]; if ready!(Pin::new(&mut self.inner).poll_read(cx, &mut byte))? == 1 { Poll::Ready(Ok(byte[0])) } else { eof() } } fn expect_byte( &mut self, cx: &mut Context<'_>, expected_byte: u8, expected: &'static str, ) -> Poll> { let byte = ready!(self.poll_read_byte(cx))?; if byte == expected_byte { Poll::Ready(Ok(())) } else { unexpected(byte, expected) } } fn send_trailers(&mut self, trailers: Trailers) { let sender = self .trailer_sender .take() .expect("invalid chunked state, tried sending multiple trailers"); let fut = Box::pin(sender.send(trailers)); self.state = State::TrailerSending(fut); } } fn eof() -> Poll> { Poll::Ready(Err(io::Error::new( io::ErrorKind::UnexpectedEof, "Unexpected EOF when decoding chunked data", ))) } fn unexpected(byte: u8, expected: &'static str) -> Poll> { Poll::Ready(Err(io::Error::new( io::ErrorKind::InvalidData, format!("Unexpected byte {}; expected {}", byte, expected), ))) } fn overflow() -> io::Error { io::Error::new(io::ErrorKind::InvalidData, "Chunk size overflowed 64 bits") } impl Read for ChunkedDecoder { #[allow(missing_doc_code_examples)] fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { let this = &mut *self; loop { match this.state { State::ChunkSize => { let byte = ready!(this.poll_read_byte(cx))?; let digit = match byte { b'0'..=b'9' => byte - b'0', b'a'..=b'f' => 10 + byte - b'a', b'A'..=b'F' => 10 + byte - b'A', b'\r' => { this.state = State::ChunkSizeExpectLf; continue; } _ => { return unexpected(byte, "hex digit or CR"); } }; this.chunk_size = this .chunk_size .checked_mul(16) .ok_or_else(overflow)? .checked_add(digit as u64) .ok_or_else(overflow)?; } State::ChunkSizeExpectLf => { ready!(this.expect_byte(cx, b'\n', "LF"))?; if this.chunk_size == 0 { this.state = State::Trailers(0, Box::new([0u8; 8192])); } else { this.state = State::ChunkBody; } } State::ChunkBody => { let max_bytes = std::cmp::min( buf.len(), std::cmp::min(this.chunk_size, usize::MAX as u64) as usize, ); let bytes_read = ready!(Pin::new(&mut this.inner).poll_read(cx, &mut buf[..max_bytes]))?; this.chunk_size -= bytes_read as u64; if bytes_read == 0 { return eof(); } else if this.chunk_size == 0 { this.state = State::ChunkBodyExpectCr; } return Poll::Ready(Ok(bytes_read)); } State::ChunkBodyExpectCr => { ready!(this.expect_byte(cx, b'\r', "CR"))?; this.state = State::ChunkBodyExpectLf; } State::ChunkBodyExpectLf => { ready!(this.expect_byte(cx, b'\n', "LF"))?; this.state = State::ChunkSize; } State::Trailers(ref mut len, ref mut buf) => { let bytes_read = ready!(Pin::new(&mut this.inner).poll_read(cx, &mut buf[*len..]))?; *len += bytes_read; let len = *len; if len == 0 { this.send_trailers(Trailers::new()); continue; } if bytes_read == 0 { return eof(); } let mut headers = [httparse::EMPTY_HEADER; 16]; let parse_result = httparse::parse_headers(&buf[..len], &mut headers) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; use httparse::Status; match parse_result { Status::Partial => { if len == buf.len() { return eof(); } else { return Poll::Pending; } } Status::Complete((offset, headers)) => { if offset != len { return unexpected(buf[offset], "end of trailers"); } let mut trailers = Trailers::new(); for header in headers { trailers.insert( header.name, String::from_utf8_lossy(header.value).as_ref(), ); } this.send_trailers(trailers); } } } State::TrailerSending(ref mut fut) => { ready!(Pin::new(fut).poll(cx)); this.state = State::Done; } State::Done => return Poll::Ready(Ok(0)), } } } } #[cfg(test)] mod tests { use super::*; use async_std::prelude::*; #[test] fn test_chunked_wiki() { async_std::task::block_on(async move { let input = async_std::io::Cursor::new( "4\r\n\ Wiki\r\n\ 5\r\n\ pedia\r\n\ E\r\n in\r\n\ \r\n\ chunks.\r\n\ 0\r\n\ \r\n" .as_bytes(), ); let (s, _r) = async_channel::bounded(1); let sender = Sender::new(s); let mut decoder = ChunkedDecoder::new(input, sender); let mut output = String::new(); decoder.read_to_string(&mut output).await.unwrap(); assert_eq!( output, "Wikipedia in\r\n\ \r\n\ chunks." ); }); } #[test] fn test_chunked_big() { async_std::task::block_on(async move { let mut input: Vec = b"800\r\n".to_vec(); input.extend(vec![b'X'; 2048]); input.extend(b"\r\n1800\r\n"); input.extend(vec![b'Y'; 6144]); input.extend(b"\r\n800\r\n"); input.extend(vec![b'Z'; 2048]); input.extend(b"\r\n0\r\n\r\n"); let (s, _r) = async_channel::bounded(1); let sender = Sender::new(s); let mut decoder = ChunkedDecoder::new(async_std::io::Cursor::new(input), sender); let mut output = String::new(); decoder.read_to_string(&mut output).await.unwrap(); let mut expected = vec![b'X'; 2048]; expected.extend(vec![b'Y'; 6144]); expected.extend(vec![b'Z'; 2048]); assert_eq!(output.len(), 10240); assert_eq!(output.as_bytes(), expected.as_slice()); }); } #[test] fn test_chunked_mdn() { async_std::task::block_on(async move { let input = async_std::io::Cursor::new( "7\r\n\ Mozilla\r\n\ 9\r\n\ Developer\r\n\ 7\r\n\ Network\r\n\ 0\r\n\ Expires: Wed, 21 Oct 2015 07:28:00 GMT\r\n\ \r\n" .as_bytes(), ); let (s, r) = async_channel::bounded(1); let sender = Sender::new(s); let mut decoder = ChunkedDecoder::new(input, sender); let mut output = String::new(); decoder.read_to_string(&mut output).await.unwrap(); assert_eq!(output, "MozillaDeveloperNetwork"); let trailers = r.recv().await.unwrap(); assert_eq!(trailers.iter().count(), 1); assert_eq!(trailers["Expires"], "Wed, 21 Oct 2015 07:28:00 GMT"); }); } #[test] fn test_ff7() { async_std::task::block_on(async move { let mut input: Vec = b"FF7\r\n".to_vec(); input.extend(vec![b'X'; 0xFF7]); input.extend(b"\r\n4\r\n"); input.extend(vec![b'Y'; 4]); input.extend(b"\r\n0\r\n\r\n"); let (s, _r) = async_channel::bounded(1); let sender = Sender::new(s); let mut decoder = ChunkedDecoder::new(async_std::io::Cursor::new(input), sender); let mut output = String::new(); decoder.read_to_string(&mut output).await.unwrap(); assert_eq!( output, "X".to_string().repeat(0xFF7) + &"Y".to_string().repeat(4) ); }); } } async-h1-2.3.4/src/chunked/encoder.rs000064400000000000000000000075001046102023000154540ustar 00000000000000use std::pin::Pin; use std::task::{Context, Poll}; use futures_lite::io::AsyncRead as Read; use futures_lite::{io, ready}; /// An encoder for chunked encoding. #[derive(Debug)] pub(crate) struct ChunkedEncoder { reader: R, done: bool, } impl ChunkedEncoder { /// Create a new instance. pub(crate) fn new(reader: R) -> Self { Self { reader, done: false, } } } impl Read for ChunkedEncoder { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { if self.done { return Poll::Ready(Ok(0)); } let reader = &mut self.reader; let max_bytes_to_read = max_bytes_to_read(buf.len()); let bytes = ready!(Pin::new(reader).poll_read(cx, &mut buf[..max_bytes_to_read]))?; if bytes == 0 { self.done = true; } let start = format!("{:X}\r\n", bytes); let start_length = start.as_bytes().len(); let total = bytes + start_length + 2; buf.copy_within(..bytes, start_length); buf[..start_length].copy_from_slice(start.as_bytes()); buf[total - 2..total].copy_from_slice(b"\r\n"); Poll::Ready(Ok(total)) } } fn max_bytes_to_read(buf_len: usize) -> usize { if buf_len < 6 { // the minimum read size is of 6 represents one byte of // content from the body. the other five bytes are 1\r\n_\r\n // where _ is the actual content in question panic!("buffers of length {} are too small for this implementation. if this is a problem for you, please open an issue", buf_len); } let bytes_remaining_after_two_cr_lns = (buf_len - 4) as f64; // the maximum number of bytes that the hex representation of remaining bytes might take let max_bytes_of_hex_framing = bytes_remaining_after_two_cr_lns.log2() / 4f64; (bytes_remaining_after_two_cr_lns - max_bytes_of_hex_framing.ceil()) as usize } #[cfg(test)] mod test_bytes_to_read { #[test] fn simple_check_of_known_values() { // the marked rows are the most important part of this test, // and a nonobvious but intentional consequence of the // implementation. in order to avoid overflowing, we must use // one fewer than the available buffer bytes because // increasing the read size increase the number of framed // bytes by two. This occurs when the hex representation of // the content bytes is near an increase in order of magnitude // (F->10, FF->100, FFF-> 1000, etc) let values = vec![ (6, 1), // 1 (7, 2), // 2 (20, 15), // F (21, 15), // F <- (22, 16), // 10 (23, 17), // 11 (260, 254), // FE (261, 254), // FE <- (262, 255), // FF <- (263, 256), // 100 (4100, 4093), // FFD (4101, 4093), // FFD <- (4102, 4094), // FFE <- (4103, 4095), // FFF <- (4104, 4096), // 1000 ]; for (input, expected) in values { let actual = super::max_bytes_to_read(input); assert_eq!( actual, expected, "\n\nexpected max_bytes_to_read({}) to be {}, but it was {}", input, expected, actual ); // testing the test: let used_bytes = expected + 4 + format!("{:X}", expected).len(); assert!( used_bytes == input || used_bytes == input - 1, "\n\nfor an input of {}, expected used bytes to be {} or {}, but was {}", input, input, input - 1, used_bytes ); } } } async-h1-2.3.4/src/chunked/mod.rs000064400000000000000000000001531046102023000146110ustar 00000000000000mod decoder; mod encoder; pub(crate) use decoder::ChunkedDecoder; pub(crate) use encoder::ChunkedEncoder; async-h1-2.3.4/src/client/decode.rs000064400000000000000000000065561046102023000151270ustar 00000000000000use futures_lite::io::{AsyncRead as Read, BufReader}; use futures_lite::prelude::*; use http_types::{ensure, ensure_eq, format_err}; use http_types::{ headers::{CONTENT_LENGTH, DATE, TRANSFER_ENCODING}, Body, Response, StatusCode, }; use std::convert::TryFrom; use crate::chunked::ChunkedDecoder; use crate::date::fmt_http_date; use crate::{MAX_HEADERS, MAX_HEAD_LENGTH}; const CR: u8 = b'\r'; const LF: u8 = b'\n'; /// Decode an HTTP response on the client. pub async fn decode(reader: R) -> http_types::Result where R: Read + Unpin + Send + Sync + 'static, { let mut reader = BufReader::new(reader); let mut buf = Vec::new(); let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS]; let mut httparse_res = httparse::Response::new(&mut headers); // Keep reading bytes from the stream until we hit the end of the stream. loop { let bytes_read = reader.read_until(LF, &mut buf).await?; // No more bytes are yielded from the stream. match (bytes_read, buf.len()) { (0, 0) => return Err(format_err!("connection closed")), (0, _) => return Err(format_err!("empty response")), _ => {} } // Prevent CWE-400 DDOS with large HTTP Headers. ensure!( buf.len() < MAX_HEAD_LENGTH, "Head byte length should be less than 8kb" ); // We've hit the end delimiter of the stream. let idx = buf.len() - 1; if idx >= 3 && buf[idx - 3..=idx] == [CR, LF, CR, LF] { break; } if idx >= 1 && buf[idx - 1..=idx] == [LF, LF] { break; } } // Convert our header buf into an httparse instance, and validate. let status = httparse_res.parse(&buf)?; ensure!(!status.is_partial(), "Malformed HTTP head"); let code = httparse_res.code; let code = code.ok_or_else(|| format_err!("No status code found"))?; // Convert httparse headers + body into a `http_types::Response` type. let version = httparse_res.version; let version = version.ok_or_else(|| format_err!("No version found"))?; ensure_eq!(version, 1, "Unsupported HTTP version"); let mut res = Response::new(StatusCode::try_from(code)?); for header in httparse_res.headers.iter() { res.append_header(header.name, std::str::from_utf8(header.value)?); } if res.header(DATE).is_none() { let date = fmt_http_date(std::time::SystemTime::now()); res.insert_header(DATE, &format!("date: {}\r\n", date)[..]); } let content_length = res.header(CONTENT_LENGTH); let transfer_encoding = res.header(TRANSFER_ENCODING); ensure!( content_length.is_none() || transfer_encoding.is_none(), "Unexpected Content-Length header" ); if let Some(encoding) = transfer_encoding { if encoding.last().as_str() == "chunked" { let trailers_sender = res.send_trailers(); let reader = BufReader::new(ChunkedDecoder::new(reader, trailers_sender)); res.set_body(Body::from_reader(reader, None)); // Return the response. return Ok(res); } } // Check for Content-Length. if let Some(len) = content_length { let len = len.last().as_str().parse::()?; res.set_body(Body::from_reader(reader.take(len as u64), Some(len))); } // Return the response. Ok(res) } async-h1-2.3.4/src/client/encode.rs000064400000000000000000000101361046102023000151260ustar 00000000000000use std::io::Write; use std::pin::Pin; use std::task::{Context, Poll}; use futures_lite::io::{self, AsyncRead as Read, Cursor}; use http_types::headers::{CONTENT_LENGTH, HOST, TRANSFER_ENCODING}; use http_types::{Method, Request}; use crate::body_encoder::BodyEncoder; use crate::read_to_end; use crate::EncoderState; /// An HTTP encoder. #[doc(hidden)] #[derive(Debug)] pub struct Encoder { request: Request, state: EncoderState, } impl Encoder { /// build a new client encoder pub fn new(request: Request) -> Self { Self { request, state: EncoderState::Start, } } fn finalize_headers(&mut self) -> io::Result<()> { if self.request.header(HOST).is_none() { let url = self.request.url(); let host = url .host_str() .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Missing hostname"))? .to_owned(); if let Some(port) = url.port() { self.request .insert_header(HOST, format!("{}:{}", host, port)); } else { self.request.insert_header(HOST, host); }; } // Insert Proxy-Connection header when method is CONNECT if self.request.method() == Method::Connect { self.request.insert_header("proxy-connection", "keep-alive"); } // If the body isn't streaming, we can set the content-length ahead of time. Else we need to // send all items in chunks. if let Some(len) = self.request.len() { self.request.insert_header(CONTENT_LENGTH, len.to_string()); } else { self.request.insert_header(TRANSFER_ENCODING, "chunked"); } Ok(()) } fn compute_head(&mut self) -> io::Result>> { let mut buf = Vec::with_capacity(128); let url = self.request.url(); let method = self.request.method(); write!(buf, "{} ", method)?; // A client sending a CONNECT request MUST consists of only the host // name and port number of the tunnel destination, separated by a colon. // See: https://tools.ietf.org/html/rfc7231#section-4.3.6 if method == Method::Connect { let host = url .host_str() .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Missing hostname"))?; let port = url.port_or_known_default().ok_or_else(|| { io::Error::new( io::ErrorKind::InvalidData, "Unexpected scheme with no default port", ) })?; write!(buf, "{}:{}", host, port)?; } else { write!(buf, "{}", url.path())?; if let Some(query) = url.query() { write!(buf, "?{}", query)?; } } write!(buf, " HTTP/1.1\r\n")?; self.finalize_headers()?; let mut headers = self.request.iter().collect::>(); headers.sort_unstable_by_key(|(h, _)| if **h == HOST { "0" } else { h.as_str() }); for (header, values) in headers { for value in values.iter() { write!(buf, "{}: {}\r\n", header, value)?; } } write!(buf, "\r\n")?; Ok(Cursor::new(buf)) } } impl Read for Encoder { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { loop { self.state = match self.state { EncoderState::Start => EncoderState::Head(self.compute_head()?), EncoderState::Head(ref mut cursor) => { read_to_end!(Pin::new(cursor).poll_read(cx, buf)); EncoderState::Body(BodyEncoder::new(self.request.take_body())) } EncoderState::Body(ref mut encoder) => { read_to_end!(Pin::new(encoder).poll_read(cx, buf)); EncoderState::End } EncoderState::End => return Poll::Ready(Ok(0)), } } } } async-h1-2.3.4/src/client/mod.rs000064400000000000000000000011651046102023000144520ustar 00000000000000//! Process HTTP connections on the client. use futures_lite::io::{self, AsyncRead as Read, AsyncWrite as Write}; use http_types::{Request, Response}; mod decode; mod encode; pub use decode::decode; pub use encode::Encoder; /// Opens an HTTP/1.1 connection to a remote host. pub async fn connect(mut stream: RW, req: Request) -> http_types::Result where RW: Read + Write + Send + Sync + Unpin + 'static, { let mut req = Encoder::new(req); log::trace!("> {:?}", &req); io::copy(&mut req, &mut stream).await?; let res = decode(stream).await?; log::trace!("< {:?}", &res); Ok(res) } async-h1-2.3.4/src/date.rs000064400000000000000000000350451046102023000133360ustar 00000000000000use std::fmt::{self, Display, Formatter}; use std::str::{from_utf8, FromStr}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; use http_types::{bail, ensure, format_err}; const IMF_FIXDATE_LENGTH: usize = 29; const RFC850_MAX_LENGTH: usize = 23; const ASCTIME_LENGTH: usize = 24; const YEAR_9999_SECONDS: u64 = 253402300800; const SECONDS_IN_DAY: u64 = 86400; const SECONDS_IN_HOUR: u64 = 3600; /// Format using the `Display` trait. /// Convert timestamp into/from `SytemTime` to use. /// Supports comparison and sorting. #[derive(Copy, Clone, Debug, Eq)] pub struct HttpDate { /// 0...59 second: u8, /// 0...59 minute: u8, /// 0...23 hour: u8, /// 1...31 day: u8, /// 1...12 month: u8, /// 1970...9999 year: u16, /// 1...7 week_day: u8, } /// Parse a date from an HTTP header field. /// /// Supports the preferred IMF-fixdate and the legacy RFC 805 and /// ascdate formats. Two digit years are mapped to dates between /// 1970 and 2069. #[allow(dead_code)] pub(crate) fn parse_http_date(s: &str) -> http_types::Result { s.parse::().map(|d| d.into()) } /// Format a date to be used in a HTTP header field. /// /// Dates are formatted as IMF-fixdate: `Fri, 15 May 2015 15:34:21 GMT`. pub(crate) fn fmt_http_date(d: SystemTime) -> String { format!("{}", HttpDate::from(d)) } impl HttpDate { fn is_valid(self) -> bool { self.second < 60 && self.minute < 60 && self.hour < 24 && self.day > 0 && self.day < 32 && self.month > 0 && self.month <= 12 && self.year >= 1970 && self.year <= 9999 && self.week_day >= 1 && self.week_day < 8 } } fn parse_imf_fixdate(s: &[u8]) -> http_types::Result { // Example: `Sun, 06 Nov 1994 08:49:37 GMT` if s.len() != IMF_FIXDATE_LENGTH || &s[25..] != b" GMT" || s[16] != b' ' || s[19] != b':' || s[22] != b':' { bail!("Date time not in imf fixdate format"); } Ok(HttpDate { second: from_utf8(&s[23..25])?.parse()?, minute: from_utf8(&s[20..22])?.parse()?, hour: from_utf8(&s[17..19])?.parse()?, day: from_utf8(&s[5..7])?.parse()?, month: match &s[7..12] { b" Jan " => 1, b" Feb " => 2, b" Mar " => 3, b" Apr " => 4, b" May " => 5, b" Jun " => 6, b" Jul " => 7, b" Aug " => 8, b" Sep " => 9, b" Oct " => 10, b" Nov " => 11, b" Dec " => 12, _ => bail!("Invalid Month"), }, year: from_utf8(&s[12..16])?.parse()?, week_day: match &s[..5] { b"Mon, " => 1, b"Tue, " => 2, b"Wed, " => 3, b"Thu, " => 4, b"Fri, " => 5, b"Sat, " => 6, b"Sun, " => 7, _ => bail!("Invalid Day"), }, }) } fn parse_rfc850_date(s: &[u8]) -> http_types::Result { // Example: `Sunday, 06-Nov-94 08:49:37 GMT` ensure!( s.len() >= RFC850_MAX_LENGTH, "Date time not in rfc850 format" ); fn week_day<'a>(s: &'a [u8], week_day: u8, name: &'static [u8]) -> Option<(u8, &'a [u8])> { if &s[0..name.len()] == name { return Some((week_day, &s[name.len()..])); } None } let (week_day, s) = week_day(s, 1, b"Monday, ") .or_else(|| week_day(s, 2, b"Tuesday, ")) .or_else(|| week_day(s, 3, b"Wednesday, ")) .or_else(|| week_day(s, 4, b"Thursday, ")) .or_else(|| week_day(s, 5, b"Friday, ")) .or_else(|| week_day(s, 6, b"Saturday, ")) .or_else(|| week_day(s, 7, b"Sunday, ")) .ok_or_else(|| format_err!("Invalid day"))?; if s.len() != 22 || s[12] != b':' || s[15] != b':' || &s[18..22] != b" GMT" { bail!("Date time not in rfc950 fmt"); } let mut year = from_utf8(&s[7..9])?.parse::()?; if year < 70 { year += 2000; } else { year += 1900; } Ok(HttpDate { second: from_utf8(&s[16..18])?.parse()?, minute: from_utf8(&s[13..15])?.parse()?, hour: from_utf8(&s[10..12])?.parse()?, day: from_utf8(&s[0..2])?.parse()?, month: match &s[2..7] { b"-Jan-" => 1, b"-Feb-" => 2, b"-Mar-" => 3, b"-Apr-" => 4, b"-May-" => 5, b"-Jun-" => 6, b"-Jul-" => 7, b"-Aug-" => 8, b"-Sep-" => 9, b"-Oct-" => 10, b"-Nov-" => 11, b"-Dec-" => 12, _ => bail!("Invalid month"), }, year, week_day, }) } fn parse_asctime(s: &[u8]) -> http_types::Result { // Example: `Sun Nov 6 08:49:37 1994` if s.len() != ASCTIME_LENGTH || s[10] != b' ' || s[13] != b':' || s[16] != b':' || s[19] != b' ' { bail!("Date time not in asctime format"); } Ok(HttpDate { second: from_utf8(&s[17..19])?.parse()?, minute: from_utf8(&s[14..16])?.parse()?, hour: from_utf8(&s[11..13])?.parse()?, day: { let x = &s[8..10]; from_utf8(if x[0] == b' ' { &x[1..2] } else { x })?.parse()? }, month: match &s[4..8] { b"Jan " => 1, b"Feb " => 2, b"Mar " => 3, b"Apr " => 4, b"May " => 5, b"Jun " => 6, b"Jul " => 7, b"Aug " => 8, b"Sep " => 9, b"Oct " => 10, b"Nov " => 11, b"Dec " => 12, _ => bail!("Invalid month"), }, year: from_utf8(&s[20..24])?.parse()?, week_day: match &s[0..4] { b"Mon " => 1, b"Tue " => 2, b"Wed " => 3, b"Thu " => 4, b"Fri " => 5, b"Sat " => 6, b"Sun " => 7, _ => bail!("Invalid day"), }, }) } impl From for HttpDate { fn from(system_time: SystemTime) -> Self { let dur = system_time .duration_since(UNIX_EPOCH) .expect("all times should be after the epoch"); let secs_since_epoch = dur.as_secs(); if secs_since_epoch >= YEAR_9999_SECONDS { // year 9999 panic!("date must be before year 9999"); } /* 2000-03-01 (mod 400 year, immediately after feb29 */ const LEAPOCH: i64 = 11017; const DAYS_PER_400Y: i64 = 365 * 400 + 97; const DAYS_PER_100Y: i64 = 365 * 100 + 24; const DAYS_PER_4Y: i64 = 365 * 4 + 1; let days = (secs_since_epoch / SECONDS_IN_DAY) as i64 - LEAPOCH; let secs_of_day = secs_since_epoch % SECONDS_IN_DAY; let mut qc_cycles = days / DAYS_PER_400Y; let mut remdays = days % DAYS_PER_400Y; if remdays < 0 { remdays += DAYS_PER_400Y; qc_cycles -= 1; } let mut c_cycles = remdays / DAYS_PER_100Y; if c_cycles == 4 { c_cycles -= 1; } remdays -= c_cycles * DAYS_PER_100Y; let mut q_cycles = remdays / DAYS_PER_4Y; if q_cycles == 25 { q_cycles -= 1; } remdays -= q_cycles * DAYS_PER_4Y; let mut remyears = remdays / 365; if remyears == 4 { remyears -= 1; } remdays -= remyears * 365; let mut year = 2000 + remyears + 4 * q_cycles + 100 * c_cycles + 400 * qc_cycles; let months = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29]; let mut month = 0; for month_len in months.iter() { month += 1; if remdays < *month_len { break; } remdays -= *month_len; } let mday = remdays + 1; let month = if month + 2 > 12 { year += 1; month - 10 } else { month + 2 }; let mut week_day = (3 + days) % 7; if week_day <= 0 { week_day += 7 }; HttpDate { second: (secs_of_day % 60) as u8, minute: ((secs_of_day % SECONDS_IN_HOUR) / 60) as u8, hour: (secs_of_day / SECONDS_IN_HOUR) as u8, day: mday as u8, month: month as u8, year: year as u16, week_day: week_day as u8, } } } impl From for SystemTime { fn from(http_date: HttpDate) -> Self { let leap_years = ((http_date.year - 1) - 1968) / 4 - ((http_date.year - 1) - 1900) / 100 + ((http_date.year - 1) - 1600) / 400; let mut ydays = match http_date.month { 1 => 0, 2 => 31, 3 => 59, 4 => 90, 5 => 120, 6 => 151, 7 => 181, 8 => 212, 9 => 243, 10 => 273, 11 => 304, 12 => 334, _ => unreachable!(), } + http_date.day as u64 - 1; if is_leap_year(http_date.year) && http_date.month > 2 { ydays += 1; } let days = (http_date.year as u64 - 1970) * 365 + leap_years as u64 + ydays; UNIX_EPOCH + Duration::from_secs( http_date.second as u64 + http_date.minute as u64 * 60 + http_date.hour as u64 * SECONDS_IN_HOUR + days * SECONDS_IN_DAY, ) } } impl FromStr for HttpDate { type Err = http_types::Error; fn from_str(s: &str) -> Result { ensure!(s.is_ascii(), "String slice is not valid ASCII"); let x = s.trim().as_bytes(); let date = parse_imf_fixdate(x) .or_else(|_| parse_rfc850_date(x)) .or_else(|_| parse_asctime(x))?; ensure!(date.is_valid(), "Invalid date time"); Ok(date) } } impl Display for HttpDate { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { let week_day = match self.week_day { 1 => b"Mon", 2 => b"Tue", 3 => b"Wed", 4 => b"Thu", 5 => b"Fri", 6 => b"Sat", 7 => b"Sun", _ => unreachable!(), }; let month = match self.month { 1 => b"Jan", 2 => b"Feb", 3 => b"Mar", 4 => b"Apr", 5 => b"May", 6 => b"Jun", 7 => b"Jul", 8 => b"Aug", 9 => b"Sep", 10 => b"Oct", 11 => b"Nov", 12 => b"Dec", _ => unreachable!(), }; let mut buf: [u8; 29] = [ // Too long to write as: b"Thu, 01 Jan 1970 00:00:00 GMT" b' ', b' ', b' ', b',', b' ', b'0', b'0', b' ', b' ', b' ', b' ', b' ', b'0', b'0', b'0', b'0', b' ', b'0', b'0', b':', b'0', b'0', b':', b'0', b'0', b' ', b'G', b'M', b'T', ]; buf[0] = week_day[0]; buf[1] = week_day[1]; buf[2] = week_day[2]; buf[5] = b'0' + (self.day / 10); buf[6] = b'0' + (self.day % 10); buf[8] = month[0]; buf[9] = month[1]; buf[10] = month[2]; buf[12] = b'0' + (self.year / 1000) as u8; buf[13] = b'0' + (self.year / 100 % 10) as u8; buf[14] = b'0' + (self.year / 10 % 10) as u8; buf[15] = b'0' + (self.year % 10) as u8; buf[17] = b'0' + (self.hour / 10); buf[18] = b'0' + (self.hour % 10); buf[20] = b'0' + (self.minute / 10); buf[21] = b'0' + (self.minute % 10); buf[23] = b'0' + (self.second / 10); buf[24] = b'0' + (self.second % 10); f.write_str(from_utf8(&buf[..]).unwrap()) } } impl PartialEq for HttpDate { fn eq(&self, other: &HttpDate) -> bool { SystemTime::from(*self) == SystemTime::from(*other) } } impl PartialOrd for HttpDate { fn partial_cmp(&self, other: &HttpDate) -> Option { SystemTime::from(*self).partial_cmp(&SystemTime::from(*other)) } } fn is_leap_year(year: u16) -> bool { year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) } #[cfg(test)] mod tests { use std::time::{Duration, UNIX_EPOCH}; use super::{fmt_http_date, parse_http_date, HttpDate, SECONDS_IN_DAY, SECONDS_IN_HOUR}; #[test] fn test_rfc_example() { let d = UNIX_EPOCH + Duration::from_secs(784111777); assert_eq!( d, parse_http_date("Sun, 06 Nov 1994 08:49:37 GMT").expect("#1") ); assert_eq!( d, parse_http_date("Sunday, 06-Nov-94 08:49:37 GMT").expect("#2") ); assert_eq!(d, parse_http_date("Sun Nov 6 08:49:37 1994").expect("#3")); } #[test] fn test2() { let d = UNIX_EPOCH + Duration::from_secs(1475419451); assert_eq!( d, parse_http_date("Sun, 02 Oct 2016 14:44:11 GMT").expect("#1") ); assert!(parse_http_date("Sun Nov 10 08:00:00 1000").is_err()); assert!(parse_http_date("Sun Nov 10 08*00:00 2000").is_err()); assert!(parse_http_date("Sunday, 06-Nov-94 08+49:37 GMT").is_err()); } #[test] fn test3() { let mut d = UNIX_EPOCH; assert_eq!(d, parse_http_date("Thu, 01 Jan 1970 00:00:00 GMT").unwrap()); d += Duration::from_secs(SECONDS_IN_HOUR); assert_eq!(d, parse_http_date("Thu, 01 Jan 1970 01:00:00 GMT").unwrap()); d += Duration::from_secs(SECONDS_IN_DAY); assert_eq!(d, parse_http_date("Fri, 02 Jan 1970 01:00:00 GMT").unwrap()); d += Duration::from_secs(2592000); assert_eq!(d, parse_http_date("Sun, 01 Feb 1970 01:00:00 GMT").unwrap()); d += Duration::from_secs(2592000); assert_eq!(d, parse_http_date("Tue, 03 Mar 1970 01:00:00 GMT").unwrap()); d += Duration::from_secs(31536005); assert_eq!(d, parse_http_date("Wed, 03 Mar 1971 01:00:05 GMT").unwrap()); d += Duration::from_secs(15552000); assert_eq!(d, parse_http_date("Mon, 30 Aug 1971 01:00:05 GMT").unwrap()); d += Duration::from_secs(6048000); assert_eq!(d, parse_http_date("Mon, 08 Nov 1971 01:00:05 GMT").unwrap()); d += Duration::from_secs(864000000); assert_eq!(d, parse_http_date("Fri, 26 Mar 1999 01:00:05 GMT").unwrap()); } #[test] fn test_fmt() { let d = UNIX_EPOCH; assert_eq!(fmt_http_date(d), "Thu, 01 Jan 1970 00:00:00 GMT"); let d = UNIX_EPOCH + Duration::from_secs(1475419451); assert_eq!(fmt_http_date(d), "Sun, 02 Oct 2016 14:44:11 GMT"); } #[test] fn size_of() { assert_eq!(::std::mem::size_of::(), 8); } } async-h1-2.3.4/src/lib.rs000064400000000000000000000101101046102023000131510ustar 00000000000000//! Streaming async HTTP 1.1 parser. //! //! At its core HTTP is a stateful RPC protocol, where a client and server //! communicate with one another by encoding and decoding messages between them. //! //! - `client` encodes HTTP requests, and decodes HTTP responses. //! - `server` decodes HTTP requests, and encodes HTTP responses. //! //! A client always starts the HTTP connection. The lifetime of an HTTP //! connection looks like this: //! //! ```txt //! 1. encode 2. decode //! \ / //! -> request -> //! client server //! <- response <- //! / \ //! 4. decode 3. encode //! ``` //! //! See also [`async-tls`](https://docs.rs/async-tls), //! [`async-std`](https://docs.rs/async-std). //! //! # Example //! //! __HTTP client__ //! //! ```no_run //! use async_std::net::TcpStream; //! use http_types::{Method, Request, Url}; //! //! #[async_std::main] //! async fn main() -> http_types::Result<()> { //! let stream = TcpStream::connect("127.0.0.1:8080").await?; //! //! let peer_addr = stream.peer_addr()?; //! println!("connecting to {}", peer_addr); //! let url = Url::parse(&format!("http://{}/foo", peer_addr))?; //! //! let req = Request::new(Method::Get, url); //! let res = async_h1::connect(stream.clone(), req).await?; //! println!("{:?}", res); //! //! Ok(()) //! } //! ``` //! //! __HTTP Server__ //! //! ```no_run //! use async_std::net::{TcpStream, TcpListener}; //! use async_std::prelude::*; //! use async_std::task; //! use http_types::{Response, StatusCode}; //! //! #[async_std::main] //! async fn main() -> http_types::Result<()> { //! // Open up a TCP connection and create a URL. //! let listener = TcpListener::bind(("127.0.0.1", 8080)).await?; //! let addr = format!("http://{}", listener.local_addr()?); //! println!("listening on {}", addr); //! //! // For each incoming TCP connection, spawn a task and call `accept`. //! let mut incoming = listener.incoming(); //! while let Some(stream) = incoming.next().await { //! let stream = stream?; //! task::spawn(async { //! if let Err(err) = accept(stream).await { //! eprintln!("{}", err); //! } //! }); //! } //! Ok(()) //! } //! //! // Take a TCP stream, and convert it into sequential HTTP request / response pairs. //! async fn accept(stream: TcpStream) -> http_types::Result<()> { //! println!("starting new connection from {}", stream.peer_addr()?); //! async_h1::accept(stream.clone(), |_req| async move { //! let mut res = Response::new(StatusCode::Ok); //! res.insert_header("Content-Type", "text/plain"); //! res.set_body("Hello"); //! Ok(res) //! }) //! .await?; //! Ok(()) //! } //! ``` #![forbid(unsafe_code)] #![deny(missing_debug_implementations, nonstandard_style, rust_2018_idioms)] #![warn(missing_docs, missing_doc_code_examples, unreachable_pub)] #![cfg_attr(test, deny(warnings))] #![allow(clippy::if_same_then_else)] #![allow(clippy::len_zero)] #![allow(clippy::match_bool)] #![allow(clippy::unreadable_literal)] /// The maximum amount of headers parsed on the server. const MAX_HEADERS: usize = 128; /// The maximum length of the head section we'll try to parse. /// See: https://nodejs.org/en/blog/vulnerability/november-2018-security-releases/#denial-of-service-with-large-http-headers-cve-2018-12121 const MAX_HEAD_LENGTH: usize = 8 * 1024; mod body_encoder; mod chunked; mod date; mod read_notifier; pub mod client; pub mod server; use body_encoder::BodyEncoder; pub use client::connect; use futures_lite::io::Cursor; pub use server::{accept, accept_with_opts, ServerOptions}; #[derive(Debug)] pub(crate) enum EncoderState { Start, Head(Cursor>), Body(BodyEncoder), End, } /// like ready! but early-returns the Poll> early in all situations other than Ready(Ok(0)) #[macro_export] macro_rules! read_to_end { ($expr:expr) => { match $expr { Poll::Ready(Ok(0)) => (), other => return other, } }; } async-h1-2.3.4/src/read_notifier.rs000064400000000000000000000032611046102023000152260ustar 00000000000000use std::fmt; use std::pin::Pin; use std::task::{Context, Poll}; use async_channel::Sender; use futures_lite::io::{self, AsyncBufRead as BufRead, AsyncRead as Read}; /// ReadNotifier forwards [`async_std::io::Read`] and /// [`async_std::io::BufRead`] to an inner reader. When the /// ReadNotifier is read from (using `Read`, `ReadExt`, or `BufRead` /// methods), it sends a single message containing `()` on the /// channel. #[pin_project::pin_project] pub(crate) struct ReadNotifier { #[pin] reader: B, sender: Sender<()>, has_been_read: bool, } impl fmt::Debug for ReadNotifier { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("ReadNotifier") .field("read", &self.has_been_read) .finish() } } impl ReadNotifier { pub(crate) fn new(reader: B, sender: Sender<()>) -> Self { Self { reader, sender, has_been_read: false, } } } impl BufRead for ReadNotifier { fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().reader.poll_fill_buf(cx) } fn consume(self: Pin<&mut Self>, amt: usize) { self.project().reader.consume(amt) } } impl Read for ReadNotifier { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { let this = self.project(); if !*this.has_been_read { if let Ok(()) = this.sender.try_send(()) { *this.has_been_read = true; }; } this.reader.poll_read(cx, buf) } } async-h1-2.3.4/src/server/body_reader.rs000064400000000000000000000022251046102023000162000ustar 00000000000000use crate::chunked::ChunkedDecoder; use async_dup::{Arc, Mutex}; use futures_lite::io::{AsyncRead as Read, BufReader, Take}; use std::{ fmt::Debug, io, pin::Pin, task::{Context, Poll}, }; pub enum BodyReader { Chunked(Arc>>>), Fixed(Arc>>>), None, } impl Debug for BodyReader { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { BodyReader::Chunked(_) => f.write_str("BodyReader::Chunked"), BodyReader::Fixed(_) => f.write_str("BodyReader::Fixed"), BodyReader::None => f.write_str("BodyReader::None"), } } } impl Read for BodyReader { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { match &*self { BodyReader::Chunked(r) => Pin::new(&mut *r.lock()).poll_read(cx, buf), BodyReader::Fixed(r) => Pin::new(&mut *r.lock()).poll_read(cx, buf), BodyReader::None => Poll::Ready(Ok(0)), } } } async-h1-2.3.4/src/server/decode.rs000064400000000000000000000225131046102023000151460ustar 00000000000000//! Process HTTP connections on the server. use std::str::FromStr; use async_dup::{Arc, Mutex}; use futures_lite::io::{AsyncRead as Read, AsyncWrite as Write, BufReader}; use futures_lite::prelude::*; use http_types::content::ContentLength; use http_types::headers::{EXPECT, TRANSFER_ENCODING}; use http_types::{ensure, ensure_eq, format_err}; use http_types::{Body, Method, Request, Url}; use super::body_reader::BodyReader; use crate::chunked::ChunkedDecoder; use crate::read_notifier::ReadNotifier; use crate::{MAX_HEADERS, MAX_HEAD_LENGTH}; const LF: u8 = b'\n'; /// The number returned from httparse when the request is HTTP 1.1 const HTTP_1_1_VERSION: u8 = 1; const CONTINUE_HEADER_VALUE: &str = "100-continue"; const CONTINUE_RESPONSE: &[u8] = b"HTTP/1.1 100 Continue\r\n\r\n"; /// Decode an HTTP request on the server. pub async fn decode(mut io: IO) -> http_types::Result)>> where IO: Read + Write + Clone + Send + Sync + Unpin + 'static, { let mut reader = BufReader::new(io.clone()); let mut buf = Vec::new(); let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS]; let mut httparse_req = httparse::Request::new(&mut headers); // Keep reading bytes from the stream until we hit the end of the stream. loop { let bytes_read = reader.read_until(LF, &mut buf).await?; // No more bytes are yielded from the stream. if bytes_read == 0 { return Ok(None); } // Prevent CWE-400 DDOS with large HTTP Headers. ensure!( buf.len() < MAX_HEAD_LENGTH, "Head byte length should be less than 8kb" ); // We've hit the end delimiter of the stream. let idx = buf.len() - 1; if idx >= 3 && &buf[idx - 3..=idx] == b"\r\n\r\n" { break; } } // Convert our header buf into an httparse instance, and validate. let status = httparse_req.parse(&buf)?; ensure!(!status.is_partial(), "Malformed HTTP head"); // Convert httparse headers + body into a `http_types::Request` type. let method = httparse_req.method; let method = method.ok_or_else(|| format_err!("No method found"))?; let version = httparse_req.version; let version = version.ok_or_else(|| format_err!("No version found"))?; ensure_eq!( version, HTTP_1_1_VERSION, "Unsupported HTTP version 1.{}", version ); let url = url_from_httparse_req(&httparse_req)?; let mut req = Request::new(Method::from_str(method)?, url); req.set_version(Some(http_types::Version::Http1_1)); for header in httparse_req.headers.iter() { req.append_header(header.name, std::str::from_utf8(header.value)?); } let content_length = ContentLength::from_headers(&req)?; let transfer_encoding = req.header(TRANSFER_ENCODING); // Return a 400 status if both Content-Length and Transfer-Encoding headers // are set to prevent request smuggling attacks. // // https://tools.ietf.org/html/rfc7230#section-3.3.3 http_types::ensure_status!( content_length.is_none() || transfer_encoding.is_none(), 400, "Unexpected Content-Length header" ); // Establish a channel to wait for the body to be read. This // allows us to avoid sending 100-continue in situations that // respond without reading the body, saving clients from uploading // their body. let (body_read_sender, body_read_receiver) = async_channel::bounded(1); if Some(CONTINUE_HEADER_VALUE) == req.header(EXPECT).map(|h| h.as_str()) { async_global_executor::spawn(async move { // If the client expects a 100-continue header, spawn a // task to wait for the first read attempt on the body. if let Ok(()) = body_read_receiver.recv().await { io.write_all(CONTINUE_RESPONSE).await.ok(); }; // Since the sender is moved into the Body, this task will // finish when the client disconnects, whether or not // 100-continue was sent. }) .detach(); } // Check for Transfer-Encoding if transfer_encoding .map(|te| te.as_str().eq_ignore_ascii_case("chunked")) .unwrap_or(false) { let trailer_sender = req.send_trailers(); let reader = ChunkedDecoder::new(reader, trailer_sender); let reader = Arc::new(Mutex::new(reader)); let reader_clone = reader.clone(); let reader = ReadNotifier::new(reader, body_read_sender); let reader = BufReader::new(reader); req.set_body(Body::from_reader(reader, None)); Ok(Some((req, BodyReader::Chunked(reader_clone)))) } else if let Some(len) = content_length { let len = len.len(); let reader = Arc::new(Mutex::new(reader.take(len))); req.set_body(Body::from_reader( BufReader::new(ReadNotifier::new(reader.clone(), body_read_sender)), Some(len as usize), )); Ok(Some((req, BodyReader::Fixed(reader)))) } else { Ok(Some((req, BodyReader::None))) } } fn url_from_httparse_req(req: &httparse::Request<'_, '_>) -> http_types::Result { let path = req.path.ok_or_else(|| format_err!("No uri found"))?; let host = req .headers .iter() .find(|x| x.name.eq_ignore_ascii_case("host")) .ok_or_else(|| format_err!("Mandatory Host header missing"))? .value; let host = std::str::from_utf8(host)?; if path.starts_with("http://") || path.starts_with("https://") { Ok(Url::parse(path)?) } else if path.starts_with('/') { Ok(Url::parse(&format!("http://{}{}", host, path))?) } else if req.method.unwrap().eq_ignore_ascii_case("connect") { Ok(Url::parse(&format!("http://{}/", path))?) } else { Err(format_err!("unexpected uri format")) } } #[cfg(test)] mod tests { use super::*; fn httparse_req(buf: &str, f: impl Fn(httparse::Request<'_, '_>)) { let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS]; let mut res = httparse::Request::new(&mut headers[..]); res.parse(buf.as_bytes()).unwrap(); f(res) } #[test] fn url_for_connect() { httparse_req( "CONNECT server.example.com:443 HTTP/1.1\r\nHost: server.example.com:443\r\n", |req| { let url = url_from_httparse_req(&req).unwrap(); assert_eq!(url.as_str(), "http://server.example.com:443/"); }, ); } #[test] fn url_for_host_plus_path() { httparse_req( "GET /some/resource HTTP/1.1\r\nHost: server.example.com:443\r\n", |req| { let url = url_from_httparse_req(&req).unwrap(); assert_eq!(url.as_str(), "http://server.example.com:443/some/resource"); }, ) } #[test] fn url_for_host_plus_absolute_url() { httparse_req( "GET http://domain.com/some/resource HTTP/1.1\r\nHost: server.example.com\r\n", |req| { let url = url_from_httparse_req(&req).unwrap(); assert_eq!(url.as_str(), "http://domain.com/some/resource"); // host header MUST be ignored according to spec }, ) } #[test] fn url_for_conflicting_connect() { httparse_req( "CONNECT server.example.com:443 HTTP/1.1\r\nHost: conflicting.host\r\n", |req| { let url = url_from_httparse_req(&req).unwrap(); assert_eq!(url.as_str(), "http://server.example.com:443/"); }, ) } #[test] fn url_for_malformed_resource_path() { httparse_req( "GET not-a-url HTTP/1.1\r\nHost: server.example.com\r\n", |req| { assert!(url_from_httparse_req(&req).is_err()); }, ) } #[test] fn url_for_double_slash_path() { httparse_req( "GET //double/slashes HTTP/1.1\r\nHost: server.example.com:443\r\n", |req| { let url = url_from_httparse_req(&req).unwrap(); assert_eq!( url.as_str(), "http://server.example.com:443//double/slashes" ); }, ) } #[test] fn url_for_triple_slash_path() { httparse_req( "GET ///triple/slashes HTTP/1.1\r\nHost: server.example.com:443\r\n", |req| { let url = url_from_httparse_req(&req).unwrap(); assert_eq!( url.as_str(), "http://server.example.com:443///triple/slashes" ); }, ) } #[test] fn url_for_query() { httparse_req( "GET /foo?bar=1 HTTP/1.1\r\nHost: server.example.com:443\r\n", |req| { let url = url_from_httparse_req(&req).unwrap(); assert_eq!(url.as_str(), "http://server.example.com:443/foo?bar=1"); }, ) } #[test] fn url_for_anchor() { httparse_req( "GET /foo?bar=1#anchor HTTP/1.1\r\nHost: server.example.com:443\r\n", |req| { let url = url_from_httparse_req(&req).unwrap(); assert_eq!( url.as_str(), "http://server.example.com:443/foo?bar=1#anchor" ); }, ) } } async-h1-2.3.4/src/server/encode.rs000064400000000000000000000060151046102023000151570ustar 00000000000000//! Process HTTP connections on the server. use std::io::Write; use std::pin::Pin; use std::task::{Context, Poll}; use std::time::SystemTime; use futures_lite::io::{self, AsyncRead as Read, Cursor}; use http_types::headers::{CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use http_types::{Method, Response}; use crate::body_encoder::BodyEncoder; use crate::date::fmt_http_date; use crate::read_to_end; use crate::EncoderState; /// A streaming HTTP encoder. #[derive(Debug)] pub struct Encoder { response: Response, state: EncoderState, method: Method, } impl Read for Encoder { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { loop { self.state = match self.state { EncoderState::Start => EncoderState::Head(self.compute_head()?), EncoderState::Head(ref mut cursor) => { read_to_end!(Pin::new(cursor).poll_read(cx, buf)); if self.method == Method::Head { EncoderState::End } else { EncoderState::Body(BodyEncoder::new(self.response.take_body())) } } EncoderState::Body(ref mut encoder) => { read_to_end!(Pin::new(encoder).poll_read(cx, buf)); EncoderState::End } EncoderState::End => return Poll::Ready(Ok(0)), } } } } impl Encoder { /// Create a new instance of Encoder. pub fn new(response: Response, method: Method) -> Self { Self { method, response, state: EncoderState::Start, } } fn finalize_headers(&mut self) { // If the body isn't streaming, we can set the content-length ahead of time. Else we need to // send all items in chunks. if let Some(len) = self.response.len() { self.response.insert_header(CONTENT_LENGTH, len.to_string()); } else { self.response.insert_header(TRANSFER_ENCODING, "chunked"); } if self.response.header(DATE).is_none() { let date = fmt_http_date(SystemTime::now()); self.response.insert_header(DATE, date); } } /// Encode the headers to a buffer, the first time we poll. fn compute_head(&mut self) -> io::Result>> { let mut head = Vec::with_capacity(128); let reason = self.response.status().canonical_reason(); let status = self.response.status(); write!(head, "HTTP/1.1 {} {}\r\n", status, reason)?; self.finalize_headers(); let mut headers = self.response.iter().collect::>(); headers.sort_unstable_by_key(|(h, _)| h.as_str()); for (header, values) in headers { for value in values.iter() { write!(head, "{}: {}\r\n", header, value)?; } } write!(head, "\r\n")?; Ok(Cursor::new(head)) } } async-h1-2.3.4/src/server/mod.rs000064400000000000000000000130111046102023000144730ustar 00000000000000//! Process HTTP connections on the server. use async_io::Timer; use futures_lite::io::{self, AsyncRead as Read, AsyncWrite as Write}; use futures_lite::prelude::*; use http_types::headers::{CONNECTION, UPGRADE}; use http_types::upgrade::Connection; use http_types::{Request, Response, StatusCode}; use std::{future::Future, marker::PhantomData, time::Duration}; mod body_reader; mod decode; mod encode; pub use decode::decode; pub use encode::Encoder; /// Configure the server. #[derive(Debug, Clone)] pub struct ServerOptions { /// Timeout to handle headers. Defaults to 60s. headers_timeout: Option, } impl Default for ServerOptions { fn default() -> Self { Self { headers_timeout: Some(Duration::from_secs(60)), } } } /// Accept a new incoming HTTP/1.1 connection. /// /// Supports `KeepAlive` requests by default. pub async fn accept(io: RW, endpoint: F) -> http_types::Result<()> where RW: Read + Write + Clone + Send + Sync + Unpin + 'static, F: Fn(Request) -> Fut, Fut: Future>, { Server::new(io, endpoint).accept().await } /// Accept a new incoming HTTP/1.1 connection. /// /// Supports `KeepAlive` requests by default. pub async fn accept_with_opts( io: RW, endpoint: F, opts: ServerOptions, ) -> http_types::Result<()> where RW: Read + Write + Clone + Send + Sync + Unpin + 'static, F: Fn(Request) -> Fut, Fut: Future>, { Server::new(io, endpoint).with_opts(opts).accept().await } /// struct for server #[derive(Debug)] pub struct Server { io: RW, endpoint: F, opts: ServerOptions, _phantom: PhantomData, } /// An enum that represents whether the server should accept a subsequent request #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum ConnectionStatus { /// The server should not accept another request Close, /// The server may accept another request KeepAlive, } impl Server where RW: Read + Write + Clone + Send + Sync + Unpin + 'static, F: Fn(Request) -> Fut, Fut: Future>, { /// builds a new server pub fn new(io: RW, endpoint: F) -> Self { Self { io, endpoint, opts: Default::default(), _phantom: PhantomData, } } /// with opts pub fn with_opts(mut self, opts: ServerOptions) -> Self { self.opts = opts; self } /// accept in a loop pub async fn accept(&mut self) -> http_types::Result<()> { while ConnectionStatus::KeepAlive == self.accept_one().await? {} Ok(()) } /// accept one request pub async fn accept_one(&mut self) -> http_types::Result where RW: Read + Write + Clone + Send + Sync + Unpin + 'static, F: Fn(Request) -> Fut, Fut: Future>, { // Decode a new request, timing out if this takes longer than the timeout duration. let fut = decode(self.io.clone()); let (req, mut body) = if let Some(timeout_duration) = self.opts.headers_timeout { match fut .or(async { Timer::after(timeout_duration).await; Ok(None) }) .await { Ok(Some(r)) => r, Ok(None) => return Ok(ConnectionStatus::Close), /* EOF or timeout */ Err(e) => return Err(e), } } else { match fut.await? { Some(r) => r, None => return Ok(ConnectionStatus::Close), /* EOF */ } }; let has_upgrade_header = req.header(UPGRADE).is_some(); let connection_header_as_str = req .header(CONNECTION) .map(|connection| connection.as_str()) .unwrap_or(""); let connection_header_is_upgrade = connection_header_as_str .split(',') .any(|s| s.trim().eq_ignore_ascii_case("upgrade")); let mut close_connection = connection_header_as_str.eq_ignore_ascii_case("close"); let upgrade_requested = has_upgrade_header && connection_header_is_upgrade; let method = req.method(); // Pass the request to the endpoint and encode the response. let mut res = (self.endpoint)(req).await?; close_connection |= res .header(CONNECTION) .map(|c| c.as_str().eq_ignore_ascii_case("close")) .unwrap_or(false); let upgrade_provided = res.status() == StatusCode::SwitchingProtocols && res.has_upgrade(); let upgrade_sender = if upgrade_requested && upgrade_provided { Some(res.send_upgrade()) } else { None }; let mut encoder = Encoder::new(res, method); let bytes_written = io::copy(&mut encoder, &mut self.io).await?; log::trace!("wrote {} response bytes", bytes_written); let body_bytes_discarded = io::copy(&mut body, &mut io::sink()).await?; log::trace!( "discarded {} unread request body bytes", body_bytes_discarded ); if let Some(upgrade_sender) = upgrade_sender { upgrade_sender.send(Connection::new(self.io.clone())).await; Ok(ConnectionStatus::Close) } else if close_connection { Ok(ConnectionStatus::Close) } else { Ok(ConnectionStatus::KeepAlive) } } } async-h1-2.3.4/tests/accept.rs000064400000000000000000000130561046102023000142310ustar 00000000000000mod test_utils; mod accept { use super::test_utils::TestServer; use async_h1::{client::Encoder, server::ConnectionStatus}; use async_std::io::{self, prelude::WriteExt, Cursor}; use http_types::{headers::CONNECTION, Body, Request, Response, Result}; #[async_std::test] async fn basic() -> Result<()> { let mut server = TestServer::new(|req| async { let mut response = Response::new(200); let len = req.len(); response.set_body(Body::from_reader(req, len)); Ok(response) }); let content_length = 10; let request_str = format!( "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: {}\r\n\r\n{}\r\n\r\n", content_length, std::str::from_utf8(&vec![b'|'; content_length]).unwrap() ); server.write_all(request_str.as_bytes()).await?; assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); server.close(); assert_eq!(server.accept_one().await?, ConnectionStatus::Close); assert!(server.all_read()); Ok(()) } #[async_std::test] async fn request_close() -> Result<()> { let mut server = TestServer::new(|_| async { Ok(Response::new(200)) }); server .write_all(b"GET / HTTP/1.1\r\nHost: example.com\r\nConnection: Close\r\n\r\n") .await?; assert_eq!(server.accept_one().await?, ConnectionStatus::Close); assert!(server.all_read()); Ok(()) } #[async_std::test] async fn response_close() -> Result<()> { let mut server = TestServer::new(|_| async { let mut response = Response::new(200); response.insert_header(CONNECTION, "close"); Ok(response) }); server .write_all(b"GET / HTTP/1.1\r\nHost: example.com\r\n\r\n") .await?; assert_eq!(server.accept_one().await?, ConnectionStatus::Close); assert!(server.all_read()); Ok(()) } #[async_std::test] async fn keep_alive_short_fixed_length_unread_body() -> Result<()> { let mut server = TestServer::new(|_| async { Ok(Response::new(200)) }); let content_length = 10; let request_str = format!( "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: {}\r\n\r\n{}\r\n\r\n", content_length, std::str::from_utf8(&vec![b'|'; content_length]).unwrap() ); server.write_all(request_str.as_bytes()).await?; assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); server.write_all(request_str.as_bytes()).await?; assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); server.close(); assert_eq!(server.accept_one().await?, ConnectionStatus::Close); assert!(server.all_read()); Ok(()) } #[async_std::test] async fn keep_alive_short_chunked_unread_body() -> Result<()> { let mut server = TestServer::new(|_| async { Ok(Response::new(200)) }); let content_length = 100; let mut request = Request::post("http://example.com/"); request.set_body(Body::from_reader( Cursor::new(vec![b'|'; content_length]), None, )); io::copy(&mut Encoder::new(request), &mut server).await?; assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); server .write_fmt(format_args!( "GET / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0\r\n\r\n" )) .await?; assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); server.close(); assert_eq!(server.accept_one().await?, ConnectionStatus::Close); assert!(server.all_read()); Ok(()) } #[async_std::test] async fn keep_alive_long_fixed_length_unread_body() -> Result<()> { let mut server = TestServer::new(|_| async { Ok(Response::new(200)) }); let content_length = 10000; let request_str = format!( "POST / HTTP/1.1\r\nHost: example.com\r\nContent-Length: {}\r\n\r\n{}\r\n\r\n", content_length, std::str::from_utf8(&vec![b'|'; content_length]).unwrap() ); server.write_all(request_str.as_bytes()).await?; assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); server.write_all(request_str.as_bytes()).await?; assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); server.close(); assert_eq!(server.accept_one().await?, ConnectionStatus::Close); assert!(server.all_read()); Ok(()) } #[async_std::test] async fn keep_alive_long_chunked_unread_body() -> Result<()> { let mut server = TestServer::new(|_| async { Ok(Response::new(200)) }); let content_length = 10000; let mut request = Request::post("http://example.com/"); request.set_body(Body::from_reader( Cursor::new(vec![b'|'; content_length]), None, )); server.write_request(request).await?; assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); server .write_fmt(format_args!( "GET / HTTP/1.1\r\nHost: example.com\r\nContent-Length: 0\r\n\r\n" )) .await?; assert_eq!(server.accept_one().await?, ConnectionStatus::KeepAlive); server.close(); assert_eq!(server.accept_one().await?, ConnectionStatus::Close); assert!(server.all_read()); Ok(()) } } async-h1-2.3.4/tests/client_decode.rs000064400000000000000000000046211046102023000155510ustar 00000000000000mod test_utils; mod client_decode { use std::io::Write; use super::test_utils::CloseableCursor; use async_h1::client; use async_std::io::Cursor; use http_types::headers; use http_types::Response; use http_types::Result; use pretty_assertions::assert_eq; async fn decode_lines(s: Vec<&str>) -> Result { client::decode(Cursor::new(s.join("\r\n"))).await } #[async_std::test] async fn response_no_date() -> Result<()> { let res = decode_lines(vec![ "HTTP/1.1 200 OK", "transfer-encoding: chunked", "content-type: text/plain", "", "", ]) .await?; assert!(res.header(&headers::DATE).is_some()); Ok(()) } #[async_std::test] async fn multiple_header_values_for_same_header_name() -> Result<()> { let res = decode_lines(vec![ "HTTP/1.1 200 OK", "host: example.com", "content-length: 0", "set-cookie: sessionId=e8bb43229de9", "set-cookie: qwerty=219ffwef9w0f", "", "", ]) .await?; assert_eq!(res.header(&headers::SET_COOKIE).unwrap().iter().count(), 2); Ok(()) } #[async_std::test] async fn connection_closure() -> Result<()> { let mut cursor = CloseableCursor::default(); cursor.write_all(b"HTTP/1.1 200 OK\r\nhost: example.com")?; cursor.close(); assert_eq!( client::decode(cursor).await.unwrap_err().to_string(), "empty response" ); let cursor = CloseableCursor::default(); cursor.close(); assert_eq!( client::decode(cursor).await.unwrap_err().to_string(), "connection closed" ); Ok(()) } #[async_std::test] async fn response_newlines() -> Result<()> { let res = decode_lines(vec![ "HTTP/1.1 200 OK", "content-length: 78", "date: {DATE}", "content-type: text/plain; charset=utf-8", "", "http specifies headers are separated with \r\n but many servers don't do that", "", ]) .await?; assert_eq!( res[headers::CONTENT_LENGTH] .as_str() .parse::() .unwrap(), 78 ); Ok(()) } } async-h1-2.3.4/tests/client_encode.rs000064400000000000000000000115741046102023000155700ustar 00000000000000mod client_encode { use async_h1::client; use async_std::io::Cursor; use async_std::prelude::*; use client::Encoder; use http_types::Body; use http_types::Result; use http_types::{Method, Request, Url}; use pretty_assertions::assert_eq; async fn encode_to_string(request: Request, len: usize) -> http_types::Result { let mut buf = vec![]; let mut encoder = Encoder::new(request); loop { let mut inner_buf = vec![0; len]; let bytes = encoder.read(&mut inner_buf).await?; buf.extend_from_slice(&inner_buf[..bytes]); if bytes == 0 { return Ok(String::from_utf8(buf)?); } } } #[async_std::test] async fn client_encode_request_add_date() -> Result<()> { let url = Url::parse("http://localhost:8080").unwrap(); let mut req = Request::new(Method::Post, url); req.set_body("hello"); assert_encoded( 100, req, vec![ "POST / HTTP/1.1", "host: localhost:8080", "content-length: 5", "content-type: text/plain;charset=utf-8", "", "hello", ], ) .await; Ok(()) } #[async_std::test] async fn client_encode_request_with_connect() -> Result<()> { let url = Url::parse("https://example.com:443").unwrap(); let req = Request::new(Method::Connect, url); assert_encoded( 100, req, vec![ "CONNECT example.com:443 HTTP/1.1", "host: example.com", "content-length: 0", "proxy-connection: keep-alive", "", "", ], ) .await; Ok(()) } // The fragment of an URL is not send to the server, see RFC7230 and RFC3986. #[async_std::test] async fn client_encode_request_with_fragment() -> Result<()> { let url = Url::parse("http://example.com/path?query#fragment").unwrap(); let req = Request::new(Method::Get, url); assert_encoded( 10, req, vec![ "GET /path?query HTTP/1.1", "host: example.com", "content-length: 0", "", "", ], ) .await; Ok(()) } async fn assert_encoded(len: usize, req: Request, lines: Vec<&str>) { assert_eq!( encode_to_string(req, len).await.unwrap(), lines.join("\r\n"), ) } #[async_std::test] async fn client_encode_chunked_body() -> Result<()> { let url = Url::parse("http://example.com/path?query").unwrap(); let mut req = Request::new(Method::Get, url.clone()); req.set_body(Body::from_reader(Cursor::new("hello world"), None)); assert_encoded( 10, req, vec![ "GET /path?query HTTP/1.1", "host: example.com", "content-type: application/octet-stream", "transfer-encoding: chunked", "", "5", "hello", "5", " worl", "1", "d", "0", "", "", ], ) .await; let mut req = Request::new(Method::Get, url.clone()); req.set_body(Body::from_reader(Cursor::new("hello world"), None)); assert_encoded( 16, req, vec![ "GET /path?query HTTP/1.1", "host: example.com", "content-type: application/octet-stream", "transfer-encoding: chunked", "", "B", "hello world", "0", "", "", ], ) .await; let mut req = Request::new(Method::Get, url.clone()); req.set_body(Body::from_reader( Cursor::new( "this response is more than 32 bytes long in order to require a second hex digit", ), None, )); assert_encoded( 32, req, vec![ "GET /path?query HTTP/1.1", "host: example.com", "content-type: application/octet-stream", "transfer-encoding: chunked", "", "1A", "this response is more than", "1A", " 32 bytes long in order to", "1A", " require a second hex digi", "1", "t", "0", "", "", ], ) .await; Ok(()) } } async-h1-2.3.4/tests/continue.rs000064400000000000000000000032721046102023000146150ustar 00000000000000mod test_utils; use async_std::{io, prelude::*, task}; use http_types::Result; use std::time::Duration; use test_utils::TestIO; const REQUEST_WITH_EXPECT: &[u8] = b"POST / HTTP/1.1\r\n\ Host: example.com\r\n\ Content-Length: 10\r\n\ Expect: 100-continue\r\n\r\n"; const SLEEP_DURATION: Duration = std::time::Duration::from_millis(100); #[async_std::test] async fn test_with_expect_when_reading_body() -> Result<()> { let (mut client, server) = TestIO::new(); client.write_all(REQUEST_WITH_EXPECT).await?; let (mut request, _) = async_h1::server::decode(server).await?.unwrap(); task::sleep(SLEEP_DURATION).await; //prove we're not just testing before we've written assert_eq!("", &client.read.to_string()); // we haven't written yet let join_handle = task::spawn(async move { let mut string = String::new(); request.read_to_string(&mut string).await?; //this triggers the 100-continue even though there's nothing to read yet io::Result::Ok(string) }); task::sleep(SLEEP_DURATION).await; // just long enough to wait for the channel and io assert_eq!("HTTP/1.1 100 Continue\r\n\r\n", &client.read.to_string()); client.write_all(b"0123456789").await?; assert_eq!("0123456789", &join_handle.await?); Ok(()) } #[async_std::test] async fn test_without_expect_when_not_reading_body() -> Result<()> { let (mut client, server) = TestIO::new(); client.write_all(REQUEST_WITH_EXPECT).await?; let (_, _) = async_h1::server::decode(server).await?.unwrap(); task::sleep(SLEEP_DURATION).await; // just long enough to wait for the channel assert_eq!("", &client.read.to_string()); // we haven't written 100-continue Ok(()) } async-h1-2.3.4/tests/server-chunked-encode-large.rs000064400000000000000000000324271046102023000202450ustar 00000000000000use async_h1::{client, server}; use http_types::Body; use http_types::Method; use http_types::Request; use http_types::Url; use http_types::{Response, Result}; use pretty_assertions::assert_eq; mod test_utils; use test_utils::TestIO; const BODY: &str = concat![ "Et provident reprehenderit accusamus dolores et voluptates sed quia. Repellendus odit porro ut et hic molestiae. Sit autem reiciendis animi fugiat deleniti vel iste. Laborum id odio ullam ut impedit dolores. Vel aperiam dolorem voluptatibus dignissimos maxime.", "Qui cumque autem debitis consequatur aliquam impedit id nostrum. Placeat error temporibus quos sed vel rerum. Fugit perferendis enim voluptatem rerum vitae dolor distinctio. Quia iusto ex enim voluptatum omnis. Nam et aperiam asperiores nesciunt eos magnam quidem et.", "Beatae et sit iure eum voluptatem accusantium quia optio. Tempora et rerum blanditiis repellendus qui est dolorem. Blanditiis deserunt qui dignissimos ad eligendi. Qui quia sequi et. Ipsa error quia quo ducimus et. Asperiores accusantium eius possimus dolore vitae iusto.", "Accusantium voluptatum sint dolor iste ut enim laborum quisquam. Iure sunt non quam quasi. Magni officiis necessitatibus omnis consequatur.", "Sed modi officia eos explicabo non recusandae praesentium. Est culpa maxime dolorem ullam. In dicta libero aut. Eum voluptatem corporis earum doloribus similique voluptate. Corporis et quia ad ut quia officia.", "Porro quod blanditiis molestiae ea. Aut eveniet laborum natus. At repudiandae eos quisquam fugit voluptatibus voluptate. Voluptatibus sint laudantium asperiores eum excepturi autem labore.", "Voluptate omnis enim nesciunt tempora. Non eum vero velit velit. Nostrum repudiandae laudantium neque iste minima dicta labore dicta. Velit animi enim ut et tenetur qui et aut. Minus sit eveniet autem repellendus accusamus.", "Deleniti qui sit modi quis et ut. Ea ab est tempore adipisci. At voluptas occaecati rem expedita nisi voluptatem iste. Dolor dolorem deleniti hic aliquam. Ullam aspernatur voluptas suscipit corrupti eius fugiat quisquam. Non quaerat dolorem doloremque modi quisquam eaque animi quae.", "Voluptas est eaque eaque et quaerat quae dolore. Qui quam et cumque quod. Dolores veritatis dignissimos possimus non. Ipsa excepturi quo autem nemo perferendis. Tempora et repellat accusamus consectetur.", "Sint et eum molestiae molestiae explicabo quae. Enim quia repellendus molestias. Harum rerum ut asperiores asperiores. Perferendis officiis iusto ab et ut nulla officia. Qui dicta magni est qui exercitationem et. Quaerat ut commodi beatae iure.", "Beatae dolor recusandae dicta vero quibusdam error. Voluptas modi aperiam id. Consequatur id quasi voluptas voluptates doloremque.", "Cum explicabo quisquam maiores autem a beatae alias. Corrupti et consequatur repellendus eos rerum iusto. Possimus ipsa totam vero in nam commodi ut eveniet. Facere recusandae commodi tenetur dolor et.", "Dolor ut ut architecto incidunt. Sunt tempora quia et similique et. Aut aut rerum soluta quibusdam. Sit deleniti ut veritatis ea nulla eius aut. Quidem doloribus beatae repudiandae ut. Consequatur eveniet consequatur consequatur sunt.", "Molestiae debitis et porro quis quas quas quod. Amet beatae placeat qui ut nihil quia. Sunt quos voluptatem id labore. Ut dolorum cupiditate ex velit occaecati velit eaque occaecati. Est ea temporibus expedita ipsum accusantium debitis qui.", "Explicabo vitae et maxime in provident natus. Nihil illo itaque eum omnis dolorum eos ratione. Corporis consequuntur nesciunt asperiores tenetur veniam est nulla.", "Ut distinctio aut dolor quia aspernatur delectus quia. Molestiae cupiditate corporis fugit asperiores sint eligendi magni. Quo necessitatibus corrupti ea tempore officiis est minus. Nesciunt quos qui minima nostrum nobis qui earum. Temporibus doloremque sed at.", "Qui quas occaecati et. Possimus corrupti eaque quis sed accusantium voluptatum ducimus laborum. Alias sapiente et exercitationem ex sequi accusamus ea. Quis id aspernatur soluta et quisquam animi. Aspernatur quasi autem qui. Est dolores iusto perspiciatis.", "Itaque incidunt numquam dolores quaerat. Assumenda rerum porro itaque. Ut ratione temporibus occaecati rerum qui commodi.", "Nemo nemo iste qui voluptas itaque. Quae quis qui qui cum quod natus itaque est. Dolores voluptate sapiente ipsa eveniet doloremque laboriosam velit sunt. Optio voluptatum doloremque tenetur voluptate.", "Recusandae nihil sunt similique minima quis temporibus cum. Laboriosam atque aut tenetur ducimus et vitae. Ducimus qui debitis ut. Non ducimus incidunt optio voluptatum fuga non fugit veritatis. Ut laudantium est minima corporis voluptas inventore qui eum. Rem id aut amet ut.", "Et provident reprehenderit accusamus dolores et voluptates sed quia. Repellendus odit porro ut et hic molestiae. Sit autem reiciendis animi fugiat deleniti vel iste. Laborum id odio ullam ut impedit dolores. Vel aperiam dolorem voluptatibus dignissimos maxime.", "Qui cumque autem debitis consequatur aliquam impedit id nostrum. Placeat error temporibus quos sed vel rerum. Fugit perferendis enim voluptatem rerum vitae dolor distinctio. Quia iusto ex enim voluptatum omnis. Nam et aperiam asperiores nesciunt eos magnam quidem et.", "Beatae et sit iure eum voluptatem accusantium quia optio. Tempora et rerum blanditiis repellendus qui est dolorem. Blanditiis deserunt qui dignissimos ad eligendi. Qui quia sequi et. Ipsa error quia quo ducimus et. Asperiores accusantium eius possimus dolore vitae iusto.", "Accusantium voluptatum sint dolor iste ut enim laborum quisquam. Iure sunt non quam quasi. Magni officiis necessitatibus omnis consequatur.", "Sed modi officia eos explicabo non recusandae praesentium. Est culpa maxime dolorem ullam. In dicta libero aut. Eum voluptatem corporis earum doloribus similique voluptate. Corporis et quia ad ut quia officia.", "Porro quod blanditiis molestiae ea. Aut eveniet laborum natus. At repudiandae eos quisquam fugit voluptatibus voluptate. Voluptatibus sint laudantium asperiores eum excepturi autem labore.", "Voluptate omnis enim nesciunt tempora. Non eum vero velit velit. Nostrum repudiandae laudantium neque iste minima dicta labore dicta. Velit animi enim ut et tenetur qui et aut. Minus sit eveniet autem repellendus accusamus.", "Deleniti qui sit modi quis et ut. Ea ab est tempore adipisci. At voluptas occaecati rem expedita nisi voluptatem iste. Dolor dolorem deleniti hic aliquam. Ullam aspernatur voluptas suscipit corrupti eius fugiat quisquam. Non quaerat dolorem doloremque modi quisquam eaque animi quae.", "Voluptas est eaque eaque et quaerat quae dolore. Qui quam et cumque quod. Dolores veritatis dignissimos possimus non. Ipsa excepturi quo autem nemo perferendis. Tempora et repellat accusamus consectetur.", "Sint et eum molestiae molestiae explicabo quae. Enim quia repellendus molestias. Harum rerum ut asperiores asperiores. Perferendis officiis iusto ab et ut nulla officia. Qui dicta magni est qui exercitationem et. Quaerat ut commodi beatae iure.", "Beatae dolor recusandae dicta vero quibusdam error. Voluptas modi aperiam id. Consequatur id quasi voluptas voluptates doloremque.", "Cum explicabo quisquam maiores autem a beatae alias. Corrupti et consequatur repellendus eos rerum iusto. Possimus ipsa totam vero in nam commodi ut eveniet. Facere recusandae commodi tenetur dolor et.", "Explicabo vitae et maxime in provident natus. Nihil illo itaque eum omnis dolorum eos ratione. Corporis consequuntur nesciunt asperiores tenetur veniam est nulla.", "Ut distinctio aut dolor quia aspernatur delectus quia. Molestiae cupiditate corporis fugit asperiores sint eligendi magni. Quo necessitatibus corrupti ea tempore officiis est minus. Nesciunt quos qui minima nostrum nobis qui earum. Temporibus doloremque sed at.", "Qui quas occaecati et. Possimus corrupti eaque quis sed accusantium voluptatum ducimus laborum. Alias sapiente et exercitationem ex sequi accusamus ea. Quis id aspernatur soluta et quisquam animi. Aspernatur quasi autem qui. Est dolores iusto perspiciatis.", "Itaque incidunt numquam dolores quaerat. Assumenda rerum porro itaque. Ut ratione temporibus occaecati rerum qui commodi.", "Nemo nemo iste qui voluptas itaque. Quae quis qui qui cum quod natus itaque est. Dolores voluptate sapiente ipsa eveniet doloremque laboriosam velit sunt. Optio voluptatum doloremque tenetur voluptate.", "Recusandae nihil sunt similique minima quis temporibus cum. Laboriosam atque aut tenetur ducimus et vitae. Ducimus qui debitis ut. Non ducimus incidunt optio voluptatum fuga non fugit veritatis. Ut laudantium est minima corporis voluptas inventore qui eum. Rem id aut amet ut.", "Et provident reprehenderit accusamus dolores et voluptates sed quia. Repellendus odit porro ut et hic molestiae. Sit autem reiciendis animi fugiat deleniti vel iste. Laborum id odio ullam ut impedit dolores. Vel aperiam dolorem voluptatibus dignissimos maxime.", "Qui cumque autem debitis consequatur aliquam impedit id nostrum. Placeat error temporibus quos sed vel rerum. Fugit perferendis enim voluptatem rerum vitae dolor distinctio. Quia iusto ex enim voluptatum omnis. Nam et aperiam asperiores nesciunt eos magnam quidem et.", "Accusantium voluptatum sint dolor iste ut enim laborum quisquam. Iure sunt non quam quasi. Magni officiis necessitatibus omnis consequatur.", "Sed modi officia eos explicabo non recusandae praesentium. Est culpa maxime dolorem ullam. In dicta libero aut. Eum voluptatem corporis earum doloribus similique voluptate. Corporis et quia ad ut quia officia.", "Porro quod blanditiis molestiae ea. Aut eveniet laborum natus. At repudiandae eos quisquam fugit voluptatibus voluptate. Voluptatibus sint laudantium asperiores eum excepturi autem labore.", "Voluptate omnis enim nesciunt tempora. Non eum vero velit velit. Nostrum repudiandae laudantium neque iste minima dicta labore dicta. Velit animi enim ut et tenetur qui et aut. Minus sit eveniet autem repellendus accusamus.", "Deleniti qui sit modi quis et ut. Ea ab est tempore adipisci. At voluptas occaecati rem expedita nisi voluptatem iste. Dolor dolorem deleniti hic aliquam. Ullam aspernatur voluptas suscipit corrupti eius fugiat quisquam. Non quaerat dolorem doloremque modi quisquam eaque animi quae.", "Voluptas est eaque eaque et quaerat quae dolore. Qui quam et cumque quod. Dolores veritatis dignissimos possimus non. Ipsa excepturi quo autem nemo perferendis. Tempora et repellat accusamus consectetur.", "Sint et eum molestiae molestiae explicabo quae. Enim quia repellendus molestias. Harum rerum ut asperiores asperiores. Perferendis officiis iusto ab et ut nulla officia. Qui dicta magni est qui exercitationem et. Quaerat ut commodi beatae iure.", "Beatae dolor recusandae dicta vero quibusdam error. Voluptas modi aperiam id. Consequatur id quasi voluptas voluptates doloremque.", "Cum explicabo quisquam maiores autem a beatae alias. Corrupti et consequatur repellendus eos rerum iusto. Possimus ipsa totam vero in nam commodi ut eveniet. Facere recusandae commodi tenetur dolor et.", "Dolor ut ut architecto incidunt. Sunt tempora quia et similique et. Aut aut rerum soluta quibusdam. Sit deleniti ut veritatis ea nulla eius aut. Quidem doloribus beatae repudiandae ut. Consequatur eveniet consequatur consequatur sunt.", "Molestiae debitis et porro quis quas quas quod. Amet beatae placeat qui ut nihil quia. Sunt quos voluptatem id labore. Ut dolorum cupiditate ex velit occaecati velit eaque occaecati. Est ea temporibus expedita ipsum accusantium debitis qui.", "Explicabo vitae et maxime in provident natus. Nihil illo itaque eum omnis dolorum eos ratione. Corporis consequuntur nesciunt asperiores tenetur veniam est nulla.", "Ut distinctio aut dolor quia aspernatur delectus quia. Molestiae cupiditate corporis fugit asperiores sint eligendi magni. Quo necessitatibus corrupti ea tempore officiis est minus. Nesciunt quos qui minima nostrum nobis qui earum. Temporibus doloremque sed at.", "Qui quas occaecati et. Possimus corrupti eaque quis sed accusantium voluptatum ducimus laborum. Alias sapiente et exercitationem ex sequi accusamus ea. Quis id aspernatur soluta et quisquam animi. Aspernatur quasi autem qui. Est dolores iusto perspiciatis.", "Itaque incidunt numquam dolores quaerat. Assumenda rerum porro itaque. Ut ratione temporibus occaecati rerum qui commodi.", "Nemo nemo iste qui voluptas itaque. Quae quis qui qui cum quod natus itaque est. Dolores voluptate sapiente ipsa eveniet doloremque laboriosam velit sunt. Optio voluptatum doloremque tenetur voluptate.", ]; #[async_std::test] async fn server_chunked_large() -> Result<()> { let mut request = Request::new(Method::Post, Url::parse("http://domain.com").unwrap()); // request.set_body(Body::from_reader(Cursor::new(BODY), None)); request.set_body(Body::from_string(String::from(BODY))); let (mut client, server) = TestIO::new(); async_std::io::copy(&mut client::Encoder::new(request), &mut client).await?; let (request, _) = server::decode(server).await?.unwrap(); let mut response = Response::new(200); response.set_body(Body::from_reader(request, None)); let response_encoder = server::Encoder::new(response, Method::Get); let mut response = client::decode(response_encoder).await?; assert_eq!(response.body_string().await?, BODY); Ok(()) } async-h1-2.3.4/tests/server_decode.rs000064400000000000000000000072401046102023000156010ustar 00000000000000mod test_utils; mod server_decode { use super::test_utils::TestIO; use async_std::io::prelude::*; use http_types::headers::TRANSFER_ENCODING; use http_types::Request; use http_types::Result; use http_types::Url; use pretty_assertions::assert_eq; async fn decode_lines(lines: Vec<&str>) -> Result> { let s = lines.join("\r\n"); let (mut client, server) = TestIO::new(); client.write_all(s.as_bytes()).await?; client.close(); async_h1::server::decode(server) .await .map(|r| r.map(|(r, _)| r)) } #[async_std::test] async fn post_with_body() -> Result<()> { let mut request = decode_lines(vec![ "POST / HTTP/1.1", "host: localhost:8080", "content-length: 5", "content-type: text/plain;charset=utf-8", "another-header: header value", "another-header: other header value", "", "hello", "", ]) .await? .unwrap(); assert_eq!(request.method(), http_types::Method::Post); assert_eq!(request.body_string().await?, "hello"); assert_eq!(request.content_type(), Some(http_types::mime::PLAIN)); assert_eq!(request.version(), Some(http_types::Version::Http1_1)); assert_eq!(request.host(), Some("localhost:8080")); assert_eq!( request.url(), &Url::parse("http://localhost:8080/").unwrap() ); let custom_header = request.header("another-header").unwrap(); assert_eq!(custom_header[0], "header value"); assert_eq!(custom_header[1], "other header value"); Ok(()) } #[async_std::test] async fn chunked() -> Result<()> { let mut request = decode_lines(vec![ "POST / HTTP/1.1", "host: localhost:8080", "transfer-encoding: chunked", "content-type: text/plain;charset=utf-8", "", "1", "h", "1", "e", "3", "llo", "0", "", ]) .await? .unwrap(); assert_eq!(request[TRANSFER_ENCODING], "chunked"); assert_eq!(request.body_string().await?, "hello"); Ok(()) } #[ignore = r#" the test previously did not actually assert the correct thing prevously and the behavior does not yet work as intended "#] #[async_std::test] async fn invalid_trailer() -> Result<()> { let mut request = decode_lines(vec![ "GET / HTTP/1.1", "host: domain.com", "content-type: application/octet-stream", "transfer-encoding: chunked", "trailer: x-invalid", "", "0", "x-invalid: å", "", ]) .await? .unwrap(); assert!(request.body_string().await.is_err()); Ok(()) } #[async_std::test] async fn unexpected_eof() -> Result<()> { let mut request = decode_lines(vec![ "POST / HTTP/1.1", "host: example.com", "content-type: text/plain", "content-length: 11", "", "not 11", ]) .await? .unwrap(); let mut string = String::new(); // we use read_to_string because although not currently the // case, at some point soon body_string will error if the // retrieved content length is not the same as the header (if // the client disconnects) request.read_to_string(&mut string).await?; assert_eq!(string, "not 11"); Ok(()) } } async-h1-2.3.4/tests/server_encode.rs000064400000000000000000000101221046102023000156040ustar 00000000000000mod server_encode { use async_h1::server::Encoder; use async_std::io::Cursor; use async_std::io::ReadExt; use http_types::Body; use http_types::Result; use http_types::StatusCode; use http_types::{Method, Response}; use pretty_assertions::assert_eq; async fn encode_to_string( response: Response, len: usize, method: Method, ) -> http_types::Result { let mut buf = vec![]; let mut encoder = Encoder::new(response, method); loop { let mut inner_buf = vec![0; len]; let bytes = encoder.read(&mut inner_buf).await?; buf.extend_from_slice(&inner_buf[..bytes]); if bytes == 0 { return Ok(String::from_utf8(buf)?); } } } async fn assert_encoded(len: usize, method: Method, response: Response, lines: Vec<&str>) { assert_eq!( encode_to_string(response, len, method) .await .unwrap() .split("\r\n") .map(|line| { if line.starts_with("date:") { "date: {DATE}" } else { line } }) .collect::>() .join("\r\n"), lines.join("\r\n") ); } #[async_std::test] async fn basic() -> Result<()> { let res = Response::new(StatusCode::Ok); assert_encoded( 100, Method::Get, res, vec![ "HTTP/1.1 200 OK", "content-length: 0", "date: {DATE}", "", "", ], ) .await; Ok(()) } #[async_std::test] async fn basic_404() -> Result<()> { let res = Response::new(StatusCode::NotFound); assert_encoded( 100, Method::Get, res, vec![ "HTTP/1.1 404 Not Found", "content-length: 0", "date: {DATE}", "", "", ], ) .await; Ok(()) } #[async_std::test] async fn chunked() -> Result<()> { let mut res = Response::new(StatusCode::Ok); res.set_body(Body::from_reader(Cursor::new("hello world"), None)); assert_encoded( 10, Method::Get, res, vec![ "HTTP/1.1 200 OK", "content-type: application/octet-stream", "date: {DATE}", "transfer-encoding: chunked", "", "5", "hello", "5", " worl", "1", "d", "0", "", "", ], ) .await; Ok(()) } #[async_std::test] async fn head_request_fixed_body() -> Result<()> { let mut res = Response::new(StatusCode::Ok); res.set_body("empty body because head request"); assert_encoded( 10, Method::Head, res, vec![ "HTTP/1.1 200 OK", "content-length: 31", "content-type: text/plain;charset=utf-8", "date: {DATE}", "", "", ], ) .await; Ok(()) } #[async_std::test] async fn head_request_chunked_body() -> Result<()> { let mut res = Response::new(StatusCode::Ok); res.set_body(Body::from_reader( Cursor::new("empty body because head request"), None, )); assert_encoded( 10, Method::Head, res, vec![ "HTTP/1.1 200 OK", "content-type: application/octet-stream", "date: {DATE}", "transfer-encoding: chunked", "", "", ], ) .await; Ok(()) } } async-h1-2.3.4/tests/test_utils.rs000064400000000000000000000157371046102023000152010ustar 00000000000000use async_h1::{ client::Encoder, server::{ConnectionStatus, Server}, }; use async_std::io::{Read as AsyncRead, Write as AsyncWrite}; use http_types::{Request, Response, Result}; use std::{ fmt::{Debug, Display}, future::Future, io, pin::Pin, sync::RwLock, task::{Context, Poll, Waker}, }; use async_dup::Arc; #[pin_project::pin_project] pub struct TestServer { server: Server, #[pin] client: TestIO, } impl TestServer where F: Fn(Request) -> Fut, Fut: Future>, { #[allow(dead_code)] pub fn new(f: F) -> Self { let (client, server) = TestIO::new(); Self { server: Server::new(server, f), client, } } #[allow(dead_code)] pub async fn accept_one(&mut self) -> http_types::Result { self.server.accept_one().await } #[allow(dead_code)] pub fn close(&mut self) { self.client.close(); } #[allow(dead_code)] pub fn all_read(&self) -> bool { self.client.all_read() } #[allow(dead_code)] pub async fn write_request(&mut self, request: Request) -> io::Result<()> { async_std::io::copy(&mut Encoder::new(request), self).await?; Ok(()) } } impl AsyncRead for TestServer where F: Fn(Request) -> Fut, Fut: Future>, { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { self.project().client.poll_read(cx, buf) } } impl AsyncWrite for TestServer where F: Fn(Request) -> Fut, Fut: Future>, { fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { self.project().client.poll_write(cx, buf) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().client.poll_flush(cx) } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().client.poll_close(cx) } } /// a Test IO #[derive(Default, Clone, Debug)] pub struct TestIO { pub read: Arc, pub write: Arc, } #[derive(Default)] struct CloseableCursorInner { data: Vec, cursor: usize, waker: Option, closed: bool, } #[derive(Default)] pub struct CloseableCursor(RwLock); impl CloseableCursor { pub fn len(&self) -> usize { self.0.read().unwrap().data.len() } pub fn cursor(&self) -> usize { self.0.read().unwrap().cursor } pub fn is_empty(&self) -> bool { self.len() == 0 } pub fn current(&self) -> bool { let inner = self.0.read().unwrap(); inner.data.len() == inner.cursor } pub fn close(&self) { let mut inner = self.0.write().unwrap(); inner.closed = true; if let Some(waker) = inner.waker.take() { waker.wake(); } } } impl Display for CloseableCursor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let inner = self.0.read().unwrap(); let s = std::str::from_utf8(&inner.data).unwrap_or("not utf8"); write!(f, "{}", s) } } impl TestIO { pub fn new() -> (TestIO, TestIO) { let client = Arc::new(CloseableCursor::default()); let server = Arc::new(CloseableCursor::default()); ( TestIO { read: client.clone(), write: server.clone(), }, TestIO { read: server, write: client, }, ) } pub fn all_read(&self) -> bool { self.write.current() } pub fn close(&mut self) { self.write.close(); } } impl Debug for CloseableCursor { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let inner = self.0.read().unwrap(); f.debug_struct("CloseableCursor") .field( "data", &std::str::from_utf8(&inner.data).unwrap_or("not utf8"), ) .field("closed", &inner.closed) .field("cursor", &inner.cursor) .finish() } } impl AsyncRead for CloseableCursor { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { Pin::new(&mut &*self).poll_read(cx, buf) } } impl AsyncRead for &CloseableCursor { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { let mut inner = self.0.write().unwrap(); if inner.cursor < inner.data.len() { let bytes_to_copy = buf.len().min(inner.data.len() - inner.cursor); buf[..bytes_to_copy] .copy_from_slice(&inner.data[inner.cursor..inner.cursor + bytes_to_copy]); inner.cursor += bytes_to_copy; Poll::Ready(Ok(bytes_to_copy)) } else if inner.closed { Poll::Ready(Ok(0)) } else { inner.waker = Some(cx.waker().clone()); Poll::Pending } } } impl AsyncWrite for &CloseableCursor { fn poll_write( self: Pin<&mut Self>, _cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { let mut inner = self.0.write().unwrap(); if inner.closed { Poll::Ready(Ok(0)) } else { inner.data.extend_from_slice(buf); if let Some(waker) = inner.waker.take() { waker.wake(); } Poll::Ready(Ok(buf.len())) } } fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { self.close(); Poll::Ready(Ok(())) } } impl AsyncRead for TestIO { fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { Pin::new(&mut &*self.read).poll_read(cx, buf) } } impl AsyncWrite for TestIO { fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { Pin::new(&mut &*self.write).poll_write(cx, buf) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut &*self.write).poll_flush(cx) } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut &*self.write).poll_close(cx) } } impl std::io::Write for CloseableCursor { fn write(&mut self, buf: &[u8]) -> io::Result { self.0.write().unwrap().data.write(buf) } fn flush(&mut self) -> io::Result<()> { Ok(()) } }