IMAP in OCaml
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

imap: fix RECENT response parsing that was overwriting EXISTS count

The parser had a fallback that returned Response.Exists 0 for any
unrecognized `* <number> <keyword>` response. This caused `* 0 RECENT`
to overwrite the real EXISTS count.

Changes:
- Add Response.Recent to handle `* <n> RECENT` responses
- Add Response.Unknown for unrecognized responses instead of returning
a fake Exists 0
- Update client to populate mailbox_info.recent field

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+13 -2
+1
lib/imap/client.ml
··· 318 318 List.iter 319 319 (function 320 320 | Response.Exists n -> info := { !info with exists = n } 321 + | Response.Recent n -> info := { !info with recent = n } 321 322 | Response.Flags flags -> info := { !info with flags } 322 323 | Response.Ok { code = Some (Code.Uidvalidity v); _ } -> 323 324 info := { !info with uidvalidity = v }
+5 -2
lib/imap/read.ml
··· 418 418 | "EXISTS" -> 419 419 crlf r; 420 420 Response.Exists n 421 + | "RECENT" -> 422 + crlf r; 423 + Response.Recent n 421 424 | "EXPUNGE" -> 422 425 crlf r; 423 426 Response.Expunge n ··· 427 430 crlf r; 428 431 Response.Fetch { seq = n; items } 429 432 | _ -> 430 - let _ = rest_of_line r in 431 - Response.Exists 0) 433 + let rest = rest_of_line r in 434 + Response.Unknown { num = Some n; keyword; rest }) 432 435 | _ -> 433 436 let keyword = atom r in 434 437 (match String.uppercase_ascii keyword with
+5
lib/imap/response.ml
··· 45 45 | Esearch of { tag : string option; uid : bool; results : esearch_result list } 46 46 | Flags of Flag.t list 47 47 | Exists of int 48 + | Recent of int 48 49 | Expunge of int 49 50 | Fetch of { seq : int; items : Fetch.response list } 50 51 | Continuation of string option 51 52 | Id of (string * string) list option 53 + | Unknown of { num : int option; keyword : string; rest : string } 52 54 53 55 let pp ppf = function 54 56 | Ok { tag; code; text } -> ··· 79 81 | Esearch _ -> Fmt.string ppf "* ESEARCH ..." 80 82 | Flags flags -> Fmt.pf ppf "* FLAGS (%a)" Fmt.(list ~sep:sp Flag.pp) flags 81 83 | Exists n -> Fmt.pf ppf "* %d EXISTS" n 84 + | Recent n -> Fmt.pf ppf "* %d RECENT" n 82 85 | Expunge n -> Fmt.pf ppf "* %d EXPUNGE" n 83 86 | Fetch { seq; _ } -> Fmt.pf ppf "* %d FETCH (...)" seq 84 87 | Continuation text -> Fmt.pf ppf "+ %a" Fmt.(option string) text 85 88 | Id _ -> Fmt.string ppf "* ID ..." 89 + | Unknown { num; keyword; rest } -> 90 + Fmt.pf ppf "* %a%s%s" Fmt.(option (int ++ sp)) num keyword rest 86 91 87 92 let to_string r = Fmt.str "%a" pp r
+2
lib/imap/response.mli
··· 45 45 | Esearch of { tag : string option; uid : bool; results : esearch_result list } 46 46 | Flags of Flag.t list 47 47 | Exists of int 48 + | Recent of int 48 49 | Expunge of int 49 50 | Fetch of { seq : int; items : Fetch.response list } 50 51 | Continuation of string option 51 52 | Id of (string * string) list option 53 + | Unknown of { num : int option; keyword : string; rest : string } 52 54 53 55 val pp : Format.formatter -> t -> unit 54 56 val to_string : t -> string