Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source
Page
Library
Module
Module type
Parameter
Class
Class type
Source
path.ml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184
type t = { local : [ `String of string | `Dot_string of string list ]; domain : Domain.t; rest : Domain.t list; } type mailbox = [ `String of string | `Dot_string of string list ] * Domain.t let equal_local a b = match (a, b) with | (`String a | `Dot_string [ a ]), (`String b | `Dot_string [ b ]) -> String.(equal (lowercase_ascii a) (lowercase_ascii b)) | `Dot_string a, `Dot_string b -> ( try List.for_all2 (fun a b -> String.(equal (lowercase_ascii a) (lowercase_ascii b))) a b with _ -> false) | _, _ -> false let equal a b = equal_local a.local b.local && Domain.equal a.domain b.domain && try List.for_all2 Domain.equal a.rest b.rest with _ -> false let compare_domains a b = let inf = -1 and sup = 1 in let rec go a b = match (a, b) with | _ :: _, [] -> sup | [], _ :: _ -> inf | a :: ar, b :: br -> let res = Domain.compare a b in if res = 0 then go ar br else res | [], [] -> 0 in go (List.sort Domain.compare a) (List.sort Domain.compare b) let compare_local a b = let inf = -1 and sup = 1 in match (a, b) with | `Dot_string a, `Dot_string b -> let rec go a b = match (a, b) with | _ :: _, [] -> sup | [], _ :: _ -> inf | a :: ar, b :: br -> let res = String.compare a b in if res = 0 then go ar br else res | [], [] -> 0 in go a b | `Dot_string a, `String b -> let a = String.concat "." a in String.compare a b | `String a, `Dot_string b -> let b = String.concat "." b in String.compare a b | `String a, `String b -> String.compare a b let compare a b = let res = compare_domains (a.domain :: a.rest) (b.domain :: b.rest) in if res = 0 then compare_local a.local b.local else res let pp_local ppf = function | `String x -> Fmt.(quote string) ppf x | `Dot_string l -> Fmt.(list ~sep:(const string ".") string) ppf l let pp ppf { local; domain; rest } = match rest with | [] -> Fmt.pf ppf "<%a@%a>" pp_local local Domain.pp domain | rest -> Fmt.pf ppf "<%a:%a@%a>" Fmt.(list ~sep:(const string ",") (prefix (const string "@") Domain.pp)) rest pp_local local Domain.pp domain module Decoder = struct open Angstrom let at_domain = char '@' *> Domain.Decoder.domain let a_d_l = at_domain >>= fun x -> many (char ',' *> at_domain) >>| fun r -> x :: r let is_atext = function | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '!' | '#' | '$' | '%' | '&' | '\'' | '*' | '+' | '-' | '/' | '=' | '?' | '^' | '_' | '`' | '{' | '}' | '|' | '~' -> true | _ -> false let is_qtextSMTP = function | '\032' | '\033' | '\035' .. '\091' | '\093' .. '\126' -> true | _ -> false let atom = take_while1 is_atext let dot_string = atom >>= fun x -> many (char '.' *> atom) >>| fun r -> `Dot_string (x :: r) let quoted_pairSMTP = char '\\' *> satisfy (function '\032' .. '\126' -> true | _ -> false) >>| String.make 1 let qcontentSMTP = quoted_pairSMTP <|> take_while1 is_qtextSMTP let quoted_string = char '"' *> many qcontentSMTP <* char '"' >>| String.concat "" >>| fun x -> `String x let local_part = dot_string <|> quoted_string let mailbox = local_part >>= fun local -> char '@' *> (Domain.Decoder.domain <|> Domain.Decoder.address_literal) >>| fun domain -> (local, domain) let path = char '<' *> option [] (a_d_l <* char ':') >>= fun rest -> mailbox <* char '>' >>= fun (local, domain) -> return { local; domain; rest } end let error_msgf fmt = Fmt.kstrf (fun err -> Error (`Msg err)) fmt let of_string str = match Angstrom.parse_string ~consume:All Decoder.path str with | Ok v -> Ok v | Error _ -> error_msgf "Invalid path: %S" str let of_string_exn str = match of_string str with Ok v -> v | Error (`Msg err) -> invalid_arg err module Encoder = struct let need_to_escape, escape_char = (* See [Mrmime.Rfc822.of_escaped_character] but totally arbitrary. *) let bindings = [ ('\000', '\000'); ('\\', '\\'); ('\x07', 'a'); ('\b', 'b'); ('\t', 't'); ('\n', 'n'); ('\x0b', 'v'); ('\x0c', 'f'); ('\r', 'r'); ('"', '"'); ] in ( (fun chr -> List.mem_assoc chr bindings), fun chr -> List.assoc chr bindings ) let escape x = let len = String.length x in let res = Buffer.create (len * 2) in let pos = ref 0 in while !pos < len do if need_to_escape x.[!pos] then ( Buffer.add_char res '\\' ; Buffer.add_char res (escape_char x.[!pos])) else Buffer.add_char res x.[!pos] ; incr pos done ; Buffer.contents res let local_to_string = function | `String x -> Fmt.strf "%a" Fmt.(using escape string) x | `Dot_string l -> Fmt.strf "%a" Fmt.(list ~sep:(const string ".") string) l let to_string x = match x.rest with | [] -> Fmt.strf "<%s@%s>" (local_to_string x.local) (Domain.to_string x.domain) | rest -> Fmt.strf "<%a:%s@%s>" Fmt.( list ~sep:(const string ",") (prefix (const string "@") (using Domain.to_string string))) rest (local_to_string x.local) (Domain.to_string x.domain) end