(* Addresses indicate the senders and recipients of messages and * correspond to either an individual mailbox or a group of * mailboxes. *) type local_part = string type domain = string type addr_spec = local_part * domain option class mailbox ?(name : string option) (route : string list) (spec : addr_spec) = object method name = match name with Some s -> s | _ -> raise Not_found method route = route method spec = spec end class group (name : string) (mailboxes : mailbox list) = object method name = name method mailboxes = mailboxes end type t = [ `Mailbox of mailbox | `Group of group ] let mbox_addr_spec spec = `Mailbox (new mailbox [] spec) let mbox_route_addr personal (route, spec) = `Mailbox (new mailbox ?name:personal route spec) open Mimestring let rev = List.rev exception Parse_error of int * string let parse string = let scanner = create_mime_scanner ~specials:specials_rfc822 ~scan_options:[] string in (* manage lookahead token *) let lookahead_et, lookahead = let et, t = Mimestring.scan_token scanner in ref et, ref t in let next () = let et, t = Mimestring.scan_token scanner in lookahead_et := et; lookahead := t in let peek () = !lookahead in (* parsing error - some kind of location/error recovery? *) let error s = let pos = Mimestring.get_pos !lookahead_et in raise (Parse_error (pos, s)) in (* parse a list of elements *) let list elem next acc = next (elem () :: acc) in (* match a special token for a character *) let special c = match peek () with | Special c' when c = c' -> next () | _ -> error (Printf.sprintf "expecting '%c'" c) in (* main entry point *) let rec address_list acc = match peek () with | End -> rev acc | _ -> list address next_address acc and next_address acc = match peek () with | End -> rev acc | Special ',' -> next (); address_list acc | _ -> error "expecting ','" (* RFC-1123 section 5.2.15: syntax definition of "mailbox" is changed to allow route address with no phrase *) and address () = match peek () with | (Atom _ | QString _) -> address1 () | Special '<' -> mbox_route_addr None (route_addr ()) | Special ',' -> next (); address () (* RFC 2822 section 4.4: support for "null" members *) | _ -> error "expecting address" and address1 () = let w0 = word () in match peek () with | Special '@' -> mbox_addr_spec (w0, Some (at_domain ())) | Special ('<'|':') -> address2 (w0) | Special '.' -> next (); mbox_addr_spec (addr_spec [w0]) | (Atom _ | QString _) -> address2 (phrase [w0]) | _ -> error "syntax error" and address2 name = match peek () with | Special '<' -> mbox_route_addr (Some name) (route_addr ()) | Special ':' -> next (); group name | _ -> error "expecting '<' or ':'" and group name = let mboxes = mailbox_list_opt () in special ';'; `Group (new group name mboxes) and mailbox_list_opt () = match peek () with | Special ';' -> [] | _ -> list mailbox next_mailbox [] and next_mailbox acc = match peek () with | Special ',' -> next (); list mailbox next_mailbox acc | _ -> rev acc (* reuse parsing code for address () and filter out group response *) and mailbox () = match address () with | `Mailbox m -> m | _ -> error "expecting mailbox" and route_addr () = special '<'; let x = match peek () with | (Atom _ | QString _) -> let spec = addr_spec [] in ([], spec) | Special '@' -> let r = route () in let spec = addr_spec [] in (r, spec) | _ -> error "expecting local part or route address" in special '>'; x and route () = let r = at_domain_list [] in special ':'; r and addr_spec acc = let lp = local_part acc in match peek () with | Special '@' -> (lp, Some (at_domain ())) | _ -> (lp, None) and local_part acc = list word next_local_part acc and next_local_part acc = match peek () with | Special '.' -> next (); local_part acc | _ -> String.concat "." (rev acc) and at_domain_list acc = list at_domain next_at_domain_list acc and next_at_domain_list acc = match peek () with | Special ',' -> next (); at_domain_list acc | _ -> rev acc and at_domain () = special '@'; domain [] and domain acc = list subdomain next_subdomain acc and next_subdomain acc = match peek () with | Special '.' -> next (); domain acc | _ -> String.concat "." (rev acc) and subdomain () = match peek () with | Atom s -> next (); s | DomainLiteral s -> next (); s | _ -> error "expecting atom or domain" and phrase acc = list word_or_dot next_phrase acc and next_phrase acc = match peek() with | (Atom _ | QString _ | Special '.') -> phrase acc | _ -> String.concat " " (rev acc) (* RFC 2822 section 4.1: support for '.' often used for initials in names *) and word_or_dot () = match peek () with | Atom s -> next (); s | QString s -> next (); s | Special '.' -> next (); "." | _ -> error "expecting atom or quoted-string" and word () = match peek () with | Atom s -> next (); s | QString s -> next (); s | _ -> error "expecting atom or quoted-string" in address_list []