pax_global_header 0000666 0000000 0000000 00000000064 14724135353 0014521 g ustar 00root root 0000000 0000000 52 comment=7bd8e3fd7cad0afab15b41944184e6523d80f23f
imap-next-0.3.1/ 0000775 0000000 0000000 00000000000 14724135353 0013424 5 ustar 00root root 0000000 0000000 imap-next-0.3.1/.github/ 0000775 0000000 0000000 00000000000 14724135353 0014764 5 ustar 00root root 0000000 0000000 imap-next-0.3.1/.github/FUNDING.yml 0000664 0000000 0000000 00000000021 14724135353 0016572 0 ustar 00root root 0000000 0000000 github: [duesee]
imap-next-0.3.1/.github/actions/ 0000775 0000000 0000000 00000000000 14724135353 0016424 5 ustar 00root root 0000000 0000000 imap-next-0.3.1/.github/actions/cache_restore/ 0000775 0000000 0000000 00000000000 14724135353 0021232 5 ustar 00root root 0000000 0000000 imap-next-0.3.1/.github/actions/cache_restore/action.yml 0000664 0000000 0000000 00000001227 14724135353 0023234 0 ustar 00root root 0000000 0000000 name: cache_restore
runs:
using: composite
steps:
- uses: actions/cache/restore@v4
with:
path: |
# See https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci
~/.cargo/.crates.toml
~/.cargo/.crates2.json
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
# See https://doc.rust-lang.org/cargo/guide/build-cache.html
target
key: ${{ runner.os }}|${{ github.job }}|${{ github.run_attempt }}
restore-keys: |
${{ runner.os }}|${{ github.job }}
${{ runner.os }}
imap-next-0.3.1/.github/actions/cache_save/ 0000775 0000000 0000000 00000000000 14724135353 0020505 5 ustar 00root root 0000000 0000000 imap-next-0.3.1/.github/actions/cache_save/action.yml 0000664 0000000 0000000 00000001061 14724135353 0022503 0 ustar 00root root 0000000 0000000 name: cache_save
runs:
using: composite
steps:
- uses: actions/cache/save@v4
with:
path: |
# See https://doc.rust-lang.org/cargo/guide/cargo-home.html#caching-the-cargo-home-in-ci
~/.cargo/.crates.toml
~/.cargo/.crates2.json
~/.cargo/bin/
~/.cargo/registry/index/
~/.cargo/registry/cache/
~/.cargo/git/db/
# See https://doc.rust-lang.org/cargo/guide/build-cache.html
target
key: ${{ runner.os }}|${{ github.job }}|${{ github.run_attempt }}
imap-next-0.3.1/.github/dependabot.yml 0000664 0000000 0000000 00000000421 14724135353 0017611 0 ustar 00root root 0000000 0000000 version: 2
updates:
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "cargo"
directory: "/"
schedule:
interval: "weekly"
groups:
dependencies:
patterns:
- "*"
imap-next-0.3.1/.github/workflows/ 0000775 0000000 0000000 00000000000 14724135353 0017021 5 ustar 00root root 0000000 0000000 imap-next-0.3.1/.github/workflows/audit.yml 0000664 0000000 0000000 00000001026 14724135353 0020651 0 ustar 00root root 0000000 0000000 name: audit
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
schedule:
# 21:43 on Wednesday and Sunday. (Thanks, crontab.guru)
- cron: '43 21 * * 3,0'
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/cache_restore
- run: cargo install just
- run: just audit
- uses: ./.github/actions/cache_save
imap-next-0.3.1/.github/workflows/main.yml 0000664 0000000 0000000 00000004334 14724135353 0020474 0 ustar 00root root 0000000 0000000 name: main
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/cache_restore
- run: cargo install just
- run: just check
- uses: ./.github/actions/cache_save
test:
strategy:
matrix:
os: [ ubuntu-latest, macos-latest, windows-latest ]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- name: Setup | Install NASM (Windows)
uses: ilammy/setup-nasm@v1
if: matrix.os == 'windows-latest'
- uses: ./.github/actions/cache_restore
- run: cargo install just
- run: just test
- uses: ./.github/actions/cache_save
# benchmark:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: ./.github/actions/cache_restore
# - run: cargo install just
# - run: just bench_against_main
# - uses: ./.github/actions/cache_save
coverage:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/cache_restore
- run: cargo install just
- run: just coverage
- uses: ./.github/actions/cache_save
- uses: coverallsapp/github-action@cfd0633edbd2411b532b808ba7a8b5e04f76d2c8
with:
format: lcov
file: target/coverage/coverage.lcov
# fuzz:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v4
# - uses: ./.github/actions/cache_restore
# - run: cargo install just
# - run: just fuzz
# - uses: ./.github/actions/cache_save
check_msrv:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/cache_restore
- run: cargo install just
- run: just check_msrv
- uses: ./.github/actions/cache_save
check_minimal_dependency_versions:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: ./.github/actions/cache_restore
- run: cargo install just
- run: just check_minimal_dependency_versions
- uses: ./.github/actions/cache_save
imap-next-0.3.1/.github/workflows/release.yml 0000664 0000000 0000000 00000001467 14724135353 0021174 0 ustar 00root root 0000000 0000000 name: release
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Assert release version matches crate version
run: |
set -euo pipefail
# Get release version from Git tag
tag_version=${GITHUB_REF#refs/tags/v}
# Get crate version from Cargo.toml
crate_version=$(cargo read-manifest | jq -r .version)
if [ "$tag_version" != "$crate_version" ]; then
echo "Error: Release version ${tag_version} from Git tag does not match crate version ${crate_version} from Cargo.toml"
exit 1
fi
- name: Publish crate to crates.io
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish
imap-next-0.3.1/.gitignore 0000664 0000000 0000000 00000000140 14724135353 0015407 0 ustar 00root root 0000000 0000000 /target
# IntelliJ, CLion, RustRover, ...
.idea
# direnv (https://direnv.net/)
.envrc
.direnv
imap-next-0.3.1/CHANGELOG.md 0000664 0000000 0000000 00000002347 14724135353 0015243 0 ustar 00root root 0000000 0000000 # Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] - YYYY-MM-DD
### Added
* Created README, CHANGELOG, badges, rustfmt.toml, ...
* Created project board
* Setup CI: Check, Build, Lint, Audit, Coverage, ...
* Licensed everything as "APACHE OR MIT"
* `imap-next`
* Implemented literal handling, handles, events, and examples
* Implemented AUTHENTICATE and IDLE
* Implemented a self-test, and tested against a few providers
* `proxy`
* Implemented argument processing and configuration
* Smoke tested against a few providers (and a few MUAs)
* Provided a README
* Supported capabilities are ...
* AUTH={PLAIN,LOGIN,XOAUTH2,ScramSha1,ScramSha256}
* SASL-IR
* QUOTA*
* MOVE
* LITERAL+/LITERAL-
* UNSELECT
* ID
* IDLE
* Use ALPN==imap
* `imap-tasks` prototype
* Designed `Task`s trait
* Implemented `Task` for a few commands
* Implemented a task scheduler/manager
* `tag-generator`
[Unreleased]: https://github.com/duesee/imap-next/compare/0a89b5e180ad7dfd3d67d1184370fa1028ea92b4...HEAD
imap-next-0.3.1/Cargo.lock 0000664 0000000 0000000 00000055010 14724135353 0015332 0 ustar 00root root 0000000 0000000 # This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "abnf-core"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec182d1f071b906a9f59269c89af101515a5cbe58f723eb6717e7fe7445c0dea"
dependencies = [
"nom",
]
[[package]]
name = "addr2line"
version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "aho-corasick"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
"memchr",
]
[[package]]
name = "arbitrary"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d5a26814d8dcb93b0e5a0ff3c6d80a8843bafb21b39e8e18a6f05471870e110"
dependencies = [
"derive_arbitrary",
]
[[package]]
name = "autocfg"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
[[package]]
name = "backtrace"
version = "0.3.73"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bitflags"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
[[package]]
name = "bounded-static"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0beb903daa49b43bcafb5d5eebe633f9ad638d8b16cd08f95fb05ee7bd099321"
[[package]]
name = "bounded-static-derive"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0af050e27e5d57aa14975f97fe47a134c46a390f91819f23a625319a7111bfa"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "bstr"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a68f1f47cdf0ec8ee4b941b2eee2a80cb796db73118c0dd09ac63fbe405be22"
dependencies = [
"memchr",
]
[[package]]
name = "bytes"
version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9ac0150caa2ae65ca5bd83f25c7de183dea78d4d366469f148435e2acfbad0da"
[[package]]
name = "cc"
version = "1.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2aba8f4e9906c7ce3c73463f62a7f0c65183ada1a2d47e397cc8810827f9694f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
dependencies = [
"arbitrary",
"num-traits",
"serde",
]
[[package]]
name = "derive_arbitrary"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.29.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd"
[[package]]
name = "hermit-abi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024"
[[package]]
name = "imap-codec"
version = "2.0.0-alpha.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f584310addd1fb8fe288e4f07c279fec9264ac1ea68b018241ae4dcd4fb28557"
dependencies = [
"abnf-core",
"base64",
"chrono",
"imap-types",
"log",
"nom",
]
[[package]]
name = "imap-next"
version = "0.3.1"
dependencies = [
"bytes",
"imap-codec",
"imap-next",
"rand",
"thiserror 2.0.3",
"tokio",
"tokio-rustls",
"tracing",
]
[[package]]
name = "imap-types"
version = "2.0.0-alpha.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d601d81f11962a649acc2d535ad7311770e30364b4a978a762de291829c9ef53"
dependencies = [
"arbitrary",
"base64",
"bounded-static",
"bounded-static-derive",
"chrono",
"rand",
"serde",
"thiserror 1.0.69",
]
[[package]]
name = "integration-test"
version = "0.1.0"
dependencies = [
"bstr",
"bytes",
"imap-codec",
"imap-next",
"lazy_static",
"tokio",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
version = "0.2.155"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "matchers"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558"
dependencies = [
"regex-automata 0.1.10",
]
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "minimal-lexical"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
dependencies = [
"adler",
]
[[package]]
name = "mio"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4"
dependencies = [
"hermit-abi",
"libc",
"wasi",
"windows-sys",
]
[[package]]
name = "nom"
version = "7.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
dependencies = [
"memchr",
"minimal-lexical",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]]
name = "num-traits"
version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
"autocfg",
]
[[package]]
name = "object"
version = "0.36.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "081b846d1d56ddfc18fdf1a922e4f6e07a11768ea1b92dec44e42b72712ccfce"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "parking_lot"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27"
dependencies = [
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.9.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"smallvec",
"windows-targets",
]
[[package]]
name = "pin-project-lite"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro2"
version = "1.0.86"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "redox_syscall"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4"
dependencies = [
"bitflags",
]
[[package]]
name = "regex"
version = "1.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata 0.4.7",
"regex-syntax 0.8.4",
]
[[package]]
name = "regex-automata"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
dependencies = [
"regex-syntax 0.6.29",
]
[[package]]
name = "regex-automata"
version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax 0.8.4",
]
[[package]]
name = "regex-syntax"
version = "0.6.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1"
[[package]]
name = "regex-syntax"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b"
[[package]]
name = "ring"
version = "0.17.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d"
dependencies = [
"cc",
"cfg-if",
"getrandom",
"libc",
"spin",
"untrusted",
"windows-sys",
]
[[package]]
name = "rustc-demangle"
version = "0.1.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
[[package]]
name = "rustls"
version = "0.23.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044"
dependencies = [
"once_cell",
"rustls-pki-types",
"rustls-webpki",
"subtle",
"zeroize",
]
[[package]]
name = "rustls-pki-types"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d"
[[package]]
name = "rustls-webpki"
version = "0.102.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e"
dependencies = [
"ring",
"rustls-pki-types",
"untrusted",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.210"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "sharded-slab"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6"
dependencies = [
"lazy_static",
]
[[package]]
name = "signal-hook-registry"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1"
dependencies = [
"libc",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "socket2"
version = "0.5.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
[[package]]
name = "subtle"
version = "2.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
[[package]]
name = "syn"
version = "2.0.87"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl 1.0.69",
]
[[package]]
name = "thiserror"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa"
dependencies = [
"thiserror-impl 2.0.3",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thiserror-impl"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "thread_local"
version = "1.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "tokio"
version = "1.41.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22cfb5bee7a6a52939ca9224d6ac897bb669134078daa8735560897f69de4d33"
dependencies = [
"backtrace",
"bytes",
"libc",
"mio",
"parking_lot",
"pin-project-lite",
"signal-hook-registry",
"socket2",
"tokio-macros",
"windows-sys",
]
[[package]]
name = "tokio-macros"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tokio-rustls"
version = "0.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4"
dependencies = [
"rustls",
"rustls-pki-types",
"tokio",
]
[[package]]
name = "tracing"
version = "0.1.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
dependencies = [
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3"
dependencies = [
"log",
"once_cell",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b"
dependencies = [
"matchers",
"nu-ansi-term",
"once_cell",
"regex",
"sharded-slab",
"smallvec",
"thread_local",
"tracing",
"tracing-core",
"tracing-log",
]
[[package]]
name = "unicode-ident"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
[[package]]
name = "untrusted"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
dependencies = [
"windows-targets",
]
[[package]]
name = "windows-targets"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_gnullvm",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
[[package]]
name = "windows_i686_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
[[package]]
name = "windows_i686_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
[[package]]
name = "windows_i686_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
[[package]]
name = "zeroize"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde"
imap-next-0.3.1/Cargo.toml 0000664 0000000 0000000 00000003056 14724135353 0015360 0 ustar 00root root 0000000 0000000 [package]
name = "imap-next"
description = "Thin sans I/O abstraction over IMAP's distinct protocol flows"
keywords = ["email", "imap", "protocol", "network"]
categories = ["email", "network-programming"]
version = "0.3.1"
repository = "https://github.com/duesee/imap-next"
edition = "2021"
license = "MIT OR Apache-2.0"
exclude = [".github"]
[features]
default = ["stream"]
expose_stream = []
stream = ["dep:bytes", "dep:tokio", "dep:tokio-rustls"]
#
arbitrary = ["imap-codec/arbitrary"]
arbitrary_simplified = ["imap-codec/arbitrary_simplified"]
serde = ["imap-codec/serde"]
tag_generator = ["imap-codec/tag_generator"]
# IMAP
starttls = ["imap-codec/starttls"]
ext_condstore_qresync = ["imap-codec/ext_condstore_qresync"]
ext_id = ["imap-codec/ext_id"]
ext_login_referrals = ["imap-codec/ext_login_referrals"]
ext_mailbox_referrals = ["imap-codec/ext_mailbox_referrals"]
ext_metadata = ["imap-codec/ext_metadata"]
#
[dependencies]
bytes = { version = "1.8.0", optional = true }
imap-codec = { version = "2.0.0-alpha.5", features = ["quirk_crlf_relaxed"] }
thiserror = "2.0.3"
tokio = { version = "1.41.1", optional = true, features = ["io-util", "macros", "net"] }
tokio-rustls = { version = "0.26.0", optional = true, default-features = false }
tracing = "0.1.40"
[dev-dependencies]
# We want to enable `tag_generator` for examples.
imap-next = { path = ".", features = ["tag_generator"] }
rand = "0.8.5"
tokio = { version = "1.41.1", features = ["full"] }
[workspace]
resolver = "2"
members = [
"integration-test",
]
imap-next-0.3.1/LICENSE-APACHE 0000664 0000000 0000000 00000026135 14724135353 0015357 0 ustar 00root root 0000000 0000000 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
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.
imap-next-0.3.1/LICENSE-MIT 0000664 0000000 0000000 00000002062 14724135353 0015060 0 ustar 00root root 0000000 0000000 MIT License
Copyright (c) 2020 Damian Poddebniak
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.
imap-next-0.3.1/README.md 0000664 0000000 0000000 00000007356 14724135353 0014716 0 ustar 00root root 0000000 0000000 [](https://github.com/duesee/imap-next/actions/workflows/main.yml)
[](https://github.com/duesee/imap-next/actions/workflows/audit.yml)
[](https://coveralls.io/github/duesee/imap-next?branch=main)
# imap-next ð“…Ÿ
```mermaid
%%{init: {'theme': 'neutral' } }%%
flowchart LR
imap-types --> imap-codec
imap-codec --> imap-next
imap-next -.-> imap-proxy
imap-next -.-> imap-client
style imap-codec stroke-dasharray: 10 5
style imap-next stroke-width:4px
click imap-types href "https://github.com/duesee/imap-codec/tree/main/imap-types"
click imap-codec href "https://github.com/duesee/imap-codec"
click imap-next href "https://github.com/duesee/imap-next"
click imap-proxy href "https://github.com/duesee/imap-proxy"
click imap-client href "https://github.com/soywod/imap-client"
```
`imap-next` is a thin sans I/O abstraction over IMAP's distinct protocol flows.
These are literal handling, AUTHENTICATE, and IDLE.
The way these protocol flows were defined in IMAP couples networking, parsing, and business logic.
`imap-next` untangles them, providing a minimal interface allowing sending and receiving coherent messages.
It's a thin layer paving the ground for higher-level client or server implementations.
And it's sans I/O enabling the integration in any existing I/O runtime.
## Lower-level Libraries
`imap-next` uses [`imap-codec`](https://github.com/duesee/imap-codec) internally for parsing and serialization, and
re-exposes [`imap-types`](https://github.com/duesee/imap-codec/tree/main/imap-types).
## Higher-level Libraries
* [`imap-proxy`](https://github.com/duesee/imap-proxy) is an IMAP proxy that gracefully forwards unsolicited responses,
abstracts over literals, and `Debug`-prints messages.
* [`imap-client`](https://github.com/soywod/imap-client) is a methods-based client library with
a `client.capability()`, `client.login()`, ... interface.
## Usage
```rust,no_run
use std::error::Error;
use imap_next::{
client::{Client, Event, Options},
imap_types::{
command::{Command, CommandBody},
core::Tag,
},
stream::Stream,
};
use tokio::net::TcpStream;
#[tokio::main]
async fn main() -> Result<(), Box> {
let mut stream = Stream::insecure(TcpStream::connect("127.0.0.1:1143").await?);
let mut client = Client::new(Options::default());
loop {
match stream.next(&mut client).await? {
event => {
println!("{event:?}");
if matches!(event, Event::GreetingReceived { .. }) {
break;
}
}
}
}
let handle = client.enqueue_command(Command::new("A1", CommandBody::login("Al¹cE", "pa²²w0rd")?)?);
loop {
match stream.next(&mut client).await? {
event => println!("{event:?}"),
}
}
}
```
# License
This crate is dual-licensed under Apache 2.0 and MIT terms.
# Thanks
Thanks to the [NLnet Foundation](https://nlnet.nl/) for supporting `imap-next` through
their [NGI Assure](https://nlnet.nl/assure/) program!
imap-next-0.3.1/deny.toml 0000664 0000000 0000000 00000000440 14724135353 0015256 0 ustar 00root root 0000000 0000000 [sources]
unknown-registry = "deny"
unknown-git = "deny"
[licenses]
allow = [ "Apache-2.0", "BSD-3-Clause", "MIT", "Unicode-DFS-2016", "ISC" ]
[[licenses.clarify]]
name = "ring"
expression = "MIT AND ISC AND OpenSSL"
license-files = [
{ path = "LICENSE", hash = 0xbd0eed23 }
]
imap-next-0.3.1/examples/ 0000775 0000000 0000000 00000000000 14724135353 0015242 5 ustar 00root root 0000000 0000000 imap-next-0.3.1/examples/client.rs 0000664 0000000 0000000 00000003744 14724135353 0017076 0 ustar 00root root 0000000 0000000 use imap_next::{
client::{Client, Event, Options},
imap_types::{
command::{Command, CommandBody},
core::Tag,
},
stream::Stream,
};
use tokio::net::TcpStream;
#[tokio::main(flavor = "current_thread")]
async fn main() {
let stream = TcpStream::connect("127.0.0.1:12345").await.unwrap();
let mut stream = Stream::insecure(stream);
let mut client = Client::new(Options::default());
let greeting = loop {
match stream.next(&mut client).await.unwrap() {
Event::GreetingReceived { greeting } => break greeting,
event => println!("unexpected event: {event:?}"),
}
};
println!("received greeting: {greeting:?}");
let handle = client.enqueue_command(Command {
tag: Tag::try_from("A1").unwrap(),
body: CommandBody::login("Al¹cE", "pa²²w0rd").unwrap(),
});
loop {
match stream.next(&mut client).await.unwrap() {
Event::CommandSent {
handle: got_handle,
command,
} => {
println!("command sent: {got_handle:?}, {command:?}");
assert_eq!(handle, got_handle);
}
Event::CommandRejected {
handle: got_handle,
command,
status,
} => {
println!("command rejected: {got_handle:?}, {command:?}, {status:?}");
assert_eq!(handle, got_handle);
}
Event::DataReceived { data } => {
println!("data received: {data:?}");
}
Event::StatusReceived { status } => {
println!("status received: {status:?}");
}
Event::ContinuationRequestReceived {
continuation_request,
} => {
println!("unexpected continuation request received: {continuation_request:?}");
}
event => {
println!("{event:?}");
}
}
}
}
imap-next-0.3.1/examples/client_authenticate.rs 0000664 0000000 0000000 00000003403 14724135353 0021624 0 ustar 00root root 0000000 0000000 use std::collections::VecDeque;
use imap_next::{
client::{Client, Event, Options},
imap_types::{
auth::{AuthMechanism, AuthenticateData},
command::{Command, CommandBody},
core::TagGenerator,
},
stream::Stream,
};
use tokio::net::TcpStream;
#[tokio::main(flavor = "current_thread")]
async fn main() {
let stream = TcpStream::connect("127.0.0.1:12345").await.unwrap();
let mut stream = Stream::insecure(stream);
let mut client = Client::new(Options::default());
loop {
match stream.next(&mut client).await.unwrap() {
Event::GreetingReceived { .. } => break,
event => println!("unexpected event: {event:?}"),
}
}
let mut tag_generator = TagGenerator::new();
let tag = tag_generator.generate();
client.enqueue_command(Command {
tag: tag.clone(),
body: CommandBody::authenticate(AuthMechanism::Login),
});
let mut authenticate_data = VecDeque::from([
AuthenticateData::r#continue(b"alice".to_vec()),
AuthenticateData::r#continue(b"password".to_vec()),
]);
loop {
let event = stream.next(&mut client).await.unwrap();
println!("{event:?}");
match event {
Event::AuthenticateContinuationRequestReceived { .. } => {
if let Some(authenticate_data) = authenticate_data.pop_front() {
client.set_authenticate_data(authenticate_data).unwrap();
} else {
client
.set_authenticate_data(AuthenticateData::Cancel)
.unwrap();
}
}
Event::AuthenticateStatusReceived { .. } => {
break;
}
_ => {}
}
}
}
imap-next-0.3.1/examples/client_idle.rs 0000664 0000000 0000000 00000006525 14724135353 0020073 0 ustar 00root root 0000000 0000000 use std::io::BufRead;
use imap_next::{
client::{Client, Event, Options},
imap_types::{
command::{Command, CommandBody},
core::Tag,
response::{Status, Tagged},
},
stream::Stream,
};
use tokio::{net::TcpStream, sync::mpsc::Receiver};
#[tokio::main(flavor = "current_thread")]
async fn main() {
let stream = TcpStream::connect("127.0.0.1:12345").await.unwrap();
let mut stream = Stream::insecure(stream);
let mut client = Client::new(Options::default());
loop {
match stream.next(&mut client).await.unwrap() {
Event::GreetingReceived { .. } => break,
event => println!("unexpected event: {event:?}"),
}
}
println!("Press ENTER to stop IDLE");
let mut lines = Lines::new();
let tag = Tag::unvalidated("A1");
let _handle = client.enqueue_command(Command {
tag: tag.clone(),
body: CommandBody::Idle,
});
loop {
tokio::select! {
event = stream.next(&mut client) => {
match event.unwrap() {
Event::IdleCommandSent { .. } => {
println!("IDLE command sent")
},
Event::IdleAccepted { continuation_request, .. } => {
println!("IDLE accepted: {continuation_request:?}");
},
Event::IdleRejected { status, .. } => {
println!("IDLE rejected: {status:?}");
break;
},
Event::IdleDoneSent { .. } => {
println!("IDLE DONE sent");
break;
},
Event::DataReceived { data } => {
println!("Data received: {data:?}")
},
Event::StatusReceived { status } => {
println!("Status received: {status:?}")
},
event => {
println!("Unknown event received: {event:?}");
}
}
}
_ = lines.next() => {
if client.set_idle_done().is_some() {
println!("Triggered IDLE DONE");
} else {
println!("Can't trigger IDLE DONE now");
}
}
}
}
loop {
match stream.next(&mut client).await.unwrap() {
ref event @ Event::StatusReceived {
status:
Status::Tagged(Tagged {
tag: ref got_tag, ..
}),
} if *got_tag == tag => {
println!("Status for IDLE received: {event:?}");
break;
}
event => {
println!("Unknown event received: {event:?}");
}
}
}
}
struct Lines {
receiver: Receiver,
}
impl Lines {
pub fn new() -> Self {
let (sender, receiver) = tokio::sync::mpsc::channel(1);
tokio::task::spawn_blocking(move || loop {
for line in std::io::stdin().lock().lines() {
sender.blocking_send(line.unwrap()).unwrap();
}
});
Self { receiver }
}
pub async fn next(&mut self) -> String {
self.receiver.recv().await.unwrap()
}
}
imap-next-0.3.1/examples/client_std.rs 0000664 0000000 0000000 00000005747 14724135353 0017755 0 ustar 00root root 0000000 0000000 use std::{
io::{Read, Write},
net::TcpStream,
};
use imap_next::{
client::{Client, Event, Options},
imap_types::{
command::{Command, CommandBody},
core::Tag,
},
Interrupt, Io, State,
};
fn main() {
let mut stream = TcpStream::connect("127.0.0.1:12345").unwrap();
let mut read_buffer = [0; 128];
let mut client = Client::new(Options::default());
let greeting = loop {
match client.next() {
Err(interrupt) => match interrupt {
Interrupt::Io(Io::NeedMoreInput) => {
let count = stream.read(&mut read_buffer).unwrap();
client.enqueue_input(&read_buffer[0..count]);
}
interrupt => panic!("unexpected interrupt: {interrupt:?}"),
},
Ok(event) => match event {
Event::GreetingReceived { greeting } => break greeting,
event => println!("unexpected event: {event:?}"),
},
}
};
println!("received greeting: {greeting:?}");
let handle = client.enqueue_command(Command {
tag: Tag::try_from("A1").unwrap(),
body: CommandBody::login("Al¹cE", "pa²²w0rd").unwrap(),
});
loop {
match client.next() {
Err(interrupt) => match interrupt {
Interrupt::Io(Io::NeedMoreInput) => {
let count = stream.read(&mut read_buffer).unwrap();
client.enqueue_input(&read_buffer[0..count]);
}
Interrupt::Io(Io::Output(bytes)) => {
stream.write_all(&bytes).unwrap();
}
Interrupt::Error(error) => {
panic!("unexpected error: {error:?}");
}
},
Ok(event) => match event {
Event::CommandSent {
handle: got_handle,
command,
} => {
println!("command sent: {got_handle:?}, {command:?}");
assert_eq!(handle, got_handle);
}
Event::CommandRejected {
handle: got_handle,
command,
status,
} => {
println!("command rejected: {got_handle:?}, {command:?}, {status:?}");
assert_eq!(handle, got_handle);
}
Event::DataReceived { data } => {
println!("data received: {data:?}");
}
Event::StatusReceived { status } => {
println!("status received: {status:?}");
}
Event::ContinuationRequestReceived {
continuation_request,
} => {
println!("unexpected continuation request received: {continuation_request:?}");
}
event => {
println!("{event:?}");
}
},
}
}
}
imap-next-0.3.1/examples/server.rs 0000664 0000000 0000000 00000003040 14724135353 0017113 0 ustar 00root root 0000000 0000000 use std::collections::VecDeque;
use imap_next::{
imap_types::response::{Greeting, Status},
server::{Event, Options, Server},
stream::Stream,
};
use tokio::net::TcpListener;
#[tokio::main(flavor = "current_thread")]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:12345").await.unwrap();
let (stream, _) = listener.accept().await.unwrap();
let mut stream = Stream::insecure(stream);
let mut server = Server::new(
Options::default(),
Greeting::ok(None, "server (example)").unwrap(),
);
loop {
match stream.next(&mut server).await.unwrap() {
Event::GreetingSent { greeting } => {
println!("greeting sent: {greeting:?}");
break;
}
event => println!("unexpected event: {event:?}"),
}
}
let mut handles = VecDeque::new();
loop {
match stream.next(&mut server).await.unwrap() {
Event::CommandReceived { command } => {
println!("command received: {command:?}");
handles.push_back(
server.enqueue_status(Status::no(Some(command.tag), None, "...").unwrap()),
);
}
Event::ResponseSent {
handle: got_handle,
response,
} => {
println!("response sent: {response:?}");
assert_eq!(handles.pop_front(), Some(got_handle));
}
event => {
println!("{event:?}");
}
}
}
}
imap-next-0.3.1/examples/server_authenticate.rs 0000664 0000000 0000000 00000005241 14724135353 0021656 0 ustar 00root root 0000000 0000000 use imap_next::{
imap_types::response::{CommandContinuationRequest, Greeting, Status},
server::{Event, Options, Server},
stream::Stream,
types::CommandAuthenticate,
};
use tokio::net::TcpListener;
#[tokio::main(flavor = "current_thread")]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:12345").await.unwrap();
let (stream, _) = listener.accept().await.unwrap();
let mut stream = Stream::insecure(stream);
let mut server = Server::new(
Options::default(),
Greeting::ok(None, "server_idle (example)").unwrap(),
);
loop {
match stream.next(&mut server).await.unwrap() {
Event::GreetingSent { .. } => break,
event => println!("unexpected event: {event:?}"),
}
}
let mut current_authenticate_tag = None;
loop {
let event = stream.next(&mut server).await.unwrap();
println!("{event:?}");
// We don't implement any real SASL mechanism in this example.
let pretend_to_need_more_data = rand::random();
match event {
Event::CommandAuthenticateReceived {
command_authenticate: CommandAuthenticate { tag, .. },
} => {
if pretend_to_need_more_data {
server
.authenticate_continue(
CommandContinuationRequest::basic(None, "I need more data...").unwrap(),
)
.unwrap();
current_authenticate_tag = Some(tag);
} else {
server
.authenticate_finish(
Status::ok(Some(tag), None, "Thanks, that's already enough!").unwrap(),
)
.unwrap();
}
}
Event::AuthenticateDataReceived { .. } => {
if pretend_to_need_more_data {
server
.authenticate_continue(
CommandContinuationRequest::basic(None, "...more...").unwrap(),
)
.unwrap();
} else {
let tag = current_authenticate_tag.take().unwrap();
server
.authenticate_finish(Status::ok(Some(tag), None, "Thanks!").unwrap())
.unwrap();
}
}
Event::CommandReceived { command } => {
server.enqueue_status(
Status::no(Some(command.tag), None, "Please use AUTHENTICATE").unwrap(),
);
}
_ => {}
}
}
}
imap-next-0.3.1/examples/server_idle.rs 0000664 0000000 0000000 00000010407 14724135353 0020115 0 ustar 00root root 0000000 0000000 use std::{io::BufRead, num::NonZeroU32};
use imap_next::{
imap_types::{
core::Text,
response::{
CommandContinuationRequest, Data, Greeting, Status, StatusBody, StatusKind, Tagged,
},
},
server::{Event, Options, Server},
stream::Stream,
};
use tokio::{net::TcpListener, sync::mpsc::Receiver};
#[tokio::main(flavor = "current_thread")]
async fn main() {
let listener = TcpListener::bind("127.0.0.1:12345").await.unwrap();
let (stream, _) = listener.accept().await.unwrap();
let mut stream = Stream::insecure(stream);
let mut server = Server::new(
Options::default(),
Greeting::ok(None, "server_idle (example)").unwrap(),
);
loop {
match stream.next(&mut server).await.unwrap() {
Event::GreetingSent { .. } => break,
event => println!("unexpected event: {event:?}"),
}
}
println!("Please enter 'ok', 'no', or 'expunge'");
let mut lines = Lines::new();
let mut current_idle_tag = None;
loop {
tokio::select! {
event = stream.next(&mut server) => {
match event.unwrap() {
Event::IdleCommandReceived { tag } => {
println!("IDLE received");
current_idle_tag = Some(tag);
},
Event::IdleDoneReceived => {
println!("IDLE DONE received");
if let Some(tag) = current_idle_tag.take() {
let status = Status::Tagged(Tagged {
tag,
body: StatusBody {
kind: StatusKind::Ok,
code: None,
text: Text::try_from("...").unwrap()
},
});
server.enqueue_status(status);
}
},
event => {
println!("Event received: {event:?}");
}
}
}
line = lines.next() => {
match line.as_ref() {
"ok" => {
let cont = CommandContinuationRequest::basic(None, "...").unwrap();
if server.idle_accept(cont).is_ok() {
println!("IDLE accepted");
} else {
println!("IDLE can't be accepted now");
}
}
"no" => {
let Some(tag) = current_idle_tag.clone() else {
println!("IDLE can't be rejected now");
continue;
};
let status = Status::Tagged(Tagged {
tag,
body: StatusBody {
kind: StatusKind::No,
code: None,
text: Text::try_from("...").unwrap()
},
});
if server.idle_reject(status).is_ok() {
println!("IDLE rejected");
} else {
println!("IDLE can't be rejected now");
}
}
"expunge" => {
let data = Data::Expunge(NonZeroU32::new(1).unwrap());
server.enqueue_data(data);
println!("Send EXPUNGE");
}
_ => println!("Please enter 'ok', 'no', or 'expunge'"),
}
}
}
}
}
struct Lines {
receiver: Receiver,
}
impl Lines {
pub fn new() -> Self {
let (sender, receiver) = tokio::sync::mpsc::channel(1);
tokio::task::spawn_blocking(move || loop {
for line in std::io::stdin().lock().lines() {
sender.blocking_send(line.unwrap()).unwrap();
}
});
Self { receiver }
}
pub async fn next(&mut self) -> String {
self.receiver.recv().await.unwrap()
}
}
imap-next-0.3.1/flake.lock 0000664 0000000 0000000 00000004065 14724135353 0015365 0 ustar 00root root 0000000 0000000 {
"nodes": {
"fenix": {
"inputs": {
"nixpkgs": [
"nixpkgs"
],
"rust-analyzer-src": "rust-analyzer-src"
},
"locked": {
"lastModified": 1715063087,
"narHash": "sha256-cktPkcCmJ2sR0V/FaWEuCWmKuGPbwoMltih/EfF0mXg=",
"owner": "nix-community",
"repo": "fenix",
"rev": "f8f16c1f2c83bea4e51e6522d988ec8bfcc8420e",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "fenix",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1714971268,
"narHash": "sha256-IKwMSwHj9+ec660l+I4tki/1NRoeGpyA2GdtdYpAgEw=",
"owner": "nixos",
"repo": "nixpkgs",
"rev": "27c13997bf450a01219899f5a83bd6ffbfc70d3c",
"type": "github"
},
"original": {
"owner": "nixos",
"ref": "nixos-23.11",
"repo": "nixpkgs",
"type": "github"
}
},
"root": {
"inputs": {
"fenix": "fenix",
"flake-compat": "flake-compat",
"nixpkgs": "nixpkgs"
}
},
"rust-analyzer-src": {
"flake": false,
"locked": {
"lastModified": 1714936835,
"narHash": "sha256-M+PpgfRMBfHo8Jb2ou/s3maAZbps0XnuHXQU9Hv9vL0=",
"owner": "rust-lang",
"repo": "rust-analyzer",
"rev": "c4618fe14d39992fbbb85c2d6cad028a232c13d2",
"type": "github"
},
"original": {
"owner": "rust-lang",
"ref": "nightly",
"repo": "rust-analyzer",
"type": "github"
}
}
},
"root": "root",
"version": 7
}
imap-next-0.3.1/flake.nix 0000664 0000000 0000000 00000002422 14724135353 0015226 0 ustar 00root root 0000000 0000000 {
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixos-23.11";
# The rustup equivalent for Nix.
fenix = {
url = "github:nix-community/fenix";
inputs.nixpkgs.follows = "nixpkgs";
};
# Allows non-flakes users to still be able to `nix-shell` based on
# `shell.nix` instead of this `flake.nix`.
flake-compat = {
url = "github:edolstra/flake-compat";
flake = false;
};
};
outputs = { self, nixpkgs, fenix, ... }:
let
inherit (nixpkgs) lib;
eachSupportedSystem = lib.genAttrs supportedSystems;
supportedSystems = [
"x86_64-linux"
"aarch64-linux"
"x86_64-darwin"
"aarch64-darwin"
];
mkDevShells = system:
let
pkgs = import nixpkgs { inherit system; };
# get the rust toolchain from the rustup
# `rust-toolchain.toml` configuration file
rust-toolchain = fenix.packages.${system}.fromToolchainFile {
file = ./rust-toolchain.toml;
sha256 = "opUgs6ckUQCyDxcB9Wy51pqhd0MPGHUVbwRKKPGiwZU=";
};
in
{
default = pkgs.mkShell {
buildInputs = [ rust-toolchain ];
};
};
in
{
devShells = eachSupportedSystem mkDevShells;
};
}
imap-next-0.3.1/integration-test/ 0000775 0000000 0000000 00000000000 14724135353 0016724 5 ustar 00root root 0000000 0000000 imap-next-0.3.1/integration-test/Cargo.toml 0000664 0000000 0000000 00000000747 14724135353 0020664 0 ustar 00root root 0000000 0000000 [package]
name = "integration-test"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
publish = false
[dependencies]
bstr = { version = "1.11.0", default-features = false }
bytes = "1.8.0"
imap-codec = { version = "2.0.0-alpha.4" }
imap-next = { path = ".." }
tokio = { version = "1.41.1", features = ["macros", "net", "rt", "time"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
# Fix minimal versions
lazy_static = "1.5.0"
imap-next-0.3.1/integration-test/README.md 0000664 0000000 0000000 00000000134 14724135353 0020201 0 ustar 00root root 0000000 0000000 # integration-test
Test harness for writing lightweight integration tests for `imap-next`.
imap-next-0.3.1/integration-test/src/ 0000775 0000000 0000000 00000000000 14724135353 0017513 5 ustar 00root root 0000000 0000000 imap-next-0.3.1/integration-test/src/client_tester.rs 0000664 0000000 0000000 00000033024 14724135353 0022727 0 ustar 00root root 0000000 0000000 use std::net::SocketAddr;
use bstr::ByteSlice;
use imap_next::{
client::{self, Client, CommandHandle},
imap_types::{command::Command, ToStatic},
stream::{self, Stream},
};
use tokio::net::TcpStream;
use tracing::trace;
use crate::codecs::Codecs;
/// A wrapper for `ClientFlow` suitable for testing.
pub struct ClientTester {
codecs: Codecs,
connection_state: ConnectionState,
}
impl ClientTester {
pub async fn new(
codecs: Codecs,
client_options: client::Options,
server_address: SocketAddr,
) -> Self {
let stream = TcpStream::connect(server_address).await.unwrap();
trace!(?server_address, "Client is connected");
let stream = Stream::insecure(stream);
let client = Client::new(client_options);
Self {
codecs,
connection_state: ConnectionState::Connected { stream, client },
}
}
pub async fn receive_greeting(&mut self, expected_bytes: &[u8]) {
let expected_greeting = self.codecs.decode_greeting(expected_bytes);
let (stream, client) = self.connection_state.connected();
let event = stream.next(client).await.unwrap();
match event {
client::Event::GreetingReceived { greeting } => {
assert_eq!(expected_greeting, greeting);
}
event => panic!("Client emitted unexpected event: {event:?}"),
}
}
pub fn enqueue_command(&mut self, bytes: &[u8]) -> EnqueuedCommand {
let command = self.codecs.decode_command_normalized(bytes).to_static();
let (_, client) = self.connection_state.connected();
let handle = client.enqueue_command(command.to_static());
EnqueuedCommand { command, handle }
}
pub fn set_idle_done(&mut self, idle_handle: CommandHandle) {
let (_, client) = self.connection_state.connected();
let Some(handle) = client.set_idle_done() else {
panic!("Client is in unexpected state");
};
assert_eq!(idle_handle, handle);
}
pub fn set_authenticate_data(&mut self, authenticate_handle: CommandHandle, bytes: &[u8]) {
let authenticate_data = self
.codecs
.decode_authenticate_data_normalized(bytes)
.to_static();
let (_, client) = self.connection_state.connected();
let Ok(handle) = client.set_authenticate_data(authenticate_data.to_static()) else {
panic!("Client is in unexpected state");
};
assert_eq!(authenticate_handle, handle);
}
pub async fn progress_command(&mut self, enqueued_command: EnqueuedCommand) {
let (stream, client) = self.connection_state.connected();
let event = stream.next(client).await.unwrap();
match event {
client::Event::CommandSent { handle, command } => {
assert_eq!(enqueued_command.handle, handle);
assert_eq!(enqueued_command.command, command);
}
event => panic!("Client emitted unexpected event: {event:?}"),
}
}
pub async fn progress_idle(&mut self, idle_handle: CommandHandle) {
let (stream, client) = self.connection_state.connected();
let event = stream.next(client).await.unwrap();
match event {
client::Event::IdleCommandSent { handle } => {
assert_eq!(idle_handle, handle);
}
event => panic!("Client emitted unexpected event: {event:?}"),
}
}
pub async fn progress_idle_done(&mut self, idle_handle: CommandHandle) {
let (stream, client) = self.connection_state.connected();
let event = stream.next(client).await.unwrap();
match event {
client::Event::IdleDoneSent { handle } => {
assert_eq!(idle_handle, handle);
}
event => panic!("Client emitted unexpected event: {event:?}"),
}
}
pub async fn progress_authenticate(&mut self, authenticate_handle: CommandHandle) {
let (stream, client) = self.connection_state.connected();
let event = stream.next(client).await.unwrap();
match event {
client::Event::AuthenticateStarted { handle } => {
assert_eq!(authenticate_handle, handle);
}
event => panic!("Client emitted unexpected event: {event:?}"),
}
}
pub async fn progress_rejected_command(
&mut self,
enqueued_command: EnqueuedCommand,
status_bytes: &[u8],
) {
let expected_status = self.codecs.decode_status(status_bytes);
let (stream, client) = self.connection_state.connected();
let event = stream.next(client).await.unwrap();
match event {
client::Event::CommandRejected {
handle,
command,
status,
} => {
assert_eq!(enqueued_command.handle, handle);
assert_eq!(enqueued_command.command, command);
assert_eq!(expected_status, status);
}
event => panic!("Client emitted unexpected event: {event:?}"),
}
}
/// Progresses internal commands without expecting any results.
pub async fn progress_internal_commands(&mut self) -> T {
let (stream, client) = self.connection_state.connected();
let result = stream.next(client).await;
panic!("Client emitted unexpected result: {result:?}");
}
pub async fn send_command(&mut self, bytes: &[u8]) {
let enqueued_command = self.enqueue_command(bytes);
self.progress_command(enqueued_command).await;
}
pub async fn send_idle(&mut self, bytes: &[u8]) -> CommandHandle {
let enqueued_command = self.enqueue_command(bytes);
self.progress_idle(enqueued_command.handle).await;
enqueued_command.handle
}
pub async fn send_idle_done(&mut self, idle_handle: CommandHandle) {
self.set_idle_done(idle_handle);
self.progress_idle_done(idle_handle).await;
}
pub async fn send_authenticate(&mut self, bytes: &[u8]) -> CommandHandle {
let enqueued_command = self.enqueue_command(bytes);
self.progress_authenticate(enqueued_command.handle).await;
enqueued_command.handle
}
pub async fn send_rejected_command(&mut self, command_bytes: &[u8], status_bytes: &[u8]) {
let enqueued_command = self.enqueue_command(command_bytes);
self.progress_rejected_command(enqueued_command, status_bytes)
.await;
}
pub async fn receive_data(&mut self, expected_bytes: &[u8]) {
let expected_data = self.codecs.decode_data(expected_bytes);
let (stream, client) = self.connection_state.connected();
let event = stream.next(client).await.unwrap();
match event {
client::Event::DataReceived { data } => {
assert_eq!(expected_data, data);
}
event => panic!("Client emitted unexpected event: {event:?}"),
}
}
pub async fn receive_status(&mut self, expected_bytes: &[u8]) {
let expected_status = self.codecs.decode_status(expected_bytes);
let (stream, client) = self.connection_state.connected();
let event = stream.next(client).await.unwrap();
match event {
client::Event::StatusReceived { status } => {
assert_eq!(expected_status, status);
}
event => panic!("Client emitted unexpected event: {event:?}"),
}
}
pub async fn receive_idle_accepted(
&mut self,
idle_handle: CommandHandle,
expected_bytes: &[u8],
) {
let expected_continuation_request = self.codecs.decode_continuation_request(expected_bytes);
let (stream, client) = self.connection_state.connected();
let event = stream.next(client).await.unwrap();
match event {
client::Event::IdleAccepted {
handle,
continuation_request,
} => {
assert_eq!(handle, idle_handle);
assert_eq!(expected_continuation_request, continuation_request);
}
event => panic!("Client emitted unexpected event: {event:?}"),
}
}
pub async fn receive_idle_rejected(
&mut self,
idle_handle: CommandHandle,
expected_bytes: &[u8],
) {
let expected_status = self.codecs.decode_status(expected_bytes);
let (stream, client) = self.connection_state.connected();
let event = stream.next(client).await.unwrap();
match event {
client::Event::IdleRejected { handle, status } => {
assert_eq!(handle, idle_handle);
assert_eq!(status, expected_status);
}
event => panic!("Client emitted unexpected event: {event:?}"),
}
}
pub async fn receive_authenticate_continuation_request(
&mut self,
authenticate_request: CommandHandle,
expected_bytes: &[u8],
) {
let expected_continuation_request = self.codecs.decode_continuation_request(expected_bytes);
let (stream, client) = self.connection_state.connected();
let event = stream.next(client).await.unwrap();
match event {
client::Event::AuthenticateContinuationRequestReceived {
handle,
continuation_request,
} => {
assert_eq!(handle, authenticate_request);
assert_eq!(expected_continuation_request, continuation_request);
}
event => panic!("Client emitted unexpected event: {event:?}"),
}
}
pub async fn receive_authenticate_status(
&mut self,
authenticate_request: CommandHandle,
expected_authenticate_bytes: &[u8],
expected_status_bytes: &[u8],
) {
let expected_command = self
.codecs
.decode_command_normalized(expected_authenticate_bytes);
let expected_status = self.codecs.decode_status_normalized(expected_status_bytes);
let (stream, client) = self.connection_state.connected();
let event = stream.next(client).await.unwrap();
match event {
client::Event::AuthenticateStatusReceived {
handle,
command_authenticate,
status,
} => {
assert_eq!(handle, authenticate_request);
assert_eq!(expected_command, command_authenticate.into());
assert_eq!(expected_status, status);
}
event => panic!("Client emitted unexpected event: {event:?}"),
}
}
async fn receive_error(&mut self) -> stream::Error {
let result = match &mut self.connection_state {
ConnectionState::Connected { stream, client } => stream.next(client).await,
ConnectionState::Disconnected => panic!("Client is already disconnected"),
};
match result {
Ok(event) => panic!("Client emitted unexpected event: {event:?}"),
Err(err) => err,
}
}
pub async fn receive_error_because_expected_crlf_got_lf(&mut self, expected_bytes: &[u8]) {
let error = self.receive_error().await;
match error {
stream::Error::State(client::Error::ExpectedCrlfGotLf { discarded_bytes }) => {
assert_eq!(
expected_bytes.as_bstr(),
discarded_bytes.declassify().as_bstr()
);
}
error => panic!("Client emitted unexpected error: {error:?}"),
}
}
pub async fn receive_error_because_malformed_message(&mut self, expected_bytes: &[u8]) {
let error = self.receive_error().await;
match error {
stream::Error::State(client::Error::MalformedMessage { discarded_bytes }) => {
assert_eq!(
expected_bytes.as_bstr(),
discarded_bytes.declassify().as_bstr()
);
}
error => panic!("Client emitted unexpected error: {error:?}"),
}
}
pub async fn receive_error_because_response_too_long(&mut self, expected_bytes: &[u8]) {
let error = self.receive_error().await;
match error {
stream::Error::State(client::Error::ResponseTooLong { discarded_bytes }) => {
assert_eq!(
expected_bytes.as_bstr(),
discarded_bytes.declassify().as_bstr()
);
}
error => panic!("Client emitted unexpected error: {error:?}"),
}
}
pub async fn receive_error_because_stream_closed(&mut self) {
let error = self.receive_error().await;
match error {
stream::Error::Closed => (),
error => panic!("Client emitted unexpected error: {error:?}"),
}
}
}
/// Connection state between client and server.
#[allow(clippy::large_enum_variant)]
enum ConnectionState {
/// Connection to server established.
Connected { stream: Stream, client: Client },
/// Connection dropped.
Disconnected,
}
impl ConnectionState {
fn connected(&mut self) -> (&mut Stream, &mut Client) {
match self {
ConnectionState::Connected { stream, client } => (stream, client),
ConnectionState::Disconnected => panic!("Client is already disconnected"),
}
}
#[allow(unused)]
fn take(&mut self) -> ConnectionState {
std::mem::replace(self, ConnectionState::Disconnected)
}
}
/// Enqueued command that can be used for assertions.
pub struct EnqueuedCommand {
handle: CommandHandle,
command: Command<'static>,
}
imap-next-0.3.1/integration-test/src/codecs.rs 0000664 0000000 0000000 00000020045 14724135353 0021322 0 ustar 00root root 0000000 0000000 use bstr::ByteSlice;
use imap_codec::{
decode::Decoder, encode::Encoder, AuthenticateDataCodec, CommandCodec, GreetingCodec,
ResponseCodec,
};
use imap_next::imap_types::{
auth::AuthenticateData,
command::Command,
response::{CommandContinuationRequest, Data, Greeting, Response, Status},
};
/// Contains all codecs from `imap-codec`.
#[derive(Clone, Debug, Default, PartialEq)]
#[non_exhaustive]
pub struct Codecs {
pub greeting_codec: GreetingCodec,
pub command_codec: CommandCodec,
pub response_codec: ResponseCodec,
pub authenticate_data_codec: AuthenticateDataCodec,
}
impl Codecs {
pub fn encode_greeting(&self, greeting: &Greeting) -> Vec {
self.greeting_codec.encode(greeting).dump()
}
pub fn encode_command(&self, command: &Command) -> Vec {
self.command_codec.encode(command).dump()
}
pub fn encode_response(&self, response: &Response) -> Vec {
self.response_codec.encode(response).dump()
}
pub fn encode_continuation_request(
&self,
continuation_request: &CommandContinuationRequest,
) -> Vec {
self.response_codec
.encode(&Response::CommandContinuationRequest(
continuation_request.clone(),
))
.dump()
}
pub fn encode_data(&self, data: &Data) -> Vec {
self.response_codec
.encode(&Response::Data(data.clone()))
.dump()
}
pub fn encode_status(&self, status: &Status) -> Vec {
self.response_codec
.encode(&Response::Status(status.clone()))
.dump()
}
pub fn encode_authenticate_data(&self, authenticate_data: &AuthenticateData) -> Vec {
self.authenticate_data_codec
.encode(authenticate_data)
.dump()
}
pub fn decode_greeting<'a>(&self, bytes: &'a [u8]) -> Greeting<'a> {
match self.greeting_codec.decode(bytes) {
Ok((rem, greeting)) => {
if !rem.is_empty() {
panic!(
"Expected single greeting but there are remaining bytes {:?}",
rem.as_bstr()
)
}
greeting
}
Err(err) => {
panic!(
"Got error {:?} when parsing greeting from bytes {:?}",
err,
bytes.as_bstr()
)
}
}
}
pub fn decode_command<'a>(&self, bytes: &'a [u8]) -> Command<'a> {
match self.command_codec.decode(bytes) {
Ok((rem, command)) => {
if !rem.is_empty() {
panic!(
"Expected single command but there are remaining bytes {:?}",
rem.as_bstr()
)
}
command
}
Err(err) => {
panic!(
"Got error {:?} when parsing command from bytes {:?}",
err,
bytes.as_bstr()
)
}
}
}
pub fn decode_response<'a>(&self, bytes: &'a [u8]) -> Response<'a> {
match self.response_codec.decode(bytes) {
Ok((rem, response)) => {
if !rem.is_empty() {
panic!(
"Expected single response but there are remaining bytes {:?}",
rem.as_bstr()
)
}
response
}
Err(err) => {
panic!(
"Got error {:?} when parsing response bytes {:?}",
err,
bytes.as_bstr()
)
}
}
}
pub fn decode_continuation_request<'a>(
&self,
bytes: &'a [u8],
) -> CommandContinuationRequest<'a> {
let Response::CommandContinuationRequest(expected_data) = self.decode_response(bytes)
else {
panic!("Got wrong response type when parsing continuation request from {bytes:?}")
};
expected_data
}
pub fn decode_data<'a>(&self, bytes: &'a [u8]) -> Data<'a> {
let Response::Data(expected_data) = self.decode_response(bytes) else {
panic!("Got wrong response type when parsing data from {bytes:?}")
};
expected_data
}
pub fn decode_status<'a>(&self, bytes: &'a [u8]) -> Status<'a> {
let Response::Status(expected_status) = self.decode_response(bytes) else {
panic!("Got wrong response type when parsing status from {bytes:?}")
};
expected_status
}
pub fn decode_authenticate_data<'a>(&self, bytes: &'a [u8]) -> AuthenticateData<'a> {
match self.authenticate_data_codec.decode(bytes) {
Ok((rem, response)) => {
if !rem.is_empty() {
panic!(
"Expected single authenticate data but there are remaining bytes {:?}",
rem.as_bstr()
)
}
response
}
Err(err) => {
panic!(
"Got error {:?} when parsing authenticate data bytes {:?}",
err,
bytes.as_bstr()
)
}
}
}
pub fn decode_greeting_normalized<'a>(&self, bytes: &'a [u8]) -> Greeting<'a> {
let greeting = self.decode_greeting(bytes);
let normalized_bytes = self.encode_greeting(&greeting);
assert_eq!(
normalized_bytes.as_bstr(),
bytes.as_bstr(),
"Bytes must contain a normalized greeting"
);
greeting
}
pub fn decode_command_normalized<'a>(&self, bytes: &'a [u8]) -> Command<'a> {
let command = self.decode_command(bytes);
let normalized_bytes = self.encode_command(&command);
assert_eq!(
normalized_bytes.as_bstr(),
bytes.as_bstr(),
"Bytes must contain a normalized command"
);
command
}
pub fn decode_response_normalized<'a>(&self, bytes: &'a [u8]) -> Response<'a> {
let response = self.decode_response(bytes);
let normalized_bytes = self.encode_response(&response);
assert_eq!(
normalized_bytes.as_bstr(),
bytes.as_bstr(),
"Bytes must contain a normalized response"
);
response
}
pub fn decode_continuation_request_normalized<'a>(
&self,
bytes: &'a [u8],
) -> CommandContinuationRequest<'a> {
let continuation_request = self.decode_continuation_request(bytes);
let normalized_bytes = self.encode_continuation_request(&continuation_request);
assert_eq!(
normalized_bytes.as_bstr(),
bytes.as_bstr(),
"Bytes must contain a normalized continuation request"
);
continuation_request
}
pub fn decode_data_normalized<'a>(&self, bytes: &'a [u8]) -> Data<'a> {
let data = self.decode_data(bytes);
let normalized_bytes = self.encode_data(&data);
assert_eq!(
normalized_bytes.as_bstr(),
bytes.as_bstr(),
"Bytes must contain a normalized data"
);
data
}
pub fn decode_status_normalized<'a>(&self, bytes: &'a [u8]) -> Status<'a> {
let status = self.decode_status(bytes);
let normalized_bytes = self.encode_status(&status);
assert_eq!(
normalized_bytes.as_bstr(),
bytes.as_bstr(),
"Bytes must contain a normalized status"
);
status
}
pub fn decode_authenticate_data_normalized<'a>(&self, bytes: &'a [u8]) -> AuthenticateData<'a> {
let authenticate_data = self.decode_authenticate_data(bytes);
let normalized_bytes = self.encode_authenticate_data(&authenticate_data);
assert_eq!(
normalized_bytes.as_bstr(),
bytes.as_bstr(),
"Bytes must contain a normalized authenticate data"
);
authenticate_data
}
}
imap-next-0.3.1/integration-test/src/lib.rs 0000664 0000000 0000000 00000000161 14724135353 0020625 0 ustar 00root root 0000000 0000000 pub mod client_tester;
pub mod codecs;
pub mod mock;
pub mod runtime;
pub mod server_tester;
pub mod test_setup;
imap-next-0.3.1/integration-test/src/mock.rs 0000664 0000000 0000000 00000004414 14724135353 0021015 0 ustar 00root root 0000000 0000000 use std::net::SocketAddr;
use bstr::{BStr, ByteSlice};
use bytes::{Buf, BytesMut};
use tokio::{
io::{AsyncReadExt, AsyncWriteExt},
net::{TcpListener, TcpStream},
};
use tracing::trace;
/// Mocks either the server or client.
///
/// This mock doesn't know any IMAP semantics. Instead it provides direct access to the
/// TCP connection. Therefore the correctness of the test depends on the correctness
/// of the test data.
pub struct Mock {
role: Role,
stream: TcpStream,
read_buffer: BytesMut,
}
impl Mock {
pub async fn server(server_listener: TcpListener) -> Self {
let role = Role::Server;
let (stream, client_address) = server_listener.accept().await.unwrap();
trace!(?role, ?client_address, "Mock accepts connection");
Self {
role,
stream,
read_buffer: BytesMut::default(),
}
}
pub async fn client(server_address: SocketAddr) -> Self {
let role = Role::Client;
let stream = TcpStream::connect(server_address).await.unwrap();
trace!(?role, ?server_address, "Mock is connected");
Self {
role,
stream,
read_buffer: BytesMut::default(),
}
}
pub async fn send(&mut self, bytes: &[u8]) {
trace!(
role = ?self.role,
bytes = ?BStr::new(bytes),
"Mock writes bytes"
);
self.stream.write_all(bytes).await.unwrap();
}
pub async fn receive(&mut self, expected_bytes: &[u8]) {
loop {
let bytes = &self.read_buffer[..];
trace!(
role = ?self.role,
read_bytes = ?BStr::new(bytes),
"Mock reads bytes"
);
if bytes.len() < expected_bytes.len() {
assert_eq!(expected_bytes[..bytes.len()].as_bstr(), bytes.as_bstr());
self.stream.read_buf(&mut self.read_buffer).await.unwrap();
} else {
assert_eq!(
expected_bytes.as_bstr(),
bytes[..expected_bytes.len()].as_bstr()
);
self.read_buffer.advance(expected_bytes.len());
break;
}
}
}
}
#[derive(Debug)]
enum Role {
Server,
Client,
}
imap-next-0.3.1/integration-test/src/runtime.rs 0000664 0000000 0000000 00000003741 14724135353 0021551 0 ustar 00root root 0000000 0000000 use std::{future::Future, time::Duration};
use tokio::{join, runtime, select, time::sleep};
/// Options for creating an instance of `Runtime`.
#[derive(Clone, Debug, PartialEq)]
#[non_exhaustive]
pub struct RuntimeOptions {
pub timeout: Option,
}
impl Default for RuntimeOptions {
fn default() -> Self {
Self {
timeout: Some(Duration::from_secs(1)),
}
}
}
/// Allows to execute one or more `Future`s by blocking the current thread.
///
/// We prefer to have single-threaded unit tests because it makes debugging easier.
/// This runtime allows us to execute server and client tasks in parallel on the same
/// thread the test is executed on.
pub struct Runtime {
timeout: Option,
rt: runtime::Runtime,
}
impl Runtime {
pub fn new(runtime_options: RuntimeOptions) -> Self {
let rt = runtime::Builder::new_current_thread()
.enable_time()
.enable_io()
.build()
.unwrap();
Runtime {
timeout: runtime_options.timeout,
rt,
}
}
pub fn run(&self, future: impl Future