Skip to content

Case-insensitive IMAP response parsing (RFC 3501)#116

Draft
mpscholten wants to merge 2 commits into
qnikst:masterfrom
mpscholten:codex/imap-case-insensitive
Draft

Case-insensitive IMAP response parsing (RFC 3501)#116
mpscholten wants to merge 2 commits into
qnikst:masterfrom
mpscholten:codex/imap-case-insensitive

Conversation

@mpscholten

Copy link
Copy Markdown
Contributor

Stacked on #115 (UIDPLUS). This branch is based on the UIDPLUS branch, so until #115 merges the diff here also shows those commits — the change that belongs to this PR is the single commit "Make IMAP response parsing case-insensitive (RFC 3501)". Please review/merge #115 first; I'll rebase this onto master afterwards so the diff narrows to just the parser change.

What

RFC 3501 keywords, response codes, flag names and status attributes are case-insensitive, but the response parser matched them with case-sensitive string. A server replying with non-canonical casing — a001 ok, * search …, [uidvalidity …], \seen — caused a hard parse error. Several real-world servers (Exchange/Office365 among them) don't use the exact canonical uppercase.

This adds stringCI / charCI and applies them throughout the parser.

While restructuring, also fixed

  • factor the repeated "parse untagged lines then the tagged line" pattern into pWithTaggedOrFatal
  • split pDone into pRespCode / pRespText / pStatusCode
  • unify ad-hoc string parsing into pQuotedString / pLiteralString / pAString / pMailboxName
  • surface an untagged * BYE as a fatal response (BAD) instead of failing to parse
  • accept an empty * SEARCH reply (zero matches)
  • accept a NIL hierarchy separator in LIST/LSUB
  • stop atomChar from running past CR/LF

Tests

New caseInsensitiveTest group: lowercase tagged/bracketed status codes, lowercase flags, untagged BYE, empty SEARCH, lowercase SEARCH keyword, NIL LIST separator. cabal test passes (GHC 9.8.4).

The suite has one pre-existing failure on master unrelated to this PR (append preserves raw crlf message bytes — expects unquoted APPEND INBOX); left untouched.

🤖 Generated with Claude Code

Adds UIDPLUS extension support so callers can recover the UIDs the
server assigns on APPEND/COPY and target expunges by UID:

  - appendFullUID: like appendFull, returns the APPENDUID response code
  - copyUID / copyUIDs / copyUIDR: UID COPY returning the COPYUID code
  - uidExpunge / uidExpungeR: UID EXPUNGE over a UID set or range

New types AppendUID and CopyUID, the UIDSet alias, and the
APPENDUID/COPYUID/UIDNOTSTICKY status codes with their parsers.
sendCommandWithResponse exposes the tagged ServerResponse so the
response codes can be read. The existing appendFull/copyFull keep their
old signatures by discarding the UID result.

Covered by new parser cases in baseTest and a dedicated imapUIDPlusTest
group exercising the API against scripted server responses.
RFC 3501 keywords, response codes, flag names and status attributes are
case-insensitive, but the parser matched them with case-sensitive
`string`, so any server replying with non-canonical casing (e.g. `ok`,
`* search`, `[uidvalidity ...]`, `\seen`) caused a parse error.

Adds `stringCI`/`charCI` and applies them throughout the response
parser. Along the way the tagged/fatal response handling is factored
into `pWithTaggedOrFatal`, `pDone` is split into
`pRespCode`/`pRespText`/`pStatusCode`, and string parsing is unified
(`pQuotedString`/`pLiteralString`/`pAString`/`pMailboxName`). This also:

  - surfaces an untagged `* BYE` as a fatal response instead of failing
  - accepts an empty `* SEARCH` reply (no matches)
  - accepts a `NIL` hierarchy separator in LIST/LSUB
  - stops `atomChar` from running past CR/LF

Covered by a new caseInsensitiveTest group.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant