chatmail / chatmail/async-imap
Capabilities::has_str is case-sensitive for non-IMAP4rev1 capability atoms
- Dominant language
- Rust
- Stars
- 147
- Forks
- 42
- Avg merge
- 8d 15h
- Merged PRs (30d)
- 1
Description
## Problem
`Capabilities::has_str` in 0.11.3 (`src/types/capabilities.rs:63-75`) folds ASCII case for exactly two inputs — `IMAP4rev1` and the `AUTH=` prefix — then falls through to a `HashSet` lookup on `Capability::Atom(String)`, whose derived `Eq` is byte-exact:
```rust
self.has(&Capability::Atom(s.into())) // exact match — no case folding
```
RFC 3501 §9 defines a capability as an `atom`, and IMAP protocol keywords are case-insensitive (RFC 3501 §9, §2.6). A conformant server advertising `Move` or `uidplus` in mixed or lower case reads as absent when probed via `has_str`. Every `async-imap` consumer probing a non-`IMAP4rev1` capability by string inherits this.
## Concrete impact
A server advertising `uidplus` (lowercase) reads as advertising neither `UIDPLUS` nor `uidplus` via `has_str("UIDPLUS")`, because `Capability::Atom("uidplus")` ≠ `Capability::Atom("UIDPLUS")`.
In `rusty-imap-mcp`, this caused a data-loss path (issue #649): a server spelling `uidplus` in lowercase returned `Known { has_move: false, has_uidplus: false }`, which selected the folder-wide RFC 3501 `EXPUNGE` against a server that in fact supported `UID EXPUNGE` (issue #735 reached this by a capability spelling; issue #767 tracks the upstream report). We now route all capability probes through a private `eq_ignore_ascii_case` helper as a workaround.
## The fix
The simplest fix would be to perform case-insensitive comparison in `has_str` for all `Capability::Atom` arms, not just for `IMAP4rev1` and `AUTH=`:
```rust
Capability::Atom(ref a) => a.eq_ignore_ascii_case(s),
```
An alternative is to normalize all `Capability::Atom` strings to uppercase when parsing, so the `HashSet` lookup works correctly. Either approach makes `has_str` RFC-compliant.
## Related
A related conformance gap is `imap-proto`'s `ensure_capabilities_contains_imap4rev` (called from `capability_data`), which rejects any `CAPABILITY` response that lacks the `IMAP4rev1` atom, making an RFC 9051-only server (`IMAP4rev2` without `IMAP4rev1`) permanently unreachable. Both are async-imap-stack conformance issues.
## Version
async-imap 0.11.3
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in src/types/capabilities.rs:63-75 and inspect how has_str handles IMAP4rev1, AUTH=, and Capability::Atom values. Make non-IMAP4rev1 atom checks ASCII case-insensitive, then verify that mixed- or lowercase capabilities such as Move and uidplus are recognized through has_str.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- networking
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 76/100