diff --git a/.ocamlformat b/.ocamlformat index 0965d3e..4dbd358 100644 --- a/.ocamlformat +++ b/.ocamlformat @@ -1,4 +1,4 @@ -version=0.17.0 +version=0.18.0 module-item-spacing=compact break-struct=natural break-infix=fit-or-vertical diff --git a/examples/attachment.ml b/examples/attachment.ml index b2a9b45..f713c28 100644 --- a/examples/attachment.ml +++ b/examples/attachment.ml @@ -2,10 +2,11 @@ open Mrmime let romain_calascibetta = let open Mailbox in - Local.[ w "romain"; w "calascibetta" ] @ Domain.(domain, [ a "gmail"; a "com" ]) + Local.[ w "romain"; w "calascibetta" ] + @ Domain.(domain, [ a "gmail"; a "com" ]) let date = - let now = Ptime_clock.now () in + let now = Option.get (Ptime.of_float_s 1619454050.0) in Date.of_ptime ~zone:Date.Zone.GMT now let subject = @@ -14,40 +15,56 @@ let subject = let content_disposition = Field_name.v "Content-Disposition" -let content_type_1 = Content_type.(make `Text (Subtype.v `Text "plain") Parameters.empty) -let content_type_2 = Content_type.(make `Image (Subtype.v `Image "png") Parameters.empty) +let content_type_1 = + Content_type.(make `Text (Subtype.v `Text "plain") Parameters.empty) + +let content_type_2 = + Content_type.(make `Image (Subtype.v `Image "png") Parameters.empty) let stream_of_file filename : Mt.buffer Mt.stream = let tp = Bytes.create 0x1000 in let ic = open_in filename in - fun () -> match input ic tp 0 0x1000 with - | 0 -> close_in ic ; None - | len -> Some (Bytes.unsafe_to_string tp, 0, len) + fun () -> + match input ic tp 0 0x1000 with + | 0 -> + close_in ic; + None + | len -> Some (Bytes.unsafe_to_string tp, 0, len) let stream_of_string str : Mt.buffer Mt.stream = let consumed = ref false in - fun () -> match !consumed with - | true -> None - | false -> consumed := true ; Some (str, 0, String.length str) + fun () -> + match !consumed with + | true -> None + | false -> + consumed := true; + Some (str, 0, String.length str) let part0 = let header = let open Header in empty |> add Field_name.content_type Field.(Content, content_type_1) - |> add Field_name.content_encoding Field.(Encoding, `Quoted_printable) in + |> add Field_name.content_encoding Field.(Encoding, `Quoted_printable) + in Mt.part ~header (stream_of_string "Hello World!") let part1 = - let header = + let header = let open Header in empty |> add Field_name.content_type Field.(Content, content_type_2) |> add Field_name.content_encoding Field.(Encoding, `Base64) - |> add content_disposition Field.(Unstructured, - Unstructured.Craft.(compile - [ v "attachement"; sp 0; v ";"; sp 1; - v "filename"; sp 0; v "="; sp 0; v "mrmime.png" ])) in + |> add content_disposition + Field. + ( Unstructured, + Unstructured.Craft.( + compile + [ + v "attachement"; sp 0; v ";"; sp 1; v "filename"; sp 0; + v "="; sp 0; v "mrmime.png"; + ]) ) + in Mt.part ~header (stream_of_file "mrmime.png") let header = @@ -56,7 +73,8 @@ let header = |> add Field_name.date Field.(Date, date) |> add Field_name.subject Field.(Unstructured, subject) |> add Field_name.from Field.(Mailboxes, [ romain_calascibetta ]) - |> add (Field_name.v "To") Field.(Addresses, Address.[ mailbox romain_calascibetta ]) + |> add (Field_name.v "To") + Field.(Addresses, Address.[ mailbox romain_calascibetta ]) let rng ?g:_ len = let res = Bytes.create len in @@ -65,15 +83,21 @@ let rng ?g:_ len = | n when n < 26 -> Bytes.set res i (Char.chr (Char.code 'a' + n)) | n when n < 52 -> Bytes.set res i (Char.chr (Char.code 'A' + (n - 26))) | n -> Bytes.set res i (Char.chr (Char.code '0' + (n - 26 - 26))) - done ; Bytes.unsafe_to_string res + done; + Bytes.unsafe_to_string res let email = Mt.make header Mt.multi (Mt.multipart ~rng [ part0; part1 ]) + let email = let stream = Mt.to_stream email in let buffer = Buffer.create 0x1000 in - let rec go () = match stream () with - | Some (str, off, len) -> Buffer.add_substring buffer str off len ; go () - | None -> Buffer.contents buffer in + let rec go () = + match stream () with + | Some (str, off, len) -> + Buffer.add_substring buffer str off len; + go () + | None -> Buffer.contents buffer + in go () let () = print_string email diff --git a/examples/test.t b/examples/test.t index a4e7576..dca929a 100644 --- a/examples/test.t +++ b/examples/test.t @@ -3,7 +3,7 @@ Simple email with attachment To: romain.calascibetta@gmail.com From: romain.calascibetta@gmail.com Subject: A Simple Email - Date: Mon, 26 Apr 2021 13:58:05 GMT + Date: Mon, 26 Apr 2021 16:20:50 GMT Content-Type: multipart/mixed; boundary=YlGxbWQC --YlGxbWQC diff --git a/fuzz/common.ml b/fuzz/common.ml index 0266e9c..97a1dd0 100644 --- a/fuzz/common.ml +++ b/fuzz/common.ml @@ -1,6 +1,6 @@ open Crowbar -let (<.>) f g = fun x -> f (g x) +let ( <.> ) f g x = f (g x) let char_from_alphabet alphabet = map [ range (String.length alphabet) ] (String.make 1 <.> String.get alphabet) @@ -8,7 +8,8 @@ let char_from_alphabet alphabet = let string_from_alphabet alphabet len = let rec go acc = function | 0 -> concat_gen_list (const "") acc - | n -> go (char_from_alphabet alphabet :: acc) (pred n) in + | n -> go (char_from_alphabet alphabet :: acc) (pred n) + in go [] len let alphabet_from_predicate predicate = @@ -16,18 +17,24 @@ let alphabet_from_predicate predicate = let rec go acc = function | 0 -> if predicate (Char.unsafe_chr 0) then acc + 1 else acc | n -> - let acc = if predicate (Char.unsafe_chr n) then acc + 1 else acc in - go acc (n - 1) in - go 0 255 in + let acc = if predicate (Char.unsafe_chr n) then acc + 1 else acc in + go acc (n - 1) + in + go 0 255 + in let res = Bytes.create len in let rec go idx = function | 0 -> - if predicate (Char.unsafe_chr 0) then Bytes.unsafe_set res idx (Char.unsafe_chr 0) + if predicate (Char.unsafe_chr 0) then + Bytes.unsafe_set res idx (Char.unsafe_chr 0) | n -> - if predicate (Char.unsafe_chr n) then Bytes.unsafe_set res idx (Char.unsafe_chr n) ; - let idx = if predicate (Char.unsafe_chr n) then idx + 1 else idx in - go idx (n - 1) in - go 0 255 ; Bytes.unsafe_to_string res + if predicate (Char.unsafe_chr n) then + Bytes.unsafe_set res idx (Char.unsafe_chr n); + let idx = if predicate (Char.unsafe_chr n) then idx + 1 else idx in + go idx (n - 1) + in + go 0 255; + Bytes.unsafe_to_string res let is_dcontent = function | '\033' .. '\090' | '\094' .. '\126' -> true @@ -37,8 +44,9 @@ let is_atext = function | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' - | '!' | '#' | '$' | '%' | '&' | '\'' | '*' | '+' | '-' | '/' | '=' | '?' - | '^' | '_' | '`' | '{' | '}' | '|' | '~' -> true + | '!' | '#' | '$' | '%' | '&' | '\'' | '*' | '+' | '-' | '/' | '=' | '?' | '^' + | '_' | '`' | '{' | '}' | '|' | '~' -> + true | _ -> false let is_obs_no_ws_ctl = function @@ -51,6 +59,15 @@ let is_dtext = function let atext = alphabet_from_predicate is_atext let dtext = alphabet_from_predicate is_dtext -let ldh_str = alphabet_from_predicate (function 'a' .. 'z' | 'A'.. 'Z' | '0' .. '9' | '-' -> true | _ -> false) -let let_dig = alphabet_from_predicate (function 'a' .. 'z' | 'A'.. 'Z' | '0' .. '9' -> true | _ -> false) + +let ldh_str = + alphabet_from_predicate (function + | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' | '-' -> true + | _ -> false) + +let let_dig = + alphabet_from_predicate (function + | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' -> true + | _ -> false) + let dcontent = alphabet_from_predicate is_dcontent diff --git a/fuzz/dune b/fuzz/dune index 77637e2..3d5866e 100644 --- a/fuzz/dune +++ b/fuzz/dune @@ -6,7 +6,7 @@ (executable (name fuzz_encoder) (modules fuzz_encoder) - (libraries mrmime.prettym common crowbar jsonm)) + (libraries prettym common crowbar jsonm)) (executable (name fuzz_mailbox) diff --git a/fuzz/fuzz_content_type.ml b/fuzz/fuzz_content_type.ml index 10752f3..5b9c074 100644 --- a/fuzz/fuzz_content_type.ml +++ b/fuzz/fuzz_content_type.ml @@ -5,70 +5,73 @@ open Common let token = alphabet_from_predicate Mrmime.Content_type.is_token let qtext = alphabet_from_predicate Mrmime.Content_type.is_qtext - let token = dynamic_bind (range ~min:1 32) (string_from_alphabet token) let value = - map [ token ] - (fun v -> match Mrmime.Content_type.Parameters.value v with - | Ok v -> v - | Error _ -> bad_test ()) + map [ token ] (fun v -> + match Mrmime.Content_type.Parameters.value v with + | Ok v -> v + | Error _ -> bad_test ()) (* XXX(dinosaure): IETF token does not exists - see [Rfc2045.ty] *) let x_token = - map [ choose [ const "x"; const "X" ] - ; token ] + map + [ choose [ const "x"; const "X" ]; token ] (fun head tail -> head ^ "-" ^ tail) let ty = choose - [ const `Text - ; const `Image - ; const `Audio - ; const `Video - ; const `Application - ; const `Message - ; const `Multipart - ; map [ x_token ] (fun v -> `X_token v) ] + [ + const `Text; const `Image; const `Audio; const `Video; const `Application; + const `Message; const `Multipart; map [ x_token ] (fun v -> `X_token v); + ] let iana ty = - choose (Mrmime.Iana.Map.find (Mrmime.Content_type.Type.to_string ty) Mrmime.Iana.database - |> Mrmime.Iana.Set.elements - |> List.map const) - -let key = map [ token ] (fun v -> match Mrmime.Content_type.Parameters.key v with - | Ok v -> v - | Error _ -> bad_test ()) + choose + (Mrmime.Iana.Map.find + (Mrmime.Content_type.Type.to_string ty) + Mrmime.Iana.database + |> Mrmime.Iana.Set.elements + |> List.map const) + +let key = + map [ token ] (fun v -> + match Mrmime.Content_type.Parameters.key v with + | Ok v -> v + | Error _ -> bad_test ()) let subty = function - | (#Mrmime.Content_type.Type.discrete - | #Mrmime.Content_type.Type.composite) as ty -> map [ iana ty ] (fun v -> ty, `Iana_token v) - | ty -> map [ x_token ] (fun v -> ty, `X_token v) - -let parameter = map [ key; value] (fun key value -> (key, value)) + | (#Mrmime.Content_type.Type.discrete | #Mrmime.Content_type.Type.composite) + as ty -> + map [ iana ty ] (fun v -> (ty, `Iana_token v)) + | ty -> map [ x_token ] (fun v -> (ty, `X_token v)) +let parameter = map [ key; value ] (fun key value -> (key, value)) let parameters = list parameter let content_type = - map [ dynamic_bind ty subty; parameters; ] - (fun (ty_, subty_) parameters_ -> Mrmime.Content_type.{ ty= ty_; subty= subty_; parameters= parameters_; }) + map [ dynamic_bind ty subty; parameters ] (fun (ty_, subty_) parameters_ -> + Mrmime.Content_type.{ ty = ty_; subty = subty_; parameters = parameters_ }) module BBuffer = Buffer let emitter_of_buffer buf = let open Prettym in - let write a = function - | { IOVec.buffer= Buffer.String x; off; len; } -> - BBuffer.add_substring buf x off len; a + len - | { IOVec.buffer= Buffer.Bytes x; off; len; } -> - BBuffer.add_subbytes buf x off len; a + len - | { IOVec.buffer= Buffer.Bigstring x; off; len; } -> - BBuffer.add_string buf (Bigstringaf.substring x ~off ~len); a + len in + | { IOVec.buffer = Buffer.String x; off; len } -> + BBuffer.add_substring buf x off len; + a + len + | { IOVec.buffer = Buffer.Bytes x; off; len } -> + BBuffer.add_subbytes buf x off len; + a + len + | { IOVec.buffer = Buffer.Bigstring x; off; len } -> + BBuffer.add_string buf (Bigstringaf.substring x ~off ~len); + a + len + in List.fold_left write 0 -let ( <.> ) f g = fun x -> f (g x) +let ( <.> ) f g x = f (g x) let parser buf = let open Angstrom in @@ -78,27 +81,34 @@ let parser buf = Unstrctrd.without_comments v >>| Unstrctrd.fold_fws >>| Unstrctrd.to_utf_8_string - >>= ( R.reword_error R.msg <.> Angstrom.parse_string Mrmime.Content_type.Decoder.content ) in - match res with - | Ok v -> return v - | Error (`Msg err) -> fail err + >>= (R.reword_error R.msg + <.> Angstrom.parse_string ~consume:Prefix + Mrmime.Content_type.Decoder.content) + in + match res with Ok v -> return v | Error (`Msg err) -> fail err let () = let open Mrmime in - Crowbar.add_test ~name:"content-type" [ content_type ] @@ fun content_type -> - let buffer = Buffer.create 0x100 in - let encoder = Prettym.create ~margin:78 ~new_line:"\r\n" 0x100 ~emitter:(emitter_of_buffer buffer) in - let encoder = Prettym.keval Prettym.flush encoder Prettym.[ !!Content_type.Encoder.content_type; new_line; ] content_type in - - check_eq ~pp:Fmt.bool ~eq:(=) (Prettym.is_empty encoder) true ; + let encoder = + Prettym.create ~margin:78 ~new_line:"\r\n" 0x100 + ~emitter:(emitter_of_buffer buffer) + in + let encoder = + Prettym.keval Prettym.flush encoder + Prettym.[ !!Content_type.Encoder.content_type; new_line ] + content_type + in + + check_eq ~pp:Fmt.bool ~eq:( = ) (Prettym.is_empty encoder) true; let result = Buffer.contents buffer in let buf = Bytes.create 0x7f in - match Angstrom.parse_string (parser buf) result with + match Angstrom.parse_string ~consume:Prefix (parser buf) result with | Ok content_type' -> - check_eq ~pp:Content_type.pp ~eq:Content_type.equal content_type content_type' + check_eq ~pp:Content_type.pp ~eq:Content_type.equal content_type + content_type' | Error err -> - failf "%a can not be parsed: %s" Content_type.pp content_type err + failf "%a can not be parsed: %s" Content_type.pp content_type err diff --git a/fuzz/fuzz_date.ml b/fuzz/fuzz_date.ml index ea2afa6..fd39343 100644 --- a/fuzz/fuzz_date.ml +++ b/fuzz/fuzz_date.ml @@ -2,86 +2,79 @@ open Crowbar let day = choose - [ const Mrmime.Date.Day.Mon - ; const Mrmime.Date.Day.Tue - ; const Mrmime.Date.Day.Wed - ; const Mrmime.Date.Day.Thu - ; const Mrmime.Date.Day.Fri - ; const Mrmime.Date.Day.Sat - ; const Mrmime.Date.Day.Sun ] + [ + const Mrmime.Date.Day.Mon; const Mrmime.Date.Day.Tue; + const Mrmime.Date.Day.Wed; const Mrmime.Date.Day.Thu; + const Mrmime.Date.Day.Fri; const Mrmime.Date.Day.Sat; + const Mrmime.Date.Day.Sun; + ] let month = choose - [ const Mrmime.Date.Month.Jan - ; const Mrmime.Date.Month.Feb - ; const Mrmime.Date.Month.Mar - ; const Mrmime.Date.Month.Apr - ; const Mrmime.Date.Month.May - ; const Mrmime.Date.Month.Jun - ; const Mrmime.Date.Month.Jul - ; const Mrmime.Date.Month.Aug - ; const Mrmime.Date.Month.Sep - ; const Mrmime.Date.Month.Oct - ; const Mrmime.Date.Month.Nov - ; const Mrmime.Date.Month.Dec ] + [ + const Mrmime.Date.Month.Jan; const Mrmime.Date.Month.Feb; + const Mrmime.Date.Month.Mar; const Mrmime.Date.Month.Apr; + const Mrmime.Date.Month.May; const Mrmime.Date.Month.Jun; + const Mrmime.Date.Month.Jul; const Mrmime.Date.Month.Aug; + const Mrmime.Date.Month.Sep; const Mrmime.Date.Month.Oct; + const Mrmime.Date.Month.Nov; const Mrmime.Date.Month.Dec; + ] let military_zone = - map [ range 25 ] - (fun n -> match Char.unsafe_chr n with - | '\000' .. '\008' -> Mrmime.Date.Zone.Military_zone (Char.unsafe_chr (n + 65)) - | '\009' .. '\024' -> Mrmime.Date.Zone.Military_zone (Char.unsafe_chr (n + 1 + 65)) - | _ -> assert false) + map [ range 25 ] (fun n -> + match Char.unsafe_chr n with + | '\000' .. '\008' -> + Mrmime.Date.Zone.Military_zone (Char.unsafe_chr (n + 65)) + | '\009' .. '\024' -> + Mrmime.Date.Zone.Military_zone (Char.unsafe_chr (n + 1 + 65)) + | _ -> assert false) -let tz = - map [ range 24; range 60 ] (fun a b -> Mrmime.Date.Zone.TZ (a, b)) +let tz = map [ range 24; range 60 ] (fun a b -> Mrmime.Date.Zone.TZ (a, b)) let zone = choose - [ const Mrmime.Date.Zone.UT - ; const Mrmime.Date.Zone.GMT - ; const Mrmime.Date.Zone.EST - ; const Mrmime.Date.Zone.EDT - ; const Mrmime.Date.Zone.CST - ; const Mrmime.Date.Zone.CDT - ; const Mrmime.Date.Zone.MST - ; const Mrmime.Date.Zone.MDT - ; const Mrmime.Date.Zone.PST - ; const Mrmime.Date.Zone.PDT - ; military_zone - ; tz ] - -let year = - choose - [ range ~min:1990 3000 - ; range ~min:90 100 ] - + [ + const Mrmime.Date.Zone.UT; const Mrmime.Date.Zone.GMT; + const Mrmime.Date.Zone.EST; const Mrmime.Date.Zone.EDT; + const Mrmime.Date.Zone.CST; const Mrmime.Date.Zone.CDT; + const Mrmime.Date.Zone.MST; const Mrmime.Date.Zone.MDT; + const Mrmime.Date.Zone.PST; const Mrmime.Date.Zone.PDT; military_zone; tz; + ] + +let year = choose [ range ~min:1990 3000; range ~min:90 100 ] let hours = range 25 let minutes = range 61 let seconds = range 61 let date = - map [ option day; range 31; month; year; hours; minutes; option seconds; zone ] + map + [ option day; range 31; month; year; hours; minutes; option seconds; zone ] (fun day date month year hours minutes seconds zone -> - { Mrmime.Date.day - ; date= (date, month, year) - ; time= (hours, minutes, seconds) - ; zone }) + { + Mrmime.Date.day; + date = (date, month, year); + time = (hours, minutes, seconds); + zone; + }) module BBuffer = Buffer let emitter_of_buffer buf = let open Prettym in - let write a = function - | { IOVec.buffer= Buffer.String x; off; len; } -> - BBuffer.add_substring buf x off len; a + len - | { IOVec.buffer= Buffer.Bytes x; off; len; } -> - BBuffer.add_subbytes buf x off len; a + len - | { IOVec.buffer= Buffer.Bigstring x; off; len; } -> - BBuffer.add_string buf (Bigstringaf.substring x ~off ~len); a + len in + | { IOVec.buffer = Buffer.String x; off; len } -> + BBuffer.add_substring buf x off len; + a + len + | { IOVec.buffer = Buffer.Bytes x; off; len } -> + BBuffer.add_subbytes buf x off len; + a + len + | { IOVec.buffer = Buffer.Bigstring x; off; len } -> + BBuffer.add_string buf (Bigstringaf.substring x ~off ~len); + a + len + in List.fold_left write 0 -let ( <.> ) f g = fun x -> f (g x) +let ( <.> ) f g x = f (g x) let parser buf = let open Angstrom in @@ -91,47 +84,51 @@ let parser buf = Unstrctrd.without_comments v >>| Unstrctrd.fold_fws >>| Unstrctrd.to_utf_8_string - >>= ( R.reword_error R.msg <.> Angstrom.parse_string Mrmime.Date.Decoder.date_time ) in - match res with - | Ok v -> return v - | Error (`Msg err) -> fail err + >>= (R.reword_error R.msg + <.> Angstrom.parse_string ~consume:Prefix Mrmime.Date.Decoder.date_time + ) + in + match res with Ok v -> return v | Error (`Msg err) -> fail err let () = let open Mrmime in - Crowbar.add_test ~name:"date" [ date ] @@ fun date -> - let buffer = Buffer.create 0x100 in - let encoder = Prettym.create ~margin:78 ~new_line:"\r\n" 0x100 ~emitter:(emitter_of_buffer buffer) in - let encoder = Prettym.keval Prettym.flush encoder Prettym.[ !!Date.Encoder.date; new_line; ] date in - - check_eq ~pp:Fmt.bool ~eq:(=) (Prettym.is_empty encoder) true ; + let encoder = + Prettym.create ~margin:78 ~new_line:"\r\n" 0x100 + ~emitter:(emitter_of_buffer buffer) + in + let encoder = + Prettym.keval Prettym.flush encoder + Prettym.[ !!Date.Encoder.date; new_line ] + date + in + + check_eq ~pp:Fmt.bool ~eq:( = ) (Prettym.is_empty encoder) true; let result = Buffer.contents buffer in let buf = Bytes.create 0x7f in - match Angstrom.parse_string (parser buf) result with - | Ok date' -> - check_eq ~pp:Date.pp ~eq:Date.equal date date' - | Error err -> - failf "%a can not be parsed: %s" Date.pp date err + match Angstrom.parse_string ~consume:Prefix (parser buf) result with + | Ok date' -> check_eq ~pp:Date.pp ~eq:Date.equal date date' + | Error err -> failf "%a can not be parsed: %s" Date.pp date err -let ( <.> ) f g = fun x -> f (g x) +let ( <.> ) f g x = f (g x) let () = let open Mrmime in - Crowbar.add_test ~name:"date & ptime" [ float; zone ] @@ fun seconds zone -> (* XXX(dinosaure): according [Ptime]'s documentation, subsecond precision are floored. *) match Ptime.of_float_s (Stdlib.Float.floor seconds) with | None -> Crowbar.bad_test () - | Some ptime -> - let date = Date.of_ptime ~zone ptime in - match Date.to_ptime date with - | Ok ptime' -> - check_eq ~pp:Fmt.int64 ~eq:Int64.equal - ((Int64.of_float <.> Ptime.to_float_s) ptime) - ((Int64.of_float <.> Ptime.to_float_s) ptime') - | Error (`Msg err) -> - failf "isormisphm was not respected on %a: %s" Mrmime.Date.pp date err + | Some ptime -> ( + let date = Date.of_ptime ~zone ptime in + match Date.to_ptime date with + | Ok (ptime', _tz_offset_s) -> + check_eq ~pp:Fmt.int64 ~eq:Int64.equal + ((Int64.of_float <.> Ptime.to_float_s) ptime) + ((Int64.of_float <.> Ptime.to_float_s) ptime') + | Error (`Msg err) -> + failf "isormisphm was not respected on %a: %s" Mrmime.Date.pp date err + ) diff --git a/fuzz/fuzz_encoder.ml b/fuzz/fuzz_encoder.ml index 91f9316..f94fa6b 100644 --- a/fuzz/fuzz_encoder.ml +++ b/fuzz/fuzz_encoder.ml @@ -1,10 +1,9 @@ -let comma = - (fun t () -> Prettym.char t ','), () +let comma = ((fun t () -> Prettym.char t ','), ()) let rec value t x = let binding t (k, v) = Prettym.eval t - Prettym.[char $ '"'; !!string; char $ '"'; char $ ':'; !!value] + Prettym.[ char $ '"'; !!string; char $ '"'; char $ ':'; !!value ] k v in let arr = Prettym.list ~sep:comma value in @@ -14,29 +13,34 @@ let rec value t x = | `Bool false -> Prettym.string t "false" | `Null -> Prettym.string t "null" | `Float f -> Prettym.string t (Fmt.strf "%.16g" f) - | `String s -> Prettym.eval t Prettym.[char $ '"'; !!string; char $ '"'] s - | `A a -> Prettym.eval t Prettym.[char $ '['; !!arr; char $ ']'] a - | `O o -> Prettym.eval t Prettym.[char $ '{'; !!obj; char $ '}'] o + | `String s -> Prettym.eval t Prettym.[ char $ '"'; !!string; char $ '"' ] s + | `A a -> Prettym.eval t Prettym.[ char $ '['; !!arr; char $ ']' ] a + | `O o -> Prettym.eval t Prettym.[ char $ '{'; !!obj; char $ '}' ] o exception Fail let json = let open Crowbar in - let valid str = - let is = function 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' -> true | _ -> false in - try String.iter (fun chr -> if not (is chr) then raise Fail) str ; true - with Fail -> false in + let is = function + | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' -> true + | _ -> false + in + try + String.iter (fun chr -> if not (is chr) then raise Fail) str; + true + with Fail -> false + in fix @@ fun m -> let string = map [ bytes ] (fun x -> if valid x then x else bad_test ()) in - let binding = map [ string ; m ] (fun k v -> (k, v)) in + let binding = map [ string; m ] (fun k v -> (k, v)) in choose - [ const `Null - ; map [ bool ] (fun x -> `Bool x) - ; map [ string ] (fun x -> `String x) - ; map [ list m ] (fun x -> `A x) - ; map [ list binding ] (fun x -> `O x) ] + [ + const `Null; map [ bool ] (fun x -> `Bool x); + map [ string ] (fun x -> `String x); map [ list m ] (fun x -> `A x); + map [ list binding ] (fun x -> `O x); + ] type await = [ `Await ] type error = [ `Error of Jsonm.error ] @@ -49,39 +53,50 @@ let json_of_input refiller input = let error (`Error err) = Fmt.invalid_arg "%a" Jsonm.pp_error err in let end_of_input `End = Fmt.invalid_arg "Unexpected end of input" in - let rec arr acc k = match Jsonm.decode decoder with - | #await -> refiller () ; arr acc k + let rec arr acc k = + match Jsonm.decode decoder with + | #await -> + refiller (); + arr acc k | #error as err -> error err | #eoi as eoi -> end_of_input eoi | `Lexeme `Ae -> k (`A (List.rev acc)) | `Lexeme v -> base (fun v -> arr (v :: acc) k) v - - and name n k = match Jsonm.decode decoder with - | #await -> refiller () ; name n k + and name n k = + match Jsonm.decode decoder with + | #await -> + refiller (); + name n k | #error as err -> error err | #eoi as eoi -> end_of_input eoi | `Lexeme v -> base (fun v -> k (n, v)) v - - and obj acc k = match Jsonm.decode decoder with - | #await -> refiller () ; obj acc k + and obj acc k = + match Jsonm.decode decoder with + | #await -> + refiller (); + obj acc k | #error as err -> error err | #eoi as eoi -> end_of_input eoi | `Lexeme `Oe -> k (`O (List.rev acc)) | `Lexeme (`Name n) -> name n (fun v -> obj (v :: acc) k) | `Lexeme v -> Fmt.invalid_arg "Unexpected lexeme: %a" Jsonm.pp_lexeme v - and base k = function | #value as v -> k v | `Os -> obj [] k | `As -> arr [] k | `Ae | `Oe -> Fmt.invalid_arg "Unexpected end of array/object" - | `Name v -> Fmt.invalid_arg "Unexpected key: %s" v in + | `Name v -> Fmt.invalid_arg "Unexpected key: %s" v + in - let rec go k = match Jsonm.decode decoder with - | #await -> refiller () ; go k + let rec go k = + match Jsonm.decode decoder with + | #await -> + refiller (); + go k | #error as err -> error err | #eoi as eoi -> end_of_input eoi - | `Lexeme (#Jsonm.lexeme as lexeme) -> base k lexeme in + | `Lexeme (#Jsonm.lexeme as lexeme) -> base k lexeme + in go (fun x -> x) @@ -94,28 +109,31 @@ let json_to_output flusher output json = | (#value as x) :: r -> arr (x :: acc) k r | `A l :: r -> arr [ `As ] (fun l -> arr (List.rev_append l acc) k r) l | `O l :: r -> obj [ `Os ] (fun l -> arr (List.rev_append l acc) k r) l - and obj acc k = function | [] -> k (List.rev (`Oe :: acc)) - | (n, x) :: r -> base (fun v -> obj (List.rev_append v (`Name n :: acc)) k r) x - + | (n, x) :: r -> + base (fun v -> obj (List.rev_append v (`Name n :: acc)) k r) x and base k = function | `A l -> arr [ `As ] k l | `O l -> obj [ `Os ] k l - | #value as x -> k [ x ] in + | #value as x -> k [ x ] + in - base (fun l -> l) json in + base (fun l -> l) json + in let rec write k = function | `Ok -> k () | `Partial -> - flusher (Jsonm.Manual.dst_rem encoder) ; - write k (Jsonm.encode encoder `Await) in + flusher (Jsonm.Manual.dst_rem encoder); + write k (Jsonm.encode encoder `Await) + in let rec go k = function | [] -> write k (Jsonm.encode encoder `End) | lexeme :: r -> - write (fun () -> go k r) (Jsonm.encode encoder (`Lexeme lexeme)) in + write (fun () -> go k r) (Jsonm.encode encoder (`Lexeme lexeme)) + in let lexemes = flat_json json in @@ -125,7 +143,8 @@ let json_of_string x = json_of_input (fun () -> assert false) (`String x) let json_to_string x = let buf = Buffer.create 0x100 in - json_to_output (fun _ -> assert false) (`Buffer buf) x ; Buffer.contents buf + json_to_output (fun _ -> assert false) (`Buffer buf) x; + Buffer.contents buf let rec pp_json ppf = function | `Null -> Fmt.string ppf "" @@ -136,42 +155,44 @@ let rec pp_json ppf = function | `O v -> Fmt.(Dump.list Dump.(pair string pp_json)) ppf v let rec list_cmp cmp a b = - match a, b with + match (a, b) with | [], [] -> 0 - | [], _ -> -1 - | _ , [] -> 1 + | [], _ -> -1 + | _, [] -> 1 | x :: xs, y :: ys -> - let n = cmp x y in - if n = 0 then list_cmp cmp xs ys - else n + let n = cmp x y in + if n = 0 then list_cmp cmp xs ys else n -let cmp_bool a b = match a, b with +let cmp_bool a b = + match (a, b) with | true, true | false, false -> 0 | true, false -> 1 - | false, true -> (-1) + | false, true -> -1 -let rec cmp_json a b = match a, b with +let rec cmp_json a b = + match (a, b) with | `Null, `Null -> 0 | `Bool a, `Bool b -> cmp_bool a b | `String a, `String b -> String.compare a b | `Float a, `Float b -> Stdlib.Float.compare a b | `A a, `A b -> list_cmp cmp_json a b | `O a, `O b -> - let cmp (ka, a) (kb, b) = - let x = String.compare ka kb in - if x = 0 then cmp_json a b else x in - list_cmp cmp a b - | `Null, _ -> (-1) + let cmp (ka, a) (kb, b) = + let x = String.compare ka kb in + if x = 0 then cmp_json a b else x + in + list_cmp cmp a b + | `Null, _ -> -1 | _, `Null -> 1 - | `Bool _, _ -> (-1) + | `Bool _, _ -> -1 | _, `Bool _ -> 1 - | `String _, _ -> (-1) + | `String _, _ -> -1 | _, `String _ -> 1 - | `Float _, _ -> (-1) + | `Float _, _ -> -1 | _, `Float _ -> 1 - | `A _, _ -> (-1) + | `A _, _ -> -1 | _, `A _ -> 1 - | `O _, _ -> (-1) + | `O _, _ -> -1 | _, `O _ -> 1 | `AnyOtherTag, `AnyOtherTag -> assert false @@ -181,14 +202,17 @@ module BBuffer = Buffer let emitter_of_buffer buf = let open Prettym in - let write a = function - | { IOVec.buffer= Buffer.String x; off; len; } -> - BBuffer.add_substring buf x off len; a + len - | { IOVec.buffer= Buffer.Bytes x; off; len; } -> - BBuffer.add_subbytes buf x off len; a + len - | { IOVec.buffer= Buffer.Bigstring x; off; len; } -> - BBuffer.add_string buf (Bigstringaf.substring x ~off ~len); a + len in + | { IOVec.buffer = Buffer.String x; off; len } -> + BBuffer.add_substring buf x off len; + a + len + | { IOVec.buffer = Buffer.Bytes x; off; len } -> + BBuffer.add_subbytes buf x off len; + a + len + | { IOVec.buffer = Buffer.Bigstring x; off; len } -> + BBuffer.add_string buf (Bigstringaf.substring x ~off ~len); + a + len + in List.fold_left write 0 let () = @@ -200,22 +224,24 @@ let () = let open Prettym.IOVec in let open Prettym.Buffer in match x with - | { buffer= String x; off; len; } -> - Buffer.add_substring buf x off len ; a + len - | { buffer= Bytes x; off; len; } -> - Buffer.add_subbytes buf x off len ; a + len - | { buffer= Bigstring x; off; len; } -> - let x = Bigstringaf.substring x ~off ~len in - Buffer.add_string buf x ; a + len in - List.fold_left write 0 in - - let encoder = Prettym.create - ~emitter - ~margin:78 - ~new_line:"\n" 0x100 in - let encoder = Prettym.eval encoder Prettym.[!!value; new_line] json in - - Crowbar.check_eq ~pp:Fmt.bool ~eq:(=) (Prettym.is_empty encoder) true ; + | { buffer = String x; off; len } -> + Buffer.add_substring buf x off len; + a + len + | { buffer = Bytes x; off; len } -> + Buffer.add_subbytes buf x off len; + a + len + | { buffer = Bigstring x; off; len } -> + let x = Bigstringaf.substring x ~off ~len in + Buffer.add_string buf x; + a + len + in + List.fold_left write 0 + in + + let encoder = Prettym.create ~emitter ~margin:78 ~new_line:"\n" 0x100 in + let encoder = Prettym.eval encoder Prettym.[ !!value; new_line ] json in + + Crowbar.check_eq ~pp:Fmt.bool ~eq:( = ) (Prettym.is_empty encoder) true; let res = Buffer.contents buf in let res = json_of_string res in diff --git a/fuzz/fuzz_mailbox.ml b/fuzz/fuzz_mailbox.ml index 149e0dc..6ce6953 100644 --- a/fuzz/fuzz_mailbox.ml +++ b/fuzz/fuzz_mailbox.ml @@ -4,76 +4,99 @@ open Common (* XXX(dinosaure): we did not generate UTF-8 valid string - we refer only on RFC 822. *) let local_word = - map [ dynamic_bind (range ~min:1 78) (string_from_alphabet atext) ] - (fun str -> match Mrmime.Mailbox.Local.word str with - | Ok str -> str - | Error _ -> bad_test ()) + map + [ dynamic_bind (range ~min:1 78) (string_from_alphabet atext) ] + (fun str -> + match Mrmime.Mailbox.Local.word str with + | Ok str -> str + | Error _ -> bad_test ()) let local = list1 local_word let phrase_word = - map [ dynamic_bind (range ~min:1 78) bytes_fixed ] - (fun str -> match Mrmime.Mailbox.Phrase.word str with - | Ok elt -> elt - | Error _ -> bad_test ()) + map + [ dynamic_bind (range ~min:1 78) bytes_fixed ] + (fun str -> + match Mrmime.Mailbox.Phrase.word str with + | Ok elt -> elt + | Error _ -> bad_test ()) let encoded_word = - map [ bool; bytes ] - (fun base64 input -> match base64 with - | true -> Mrmime.Mailbox.Phrase.e ~encoding:Mrmime.Encoded_word.b input - | false -> Mrmime.Mailbox.Phrase.e ~encoding:Mrmime.Encoded_word.q input) + map [ bool; bytes ] (fun base64 input -> + match base64 with + | true -> Mrmime.Mailbox.Phrase.e ~encoding:Mrmime.Encoded_word.b input + | false -> Mrmime.Mailbox.Phrase.e ~encoding:Mrmime.Encoded_word.q input) let phrase = - map [ choose [ phrase_word ]; list (choose [ const `Dot; phrase_word; ]) ] + map + [ choose [ phrase_word ]; list (choose [ const `Dot; phrase_word ]) ] (fun head -> function [] -> [ head ] | rest -> head :: rest) -let extension = map [ dynamic_bind (range 78) (string_from_alphabet ldh_str) - ; range (String.length let_dig) - ; dynamic_bind (range ~min:1 78) (string_from_alphabet dcontent) ] - (fun ldh idx x -> match Mrmime.Mailbox.Domain.(make extension (ldh ^ String.make 1 (let_dig.[idx]), x)) with - | Ok v -> v - | Error _ -> bad_test ()) - -let ipv4 = map [ bytes ] - (fun input -> match Ipaddr.V4.of_string input with - | Ok x -> Mrmime.Mailbox.Domain.(v ipv4 x) - | Error _ -> bad_test ()) - -let ipv6 = map [ bytes ] - (fun input -> match Ipaddr.V6.of_string input with - | Ok x -> Mrmime.Mailbox.Domain.(v ipv6 x) - | Error _ -> bad_test ()) - -let domain_atom = map [ dynamic_bind (range ~min:1 78) (string_from_alphabet dtext) ] - (fun input -> match Mrmime.Mailbox.Domain.atom input with - | Ok v -> v - | Error _ -> bad_test ()) - -let domain = map [ list1 domain_atom ] (fun lst -> `Domain (List.map (fun (`Atom x) -> x) lst)) -let domain = choose [ extension; ipv4; ipv6; domain; ] +let extension = + map + [ + dynamic_bind (range 78) (string_from_alphabet ldh_str); + range (String.length let_dig); + dynamic_bind (range ~min:1 78) (string_from_alphabet dcontent); + ] + (fun ldh idx x -> + match + Mrmime.Mailbox.Domain.( + make extension (ldh ^ String.make 1 let_dig.[idx], x)) + with + | Ok v -> v + | Error _ -> bad_test ()) + +let ipv4 = + map [ bytes ] (fun input -> + match Ipaddr.V4.of_string input with + | Ok x -> Mrmime.Mailbox.Domain.(v ipv4 x) + | Error _ -> bad_test ()) + +let ipv6 = + map [ bytes ] (fun input -> + match Ipaddr.V6.of_string input with + | Ok x -> Mrmime.Mailbox.Domain.(v ipv6 x) + | Error _ -> bad_test ()) + +let domain_atom = + map + [ dynamic_bind (range ~min:1 78) (string_from_alphabet dtext) ] + (fun input -> + match Mrmime.Mailbox.Domain.atom input with + | Ok v -> v + | Error _ -> bad_test ()) + +let domain = + map [ list1 domain_atom ] (fun lst -> + `Domain (List.map (fun (`Atom x) -> x) lst)) + +let domain = choose [ extension; ipv4; ipv6; domain ] (* XXX(dinosaure): we did not include [`Literal] domain because [Rfc822.domain] excludes it according to RFC 5321 (see [Rfc822.domain]). *) let mailbox = - map [ option phrase; local; list1 domain ] - (fun name local domains -> - match domains with - | x :: r -> Emile.{ name; local; domain = (x, r) } - | [] -> bad_test ()) + map [ option phrase; local; list1 domain ] (fun name local domains -> + match domains with + | x :: r -> Emile.{ name; local; domain = (x, r) } + | [] -> bad_test ()) module BBuffer = Buffer let emitter_of_buffer buf = let open Prettym in - let write a = function - | { IOVec.buffer= Buffer.String x; off; len; } -> - BBuffer.add_substring buf x off len; a + len - | { IOVec.buffer= Buffer.Bytes x; off; len; } -> - BBuffer.add_subbytes buf x off len; a + len - | { IOVec.buffer= Buffer.Bigstring x; off; len; } -> - BBuffer.add_string buf (Bigstringaf.substring x ~off ~len); a + len in + | { IOVec.buffer = Buffer.String x; off; len } -> + BBuffer.add_substring buf x off len; + a + len + | { IOVec.buffer = Buffer.Bytes x; off; len } -> + BBuffer.add_subbytes buf x off len; + a + len + | { IOVec.buffer = Buffer.Bigstring x; off; len } -> + BBuffer.add_string buf (Bigstringaf.substring x ~off ~len); + a + len + in List.fold_left write 0 let parser buf = @@ -86,28 +109,33 @@ let parser buf = (* XXX(dinosaure): '(' and ')' can be handle (and have a signification) by [Emile]. *) >>| Unstrctrd.to_utf_8_string - >>= ( R.reword_error R.msg <.> Angstrom.parse_string Mrmime.Mailbox.Decoder.mailbox ) in - match res with - | Ok v -> return v - | Error (`Msg err) -> fail err + >>= (R.reword_error R.msg + <.> Angstrom.parse_string ~consume:Prefix Mrmime.Mailbox.Decoder.mailbox + ) + in + match res with Ok v -> return v | Error (`Msg err) -> fail err let () = let open Mrmime in - Crowbar.add_test ~name:"mailbox" [ mailbox ] @@ fun mailbox -> - let buffer = Buffer.create 0x100 in - let encoder = Prettym.create ~margin:78 ~new_line:"\r\n" 0x100 ~emitter:(emitter_of_buffer buffer) in - let encoder = Prettym.keval Prettym.flush encoder Prettym.[ !!Mailbox.Encoder.mailbox; new_line; ] mailbox in - - check_eq ~pp:Fmt.bool ~eq:(=) (Prettym.is_empty encoder) true ; + let encoder = + Prettym.create ~margin:78 ~new_line:"\r\n" 0x100 + ~emitter:(emitter_of_buffer buffer) + in + let encoder = + Prettym.keval Prettym.flush encoder + Prettym.[ !!Mailbox.Encoder.mailbox; new_line ] + mailbox + in + + check_eq ~pp:Fmt.bool ~eq:( = ) (Prettym.is_empty encoder) true; let result = Buffer.contents buffer in let buf = Bytes.create 0x7f in - match Angstrom.parse_string (parser buf) result with - | Ok mailbox' -> - check_eq ~pp:Mailbox.pp ~eq:Mailbox.equal mailbox mailbox' + match Angstrom.parse_string ~consume:Prefix (parser buf) result with + | Ok mailbox' -> check_eq ~pp:Mailbox.pp ~eq:Mailbox.equal mailbox mailbox' | Error err -> - Fmt.epr "output: @[%a@]\n%!" (Hxd_string.pp Hxd.O.default) result ; - failf "%a can not be parsed: %s" Mailbox.pp mailbox err + Fmt.epr "output: @[%a@]\n%!" (Hxd_string.pp Hxd.default) result; + failf "%a can not be parsed: %s" Mailbox.pp mailbox err diff --git a/fuzz/fuzz_message_id.ml b/fuzz/fuzz_message_id.ml index c07660b..71b8d18 100644 --- a/fuzz/fuzz_message_id.ml +++ b/fuzz/fuzz_message_id.ml @@ -4,42 +4,50 @@ open Common (* XXX(dinosaure): we did not generate UTF-8 valid string - we refer only on RFC 822. *) let local_word = - map [ dynamic_bind (range ~min:1 78) (string_from_alphabet atext) ] - (fun str -> match Mrmime.Mailbox.Local.word str with - | Ok str -> str - | Error _ -> bad_test ()) + map + [ dynamic_bind (range ~min:1 78) (string_from_alphabet atext) ] + (fun str -> + match Mrmime.Mailbox.Local.word str with + | Ok str -> str + | Error _ -> bad_test ()) let local = list1 local_word -let domain_atom = map [ dynamic_bind (range ~min:1 78) (string_from_alphabet dtext) ] - (fun input -> match Mrmime.Mailbox.Domain.atom input with - | Ok v -> v - | Error _ -> bad_test ()) +let domain_atom = + map + [ dynamic_bind (range ~min:1 78) (string_from_alphabet dtext) ] + (fun input -> + match Mrmime.Mailbox.Domain.atom input with + | Ok v -> v + | Error _ -> bad_test ()) -let domain = map [ list1 domain_atom ] (fun lst -> `Domain (List.map (fun (`Atom x) -> x) lst)) +let domain = + map [ list1 domain_atom ] (fun lst -> + `Domain (List.map (fun (`Atom x) -> x) lst)) (* XXX(dinosaure): we did not include [`Literal] domain because [Rfc822.domain] excludes it according to RFC 5321 (see [Rfc822.domain]). *) -let message_id = - map [ local; domain ] - (fun local domain -> (local, domain)) +let message_id = map [ local; domain ] (fun local domain -> (local, domain)) module BBuffer = Buffer let emitter_of_buffer buf = let open Prettym in - let write a = function - | { IOVec.buffer= Buffer.String x; off; len; } -> - BBuffer.add_substring buf x off len; a + len - | { IOVec.buffer= Buffer.Bytes x; off; len; } -> - BBuffer.add_subbytes buf x off len; a + len - | { IOVec.buffer= Buffer.Bigstring x; off; len; } -> - BBuffer.add_string buf (Bigstringaf.substring x ~off ~len); a + len in + | { IOVec.buffer = Buffer.String x; off; len } -> + BBuffer.add_substring buf x off len; + a + len + | { IOVec.buffer = Buffer.Bytes x; off; len } -> + BBuffer.add_subbytes buf x off len; + a + len + | { IOVec.buffer = Buffer.Bigstring x; off; len } -> + BBuffer.add_string buf (Bigstringaf.substring x ~off ~len); + a + len + in List.fold_left write 0 -let ( <.> ) f g = fun x -> f (g x) +let ( <.> ) f g x = f (g x) let parser buf = let open Angstrom in @@ -49,29 +57,35 @@ let parser buf = Unstrctrd.without_comments v >>| Unstrctrd.fold_fws >>| Unstrctrd.to_utf_8_string - >>= ( R.reword_error R.msg <.> Angstrom.parse_string Mrmime.MessageID.Decoder.message_id ) in - match res with - | Ok v -> return v - | Error (`Msg err) -> fail err + >>= (R.reword_error R.msg + <.> Angstrom.parse_string ~consume:Prefix + Mrmime.MessageID.Decoder.message_id) + in + match res with Ok v -> return v | Error (`Msg err) -> fail err let () = let open Mrmime in - Crowbar.add_test ~name:"message_id" [ message_id ] @@ fun message_id -> - let buffer = Buffer.create 0x100 in - let encoder = Prettym.create ~margin:78 ~new_line:"\r\n" 0x100 ~emitter:(emitter_of_buffer buffer) in - let encoder = Prettym.keval Prettym.flush encoder Prettym.[ !!MessageID.Encoder.message_id; new_line; ] message_id in - - check_eq ~pp:Fmt.bool ~eq:(=) (Prettym.is_empty encoder) true ; + let encoder = + Prettym.create ~margin:78 ~new_line:"\r\n" 0x100 + ~emitter:(emitter_of_buffer buffer) + in + let encoder = + Prettym.keval Prettym.flush encoder + Prettym.[ !!MessageID.Encoder.message_id; new_line ] + message_id + in + + check_eq ~pp:Fmt.bool ~eq:( = ) (Prettym.is_empty encoder) true; let result = Buffer.contents buffer in let buf = Bytes.create 0x7f in - match Angstrom.parse_string (parser buf) result with + match Angstrom.parse_string ~consume:Prefix (parser buf) result with | Ok message_id' -> - check_eq ~pp:MessageID.pp ~eq:MessageID.equal message_id message_id' + check_eq ~pp:MessageID.pp ~eq:MessageID.equal message_id message_id' | Error err -> - Fmt.epr "message-id: @[%a@]\n%!" MessageID.pp message_id ; - Fmt.epr "output: @[%a@]\n%!" (Hxd_string.pp Hxd.O.default) result ; - failf "%a can not be parsed: %s" MessageID.pp message_id err + Fmt.epr "message-id: @[%a@]\n%!" MessageID.pp message_id; + Fmt.epr "output: @[%a@]\n%!" (Hxd_string.pp Hxd.default) result; + failf "%a can not be parsed: %s" MessageID.pp message_id err diff --git a/lib/address.ml b/lib/address.ml index c35e752..379dd34 100644 --- a/lib/address.ml +++ b/lib/address.ml @@ -19,7 +19,8 @@ type t = Emile.t let group group = `Group group let mailbox mailbox = `Mailbox mailbox -let equal a b = match a, b with +let equal a b = + match (a, b) with | `Group a, `Group b -> Group.equal a b | `Mailbox a, `Mailbox b -> Mailbox.equal a b | _ -> false @@ -36,7 +37,7 @@ module Encoder = struct let mailbox = Mailbox.Encoder.mailbox let group = Group.Encoder.group - let comma = (fun ppf () -> eval ppf [ char $ ','; fws ]), () + let comma = ((fun ppf () -> eval ppf [ char $ ','; fws ]), ()) let address ppf = function | `Mailbox m -> mailbox ppf m diff --git a/lib/address.mli b/lib/address.mli index 047df79..0feed51 100644 --- a/lib/address.mli +++ b/lib/address.mli @@ -14,7 +14,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *) -type t = [`Group of Group.t | `Mailbox of Mailbox.t] +type t = [ `Group of Group.t | `Mailbox of Mailbox.t ] (** Type of address, an address may either be an individual mailbox, or a group of mailboxes. *) diff --git a/lib/b64.ml b/lib/b64.ml index 7eb8fb6..f01366f 100644 --- a/lib/b64.ml +++ b/lib/b64.ml @@ -10,51 +10,64 @@ let parser ~write_data end_of_body = let check_end_of_body = let expected_len = String.length end_of_body in - Unsafe.peek expected_len - (fun ba ~off ~len -> - let raw = Bigstringaf.substring ba ~off ~len in - String.equal raw end_of_body) in + Unsafe.peek expected_len (fun ba ~off ~len -> + let raw = Bigstringaf.substring ba ~off ~len in + String.equal raw end_of_body) + in let trailer () = - let rec finish () = match Base64_rfc2045.decode dec with + let rec finish () = + match Base64_rfc2045.decode dec with | `Await -> assert false - | `Flush data -> write_data data ; finish () + | `Flush data -> + write_data data; + finish () | `Malformed err -> fail err | `Wrong_padding -> fail "wrong padding" | `End -> commit - - and go () = match Base64_rfc2045.decode dec with + and go () = + match Base64_rfc2045.decode dec with | `Await -> - Base64_rfc2045.src dec Bytes.empty 0 0 ; finish () - | `Flush data -> write_data data ; go () + Base64_rfc2045.src dec Bytes.empty 0 0; + finish () + | `Flush data -> + write_data data; + go () | `Malformed err -> fail err | `Wrong_padding -> fail "wrong padding" - | `End -> commit in + | `End -> commit + in - go () in + go () + in fix @@ fun m -> let choose chunk = function | true -> - let chunk = Bytes.sub chunk 0 (Bytes.length chunk - 1) in - Base64_rfc2045.src dec chunk 0 (Bytes.length chunk) ; trailer () + let chunk = Bytes.sub chunk 0 (Bytes.length chunk - 1) in + Base64_rfc2045.src dec chunk 0 (Bytes.length chunk); + trailer () | false -> - Bytes.set chunk (Bytes.length chunk - 1) end_of_body.[0] ; - Base64_rfc2045.src dec chunk 0 (Bytes.length chunk) ; - advance 1 *> m in + Bytes.set chunk (Bytes.length chunk - 1) end_of_body.[0]; + Base64_rfc2045.src dec chunk 0 (Bytes.length chunk); + advance 1 *> m + in - Unsafe.take_while ((<>) end_of_body.[0]) Bigstringaf.substring + Unsafe.take_while (( <> ) end_of_body.[0]) Bigstringaf.substring >>= fun chunk -> - let rec go () = match Base64_rfc2045.decode dec with + let rec go () = + match Base64_rfc2045.decode dec with | `End -> commit | `Await -> - let chunk' = Bytes.create (String.length chunk + 1) in - Bytes.blit_string chunk 0 chunk' 0 (String.length chunk) ; - check_end_of_body >>= choose chunk' + let chunk' = Bytes.create (String.length chunk + 1) in + Bytes.blit_string chunk 0 chunk' 0 (String.length chunk); + check_end_of_body >>= choose chunk' | `Flush data -> - write_data data ; go () + write_data data; + go () | `Malformed err -> fail err - | `Wrong_padding -> fail "wrong padding" in + | `Wrong_padding -> fail "wrong padding" + in go () let with_buffer end_of_body = @@ -70,17 +83,23 @@ let with_emitter ~emitter end_of_body = let to_end_of_input ~write_data = let dec = Base64_rfc2045.decoder `Manual in - fix @@ fun m -> match Base64_rfc2045.decode dec with + fix @@ fun m -> + match Base64_rfc2045.decode dec with | `End -> commit - | `Await -> - (peek_char >>= function - | None -> Base64_rfc2045.src dec Bytes.empty 0 0 ; return () - | Some _ -> available >>= fun n -> Unsafe.take n - (fun ba ~off ~len -> - let chunk = Bytes.create len in - Bigstringaf.blit_to_bytes ba ~src_off:off chunk ~dst_off:0 ~len ; - Base64_rfc2045.src dec chunk 0 len) - >>= fun () -> m) - | `Flush data -> write_data data ; m + | `Await -> ( + peek_char >>= function + | None -> + Base64_rfc2045.src dec Bytes.empty 0 0; + return () + | Some _ -> + available >>= fun n -> + Unsafe.take n (fun ba ~off ~len -> + let chunk = Bytes.create len in + Bigstringaf.blit_to_bytes ba ~src_off:off chunk ~dst_off:0 ~len; + Base64_rfc2045.src dec chunk 0 len) + >>= fun () -> m) + | `Flush data -> + write_data data; + m | `Malformed err -> fail err | `Wrong_padding -> fail "wrong padding" diff --git a/lib/content_encoding.ml b/lib/content_encoding.ml index 3664d87..968a1ed 100644 --- a/lib/content_encoding.ml +++ b/lib/content_encoding.ml @@ -34,21 +34,28 @@ let of_string = function - let the user to craft an extension token. - check IETF database *) -let equal a b = match a, b with +let equal a b = + match (a, b) with | `Bit7, `Bit7 -> true | `Bit8, `Bit8 -> true | `Binary, `Binary -> true | `Quoted_printable, `Quoted_printable -> true | `Base64, `Base64 -> true - | `Ietf_token a, `Ietf_token b -> String.(equal (lowercase_ascii a) (lowercase_ascii b)) - | `X_token a, `X_token b -> String.(equal (lowercase_ascii a) (lowercase_ascii b)) + | `Ietf_token a, `Ietf_token b -> + String.(equal (lowercase_ascii a) (lowercase_ascii b)) + | `X_token a, `X_token b -> + String.(equal (lowercase_ascii a) (lowercase_ascii b)) | _, _ -> false module Decoder = struct open Angstrom let invalid_token token = Fmt.kstrf fail "invalid token: %s" token - let of_string s a = match parse_string ~consume:Consume.All a s with Ok v -> Some v | Error _ -> None + + let of_string s a = + match parse_string ~consume:Consume.All a s with + | Ok v -> Some v + | Error _ -> None (* From RFC 2045 @@ -64,7 +71,8 @@ module Decoder = struct *) let is_tspecials = function | '(' | ')' | '<' | '>' | '@' | ',' | ';' | ':' | '\\' | '"' | '/' | '[' - | ']' | '?' | '=' -> true + | ']' | '?' | '=' -> + true | _ -> false let is_ctl = function '\000' .. '\031' | '\127' -> true | _ -> false @@ -76,7 +84,9 @@ module Decoder = struct or tspecials> *) let is_ascii = function '\000' .. '\127' -> true | _ -> false - let is_token c = (is_ascii c) && (not (is_tspecials c)) && (not (is_ctl c)) && (not (is_space c)) + + let is_token c = + is_ascii c && (not (is_tspecials c)) && (not (is_ctl c)) && not (is_space c) let token = take_while1 is_token @@ -103,8 +113,7 @@ module Decoder = struct extension-token := ietf-token / x-token *) let extension_token = - peek_char - >>= function + peek_char >>= function | Some 'X' | Some 'x' -> x_token >>| fun v -> `X_token v | _ -> ietf_token >>| fun v -> `Ietf_token v @@ -129,10 +138,10 @@ module Decoder = struct | "binary" -> return `Binary | "quoted-printable" -> return `Quoted_printable | "base64" -> return `Base64 - | _ -> - match of_string s extension_token with - | Some v -> return v - | None -> invalid_token s + | _ -> ( + match of_string s extension_token with + | Some v -> return v + | None -> invalid_token s) end module Encoder = struct diff --git a/lib/content_encoding.mli b/lib/content_encoding.mli index 0b99394..8ef3c8a 100644 --- a/lib/content_encoding.mli +++ b/lib/content_encoding.mli @@ -16,7 +16,6 @@ (** Content-Transfer-Encoding value *) -(** Type for standard mechanism for encoding. *) type t = [ `Bit7 | `Bit8 @@ -25,6 +24,7 @@ type t = | `Base64 | `Ietf_token of string | `X_token of string ] +(** Type for standard mechanism for encoding. *) (** {2 Basic encodings.} *) diff --git a/lib/content_type.ml b/lib/content_type.ml index 4d00f2d..5d3c3d0 100644 --- a/lib/content_type.ml +++ b/lib/content_type.ml @@ -13,8 +13,9 @@ exception Invalid_token "/", "?", and "=", and the removal of ".". *) let is_tspecials = function - | '(' | ')' | '<' | '>' | '@' | ',' | ';' | ':' | '\\' | '"' | '/' | '[' - | ']' | '?' | '=' -> true + | '(' | ')' | '<' | '>' | '@' | ',' | ';' | ':' | '\\' | '"' | '/' | '[' | ']' + | '?' | '=' -> + true | _ -> false let is_ctl = function '\000' .. '\031' | '\127' -> true | _ -> false @@ -26,11 +27,9 @@ let is_space = ( = ) ' ' or tspecials> *) let is_ascii = function '\000' .. '\127' -> true | _ -> false + let is_token c = - (is_ascii c) - && (not (is_tspecials c)) - && (not (is_ctl c)) - && (not (is_space c)) + is_ascii c && (not (is_tspecials c)) && (not (is_ctl c)) && not (is_space c) let is_obs_no_ws_ctl = function | '\001' .. '\008' | '\011' | '\012' | '\014' .. '\031' | '\127' -> true @@ -53,38 +52,33 @@ module Type = struct let application = `Application let message = `Message let multipart = `Multipart - - let is_discrete = function - | #discrete -> true - | _ -> false - - let is_multipart = function - | `Multipart -> true - | _ -> false - - let is_message = function - | `Message -> true - | _ -> false + let is_discrete = function #discrete -> true | _ -> false + let is_multipart = function `Multipart -> true | _ -> false + let is_message = function `Message -> true | _ -> false let ietf token = - if Iana.Map.mem (String.lowercase_ascii token) Iana.database - then Ok (`Ietf_token token) + if Iana.Map.mem (String.lowercase_ascii token) Iana.database then + Ok (`Ietf_token token) else Rresult.R.error_msgf "%S is not an IETF token" token let extension token = - if String.length token < 3 - then Rresult.R.error_msgf "Extension token MUST have, at least, 3 bytes: %S" token + if String.length token < 3 then + Rresult.R.error_msgf "Extension token MUST have, at least, 3 bytes: %S" + token else match (token.[0], token.[1]) with - | ('x' | 'X'), '-' -> - ( try - String.iter - (fun chr -> if not (is_token chr) then raise Invalid_token) - (String.sub token 2 (String.length token - 2)) ; - Ok (`X_token token) - with Invalid_token -> - Rresult.R.error_msgf "Extension token %S does not respect standards" token ) - | _ -> Rresult.R.error_msgf "An extension token MUST be prefixed by [X-]: %S" token + | ('x' | 'X'), '-' -> ( + try + String.iter + (fun chr -> if not (is_token chr) then raise Invalid_token) + (String.sub token 2 (String.length token - 2)); + Ok (`X_token token) + with Invalid_token -> + Rresult.R.error_msgf "Extension token %S does not respect standards" + token) + | _ -> + Rresult.R.error_msgf "An extension token MUST be prefixed by [X-]: %S" + token let pp ppf = function | `Text -> Fmt.string ppf "text" @@ -108,7 +102,8 @@ module Type = struct | `Ietf_token token | `X_token token -> token let compare a b = - String.(compare (lowercase_ascii (to_string a)) (lowercase_ascii (to_string b))) + String.( + compare (lowercase_ascii (to_string a)) (lowercase_ascii (to_string b))) let equal a b = compare a b = 0 let default = `Text @@ -130,26 +125,29 @@ module Subtype = struct else Rresult.R.error_msgf "Subtype %S does not exist" token | exception Not_found -> Rresult.R.error_msgf "Type %S does not exist" ty - let iana_exn ty token = match iana ty token with - | Ok v -> v - | Error (`Msg err) -> invalid_arg err + let iana_exn ty token = + match iana ty token with Ok v -> v | Error (`Msg err) -> invalid_arg err let v ty token = iana_exn ty token let extension token = - if String.length token < 3 - then Rresult.R.error_msgf "Extension token MUST have, at least, 3 bytes: %S" token + if String.length token < 3 then + Rresult.R.error_msgf "Extension token MUST have, at least, 3 bytes: %S" + token else match (token.[0], token.[1]) with | ('x' | 'X'), '-' -> ( - try - String.iter - (fun chr -> if not (is_token chr) then raise Invalid_token) - (String.sub token 2 (String.length token - 2)) ; - Ok (`X_token token) - with Invalid_token -> - Rresult.R.error_msgf "Extension token %S does not respect standards" token) - | _ -> Rresult.R.error_msgf "An extension token MUST be prefixed by [X-]: %S" token + try + String.iter + (fun chr -> if not (is_token chr) then raise Invalid_token) + (String.sub token 2 (String.length token - 2)); + Ok (`X_token token) + with Invalid_token -> + Rresult.R.error_msgf "Extension token %S does not respect standards" + token) + | _ -> + Rresult.R.error_msgf "An extension token MUST be prefixed by [X-]: %S" + token let pp ppf = function | `Ietf_token token -> Fmt.pf ppf "ietf:%s" token @@ -162,11 +160,10 @@ module Subtype = struct | `X_token token -> token let compare a b = - match (a, b) - with - | ( (`Ietf_token a | `Iana_token a | `X_token a) - , (`Ietf_token b | `Iana_token b | `X_token b) ) - -> String.(compare (lowercase_ascii a) (lowercase_ascii b)) + match (a, b) with + | ( (`Ietf_token a | `Iana_token a | `X_token a), + (`Ietf_token b | `Iana_token b | `X_token b) ) -> + String.(compare (lowercase_ascii a) (lowercase_ascii b)) let equal a b = compare a b = 0 let default = `Iana_token "plain" @@ -187,14 +184,13 @@ module Parameters = struct try String.iter (fun chr -> if not (is_token chr) then raise Invalid_token) - key ; + key; Ok (String.lowercase_ascii key) with Invalid_token -> Rresult.R.error_msgf "Key %S does not respect standards" key - let key_exn x = match key x with - | Ok v -> v - | Error (`Msg err) -> invalid_arg err + let key_exn x = + match key x with Ok v -> v | Error (`Msg err) -> invalid_arg err let k x = key_exn x @@ -205,10 +201,11 @@ module Parameters = struct try String.iter (fun chr -> if not (is_token chr) then raise Invalid_token) - x ; + x; Ok (`Token x) with Invalid_token -> - Rresult.R.error_msgf "Value %S does not respect standards" v in + Rresult.R.error_msgf "Value %S does not respect standards" v + in (* XXX(dinosaure): [is_quoted_pair] accepts characters \000-\127. UTF-8 extends to \000-\255. However, qtext invalids some of them: \009, \010, \013, \032, \034 and \092. Most of them need to be escaped. @@ -218,57 +215,60 @@ module Parameters = struct does not look significant - so we don't try to escape it. *) let need_to_escape = function | '\009' | '\010' | '\013' | '\034' | '\092' -> true - | _ -> false in + | _ -> false + in let of_escaped_character = function | '\009' -> 't' | '\010' -> 'n' | '\013' -> 'r' - | c -> c in + | c -> c + in let escape_characters x = let len = String.length x in let buf = Buffer.create len in String.iter (fun chr -> if need_to_escape chr then ( - Buffer.add_char buf '\\' ; - Buffer.add_char buf (of_escaped_character chr) ) - else Buffer.add_char buf chr ) - x ; - Buffer.contents buf in + Buffer.add_char buf '\\'; + Buffer.add_char buf (of_escaped_character chr)) + else Buffer.add_char buf chr) + x; + Buffer.contents buf + in let utf_8 x = try Uutf.String.fold_utf_8 - (fun () _pos -> function `Malformed _ -> raise Invalid_utf_8 - | `Uchar _ -> () ) - () x ; + (fun () _pos -> function + | `Malformed _ -> raise Invalid_utf_8 + | `Uchar _ -> ()) + () x; Ok x with Invalid_utf_8 -> - Rresult.R.error_msgf "Value %S is not a valid UTF-8 string" x in + Rresult.R.error_msgf "Value %S is not a valid UTF-8 string" x + in match to_token v with | Ok _ as v -> v | Error _ -> - (* UTF-8 respects an interval of values and it's possible to have an - invalid UTF-8 string. So we need to check it. UTF-8 is a superset of - ASCII, so we need, firstly to check if it's a valid UTF-8 string. In - this case, and mostly because we can escape anything (see - [is_quoted_pair]), we do a pass to escape some of ASCII characters only - then. - - At the end, if [value] is a valid UTF-8 string, we will don't have a - problem to encode it if we take care to escape invalid [qtext] - characters. - - However, order is really important semantically. UTF-8 -> escape - expects a special process to decoder (escape -> UTF-8). About history, - unicorn and so on, it should be the best to keep this order. *) - Rresult.R.(utf_8 v >>| escape_characters >>| fun x -> `String x) - - let value_exn x = match value x with - | Ok v -> v - | Error (`Msg err) -> invalid_arg err + (* UTF-8 respects an interval of values and it's possible to have an + invalid UTF-8 string. So we need to check it. UTF-8 is a superset of + ASCII, so we need, firstly to check if it's a valid UTF-8 string. In + this case, and mostly because we can escape anything (see + [is_quoted_pair]), we do a pass to escape some of ASCII characters only + then. - let v x = value_exn x + At the end, if [value] is a valid UTF-8 string, we will don't have a + problem to encode it if we take care to escape invalid [qtext] + characters. + + However, order is really important semantically. UTF-8 -> escape + expects a special process to decoder (escape -> UTF-8). About history, + unicorn and so on, it should be the best to keep this order. *) + Rresult.R.(utf_8 v >>| escape_characters >>| fun x -> `String x) + + let value_exn x = + match value x with Ok v -> v | Error (`Msg err) -> invalid_arg err + let v x = value_exn x let empty = Map.empty let mem key t = @@ -312,13 +312,13 @@ module Parameters = struct if x.[!pos] = '\\' && !pos < len - 1 (* XXX(dinosaure): we can avoid this check when [value] takes care about that. *) - then - ( Buffer.add_char res (of_escaped_character x.[!pos + 1]) - ; pos := !pos + 2 ) - else - ( Buffer.add_char res x.[!pos] - ; incr pos ) - done ; + then ( + Buffer.add_char res (of_escaped_character x.[!pos + 1]); + pos := !pos + 2) + else ( + Buffer.add_char res x.[!pos]; + incr pos) + done; Buffer.contents res let value_compare a b = @@ -334,8 +334,7 @@ module Parameters = struct | `Token a, `Token b -> String.equal a b | `String a, `Token b | `Token b, `String a -> String.equal (value_unescape a) b - | `String a, `String b -> - String.equal (value_unescape a) (value_unescape b) + | `String a, `String b -> String.equal (value_unescape a) (value_unescape b) let compare = Map.compare value_compare let equal = Map.equal value_equal @@ -344,35 +343,35 @@ module Parameters = struct List.fold_left (fun a (key, value) -> Map.add key value a) Map.empty lst let to_list t = Map.bindings t - let default = Map.add "charset" (`Token "us-ascii") Map.empty end -type t = - { ty : Type.t - ; subty : Subtype.t - ; parameters : (string * Parameters.value) list } +type t = { + ty : Type.t; + subty : Subtype.t; + parameters : (string * Parameters.value) list; +} let default = - { ty = Type.default - ; subty = Subtype.default - ; parameters = Parameters.to_list Parameters.default } + { + ty = Type.default; + subty = Subtype.default; + parameters = Parameters.to_list Parameters.default; + } let ty { ty; _ } = ty let subty { subty; _ } = subty let parameters { parameters; _ } = parameters - let is_discrete { ty; _ } = Type.is_discrete ty let is_multipart { ty; _ } = Type.is_multipart ty let is_message { ty; _ } = Type.is_message ty - -let with_type : t -> Type.t -> t = fun t ty -> { t with ty } +let with_type : t -> Type.t -> t = fun t ty -> { t with ty } let with_subtype : t -> Subtype.t -> t = fun t subty -> { t with subty } -let with_parameter - : t -> (Parameters.key * Parameters.value) -> t - = fun t (k, v) -> - let parameters = Parameters.of_list ((k, v) :: t.parameters) in - { t with parameters= Parameters.to_list parameters } + +let with_parameter : t -> Parameters.key * Parameters.value -> t = + fun t (k, v) -> + let parameters = Parameters.of_list ((k, v) :: t.parameters) in + { t with parameters = Parameters.to_list parameters } let boundary { parameters; _ } = match List.assoc_opt "boundary" parameters with @@ -380,13 +379,11 @@ let boundary { parameters; _ } = | None -> None let make ty subty parameters = - { ty; subty; parameters= Parameters.to_list parameters } + { ty; subty; parameters = Parameters.to_list parameters } let pp ppf { ty; subty; parameters } = - Fmt.pf ppf "%a/%a %a" - Type.pp ty - Subtype.pp subty - (Fmt.hvbox Parameters.pp) (Parameters.of_list parameters) + Fmt.pf ppf "%a/%a %a" Type.pp ty Subtype.pp subty (Fmt.hvbox Parameters.pp) + (Parameters.of_list parameters) let equal a b = Type.equal a.ty b.ty @@ -397,7 +394,11 @@ module Decoder = struct open Angstrom let invalid_token token = Fmt.kstrf fail "invalid token: %s" token - let of_string s a = match parse_string ~consume:Consume.All a s with Ok v -> Some v | Error _ -> None + + let of_string s a = + match parse_string ~consume:Consume.All a s with + | Ok v -> Some v + | Error _ -> None let is_wsp = function ' ' | '\t' -> true | _ -> false let token = take_while1 is_token @@ -436,8 +437,7 @@ module Decoder = struct extension-token := ietf-token / x-token *) let extension_token = - peek_char - >>= function + peek_char >>= function | Some 'X' | Some 'x' -> x_token >>| fun v -> `X_token v | _ -> ietf_token >>| fun v -> `Ietf_token v @@ -459,10 +459,10 @@ module Decoder = struct | "application" -> return `Application | "message" -> return `Message | "multipart" -> return `Multipart - | _ -> - match of_string s extension_token with - | Some v -> return v - | None -> invalid_token s + | _ -> ( + match of_string s extension_token with + | Some v -> return v + | None -> invalid_token s) (* From RFC 2045 @@ -470,64 +470,89 @@ module Decoder = struct *) let subty ty = token >>= fun s -> - try let v = `Iana_token (Iana.Set.find s (Iana.Map.find (Type.to_string ty) Iana.database)) in return v - with Not_found -> match of_string s extension_token with + try + let v = + `Iana_token + (Iana.Set.find s (Iana.Map.find (Type.to_string ty) Iana.database)) + in + return v + with Not_found -> ( + match of_string s extension_token with | Some v -> return v - | None -> invalid_token s + | None -> invalid_token s) let _3 x y z = (x, y, z) let _4 a b c d = (a, b, c, d) let ( .![]<- ) = Bytes.set - - let utf_8_tail = - satisfy @@ function '\x80' .. '\xbf' -> true | _ -> false + let utf_8_tail = satisfy @@ function '\x80' .. '\xbf' -> true | _ -> false let utf_8_0 = satisfy (function '\xc2' .. '\xdf' -> true | _ -> false) >>= fun b0 -> - utf_8_tail >>= fun b1 -> let res = Bytes.create 2 in - res.![0] <- b0 ; res.![1] <- b1 ; return (Bytes.unsafe_to_string res) + utf_8_tail >>= fun b1 -> + let res = Bytes.create 2 in + res.![0] <- b0; + res.![1] <- b1; + return (Bytes.unsafe_to_string res) let utf_8_1 = - (lift3 _3 (char '\xe0') (satisfy @@ function '\xa0' .. '\xbf' -> true | _ -> false) utf_8_tail) - <|> (lift3 _3 (satisfy @@ function '\xe1' .. '\xec' -> true | _ -> false) utf_8_tail utf_8_tail) - <|> (lift3 _3 (char '\xed') (satisfy @@ function '\x80' .. '\x9f' -> true | _ -> false) utf_8_tail) - <|> (lift3 _3 (satisfy @@ function '\xee' .. '\xef' -> true | _ -> false) utf_8_tail utf_8_tail) + lift3 _3 (char '\xe0') + (satisfy @@ function '\xa0' .. '\xbf' -> true | _ -> false) + utf_8_tail + <|> lift3 _3 + (satisfy @@ function '\xe1' .. '\xec' -> true | _ -> false) + utf_8_tail utf_8_tail + <|> lift3 _3 (char '\xed') + (satisfy @@ function '\x80' .. '\x9f' -> true | _ -> false) + utf_8_tail + <|> lift3 _3 + (satisfy @@ function '\xee' .. '\xef' -> true | _ -> false) + utf_8_tail utf_8_tail let utf_8_1 = utf_8_1 >>= fun (b0, b1, b2) -> let res = Bytes.create 3 in - res.![0] <- b0 ; res.![1] <- b1 ; res.![2] <- b2 ; return (Bytes.unsafe_to_string res) + res.![0] <- b0; + res.![1] <- b1; + res.![2] <- b2; + return (Bytes.unsafe_to_string res) let utf_8_2 = - (lift4 _4 (char '\xf0') (satisfy @@ function '\x90' .. '\xbf' -> true | _ -> false) utf_8_tail utf_8_tail) - <|> (lift4 _4 (satisfy @@ function '\xf1' .. '\xf3' -> true | _ -> false) utf_8_tail utf_8_tail utf_8_tail) - <|> (lift4 _4 (char '\xf4') (satisfy @@ function '\x80' .. '\x8f' -> true | _ -> false) utf_8_tail utf_8_tail) + lift4 _4 (char '\xf0') + (satisfy @@ function '\x90' .. '\xbf' -> true | _ -> false) + utf_8_tail utf_8_tail + <|> lift4 _4 + (satisfy @@ function '\xf1' .. '\xf3' -> true | _ -> false) + utf_8_tail utf_8_tail utf_8_tail + <|> lift4 _4 (char '\xf4') + (satisfy @@ function '\x80' .. '\x8f' -> true | _ -> false) + utf_8_tail utf_8_tail let utf_8_2 = utf_8_2 >>= fun (b0, b1, b2, b3) -> let res = Bytes.create 4 in - res.![0] <- b0 ; res.![1] <- b1 ; res.![2] <- b2 ; res.![3] <- b3 ; return (Bytes.unsafe_to_string res) - + res.![0] <- b0; + res.![1] <- b1; + res.![2] <- b2; + res.![3] <- b3; + return (Bytes.unsafe_to_string res) let utf_8_and is = - (satisfy is >>| String.make 1) - <|> utf_8_0 - <|> utf_8_1 - <|> utf_8_2 + satisfy is >>| String.make 1 <|> utf_8_0 <|> utf_8_1 <|> utf_8_2 let quoted_pair = char '\\' *> any_char >>| Parameters.of_escaped_character >>| String.make 1 let quoted_string = - char '"' *> (many (quoted_pair <|> utf_8_and is_qtext)) <* char '"' >>| String.concat "" + char '"' *> many (quoted_pair <|> utf_8_and is_qtext) + <* char '"' + >>| String.concat "" (* From RFC 2045 value := token / quoted-string *) let value = - (quoted_string >>| fun v -> `String v) - <|> (token >>| fun v -> `Token v) + quoted_string >>| (fun v -> `String v) <|> (token >>| fun v -> `Token v) (* From RFC 2045 @@ -593,8 +618,7 @@ module Encoder = struct Mailbox.Encoder.word let parameter ppf (key, v) = - eval ppf [ box; !!string; cut; char $ '='; cut; !!value; close ] - key v + eval ppf [ box; !!string; cut; char $ '='; cut; !!value; close ] key v let parameters ppf parameters = let sep ppf () = eval ppf [ char $ ';'; fws ] in @@ -603,11 +627,14 @@ module Encoder = struct let content_type ppf t = match t.parameters with | [] -> - eval ppf - [ bbox; !!ty; cut; char $ '/'; cut; !!subty; close ] - t.ty t.subty + eval ppf + [ bbox; !!ty; cut; char $ '/'; cut; !!subty; close ] + t.ty t.subty | _ -> - eval ppf - [ bbox; !!ty; cut; char $ '/'; cut; !!subty; cut; char $ ';'; fws; !!parameters; close ] - t.ty t.subty t.parameters + eval ppf + [ + bbox; !!ty; cut; char $ '/'; cut; !!subty; cut; char $ ';'; fws; + !!parameters; close; + ] + t.ty t.subty t.parameters end diff --git a/lib/content_type.mli b/lib/content_type.mli index 6d4f599..879cda0 100644 --- a/lib/content_type.mli +++ b/lib/content_type.mli @@ -78,7 +78,6 @@ module Type : sig val is_discrete : t -> bool val is_multipart : t -> bool val is_message : t -> bool - val to_string : t -> string end @@ -115,7 +114,7 @@ module Subtype : sig end module Parameters : sig - module Map : module type of Map.Make(String) + module Map : module type of Map.Make (String) type key = string (** Type of parameter key. *) @@ -185,10 +184,11 @@ module Parameters : sig val to_list : t -> (key * value) list end -type t = - { ty : Type.t - ; subty : Subtype.t - ; parameters : (string * Parameters.value) list } +type t = { + ty : Type.t; + subty : Subtype.t; + parameters : (string * Parameters.value) list; +} (** Type of Content-Type value. *) val default : t @@ -209,11 +209,9 @@ val parameters : t -> (Parameters.key * Parameters.value) list val is_discrete : t -> bool val is_multipart : t -> bool val is_message : t -> bool - val with_type : t -> Type.t -> t val with_subtype : t -> Subtype.t -> t -val with_parameter : t -> (Parameters.key * Parameters.value) -> t - +val with_parameter : t -> Parameters.key * Parameters.value -> t val boundary : t -> string option (** {2 Pretty-printers.} *) diff --git a/lib/date.ml b/lib/date.ml index 82fcd17..74356b5 100644 --- a/lib/date.ml +++ b/lib/date.ml @@ -1,8 +1,5 @@ module Day = struct - type t = - | Mon | Tue | Wed - | Thu | Fri | Sat - | Sun + type t = Mon | Tue | Wed | Thu | Fri | Sat | Sun let mon = Mon let tue = Tue @@ -13,13 +10,13 @@ module Day = struct let sun = Sun let pp ppf = function - | Mon -> Fmt.pf ppf "Mon" - | Tue -> Fmt.pf ppf "Tue" - | Wed -> Fmt.pf ppf "Wed" - | Thu -> Fmt.pf ppf "Thu" - | Fri -> Fmt.pf ppf "Fri" - | Sat -> Fmt.pf ppf "Sat" - | Sun -> Fmt.pf ppf "Sun" + | Mon -> Fmt.pf ppf "Mon" + | Tue -> Fmt.pf ppf "Tue" + | Wed -> Fmt.pf ppf "Wed" + | Thu -> Fmt.pf ppf "Thu" + | Fri -> Fmt.pf ppf "Fri" + | Sat -> Fmt.pf ppf "Sat" + | Sun -> Fmt.pf ppf "Sun" let to_string = Fmt.to_to_string pp @@ -33,13 +30,13 @@ module Day = struct | "Sun" -> Ok Sun | x -> Rresult.R.error_msgf "invalid day %s" x - let of_string_exn x = match of_string x with - | Ok v -> v - | Error (`Msg err) -> invalid_arg err + let of_string_exn x = + match of_string x with Ok v -> v | Error (`Msg err) -> invalid_arg err let v x = of_string_exn x - let equal a b = match a, b with + let equal a b = + match (a, b) with | Mon, Mon -> true | Tue, Tue -> true | Wed, Wed -> true @@ -52,8 +49,18 @@ end module Month = struct type t = - | Jan | Feb | Mar | Apr | May | Jun - | Jul | Aug | Sep | Oct | Nov | Dec + | Jan + | Feb + | Mar + | Apr + | May + | Jun + | Jul + | Aug + | Sep + | Oct + | Nov + | Dec let jan = Jan let feb = Feb @@ -68,7 +75,8 @@ module Month = struct let nov = Nov let dec = Dec - let equal a b = match a, b with + let equal a b = + match (a, b) with | Jan, Jan -> true | Feb, Feb -> true | Mar, Mar -> true @@ -84,26 +92,32 @@ module Month = struct | _, _ -> false let pp ppf = function - | Jan -> Fmt.pf ppf "Jan" - | Feb -> Fmt.pf ppf "Feb" - | Mar -> Fmt.pf ppf "Mar" - | Apr -> Fmt.pf ppf "Apr" - | May -> Fmt.pf ppf "May" - | Jun -> Fmt.pf ppf "Jun" - | Jul -> Fmt.pf ppf "Jul" - | Aug -> Fmt.pf ppf "Aug" - | Sep -> Fmt.pf ppf "Sep" - | Oct -> Fmt.pf ppf "Oct" - | Nov -> Fmt.pf ppf "Nov" - | Dec -> Fmt.pf ppf "Dec" + | Jan -> Fmt.pf ppf "Jan" + | Feb -> Fmt.pf ppf "Feb" + | Mar -> Fmt.pf ppf "Mar" + | Apr -> Fmt.pf ppf "Apr" + | May -> Fmt.pf ppf "May" + | Jun -> Fmt.pf ppf "Jun" + | Jul -> Fmt.pf ppf "Jul" + | Aug -> Fmt.pf ppf "Aug" + | Sep -> Fmt.pf ppf "Sep" + | Oct -> Fmt.pf ppf "Oct" + | Nov -> Fmt.pf ppf "Nov" + | Dec -> Fmt.pf ppf "Dec" let to_int = function - | Jan -> 1 | Feb -> 2 - | Mar -> 3 | Apr -> 4 - | May -> 5 | Jun -> 6 - | Jul -> 7 | Aug -> 8 - | Sep -> 9 | Oct -> 10 - | Nov -> 11 | Dec -> 12 + | Jan -> 1 + | Feb -> 2 + | Mar -> 3 + | Apr -> 4 + | May -> 5 + | Jun -> 6 + | Jul -> 7 + | Aug -> 8 + | Sep -> 9 + | Oct -> 10 + | Nov -> 11 + | Dec -> 12 let of_int = function | 1 -> Ok Jan @@ -120,9 +134,8 @@ module Month = struct | 12 -> Ok Dec | n -> Rresult.R.error_msgf "Invalid number of month: %d" n - let of_int_exn x = match of_int x with - | Ok v -> v - | Error (`Msg err) -> invalid_arg err + let of_int_exn x = + match of_int x with Ok v -> v | Error (`Msg err) -> invalid_arg err let to_string = Fmt.to_to_string pp @@ -141,24 +154,29 @@ module Month = struct | "Dec" -> Ok Dec | x -> Rresult.R.error_msgf "Invalid month %s" x - let of_string_exn x = match of_string x with - | Ok v -> v - | Error (`Msg err) -> invalid_arg err + let of_string_exn x = + match of_string x with Ok v -> v | Error (`Msg err) -> invalid_arg err let v x = of_string_exn x end module Zone = struct type t = - | UT | GMT - | EST | EDT - | CST | CDT - | MST | MDT - | PST | PDT + | UT + | GMT + | EST + | EDT + | CST + | CDT + | MST + | MDT + | PST + | PDT | Military_zone of char | TZ of int * int - let equal a b = match a, b with + let equal a b = + match (a, b) with | UT, UT -> true | GMT, GMT -> true | EST, EST -> true @@ -185,96 +203,108 @@ module Zone = struct let pdt = PDT let is_military_zone = function - | '\065' .. '\073' | '\075' .. '\090' | '\097' .. '\105' | '\107' .. '\122' -> true + | '\065' .. '\073' | '\075' .. '\090' | '\097' .. '\105' | '\107' .. '\122' + -> + true | _ -> false let military_zone = function | ('A' .. 'I' | 'K' .. 'Z') as chr -> Ok (Military_zone chr) | ('a' .. 'i' | 'k' .. 'z') as chr -> - let chr = Char.chr (Char.code chr - 32) in - Ok (Military_zone chr) + let chr = Char.chr (Char.code chr - 32) in + Ok (Military_zone chr) | chr -> Rresult.R.error_msgf "Invalid military zone '%c'" chr let tz hh mm = - if (abs hh) >= 0 && (abs hh) < 24 - && mm >= 0 && mm < 60 - then Ok (TZ (hh, mm)) + if abs hh >= 0 && abs hh < 24 && mm >= 0 && mm < 60 then Ok (TZ (hh, mm)) else Rresult.R.error_msgf "Invalid time-zone (hours: %d, minutes: %d)" hh mm let pp ppf = function - | UT -> Fmt.pf ppf "UT" - | GMT -> Fmt.pf ppf "GMT" - | EST -> Fmt.pf ppf "EST" - | EDT -> Fmt.pf ppf "EDT" - | CST -> Fmt.pf ppf "CST" - | CDT -> Fmt.pf ppf "CDT" - | MST -> Fmt.pf ppf "MST" - | MDT -> Fmt.pf ppf "MDT" - | PST -> Fmt.pf ppf "PST" - | PDT -> Fmt.pf ppf "PDT" + | UT -> Fmt.pf ppf "UT" + | GMT -> Fmt.pf ppf "GMT" + | EST -> Fmt.pf ppf "EST" + | EDT -> Fmt.pf ppf "EDT" + | CST -> Fmt.pf ppf "CST" + | CDT -> Fmt.pf ppf "CDT" + | MST -> Fmt.pf ppf "MST" + | MDT -> Fmt.pf ppf "MDT" + | PST -> Fmt.pf ppf "PST" + | PDT -> Fmt.pf ppf "PDT" | TZ (hh, mm) -> Fmt.pf ppf "(TZ %02d%02d)" hh mm | Military_zone c -> Fmt.pf ppf "(Military_zone %c)" c let to_string = function - | TZ (hh, mm) -> if hh >= 0 then Fmt.strf "+%02d%02d" hh mm else Fmt.strf "-%02d%02d" (abs hh) mm + | TZ (hh, mm) -> + if hh >= 0 then Fmt.strf "+%02d%02d" hh mm + else Fmt.strf "-%02d%02d" (abs hh) mm | Military_zone c -> String.make 1 c | x -> Fmt.to_to_string pp x let to_int = function - | UT | GMT -> 00, 00 - | EST -> -05, 00 - | EDT -> -04, 00 - | CST -> -06, 00 - | CDT -> -05, 00 - | MST -> -07, 00 - | MDT -> -06, 00 - | PST -> -08, 00 - | PDT -> -07, 00 - | TZ (hh, mm) -> hh, mm - | Military_zone c -> match c with - | 'A' -> 01, 00 - | 'B' -> 02, 00 - | 'C' -> 03, 00 - | 'D' -> 04, 00 - | 'E' -> 05, 00 - | 'F' -> 06, 00 - | 'G' -> 07, 00 - | 'H' -> 08, 00 - | 'I' -> 09, 00 - | 'K' -> 10, 00 - | 'L' -> 11, 00 - | 'M' -> 12, 00 - | 'N' -> -01, 00 - | 'O' -> -02, 00 - | 'P' -> -03, 00 - | 'Q' -> -04, 00 - | 'R' -> -05, 00 - | 'S' -> -06, 00 - | 'T' -> -07, 00 - | 'U' -> -08, 00 - | 'V' -> -09, 00 - | 'W' -> -10, 00 - | 'X' -> -11, 00 - | 'Y' -> -12, 00 - | 'Z' -> 00, 00 - | c -> Fmt.invalid_arg "Invalid military zone %c" c + | UT | GMT -> (00, 00) + | EST -> (-05, 00) + | EDT -> (-04, 00) + | CST -> (-06, 00) + | CDT -> (-05, 00) + | MST -> (-07, 00) + | MDT -> (-06, 00) + | PST -> (-08, 00) + | PDT -> (-07, 00) + | TZ (hh, mm) -> (hh, mm) + | Military_zone c -> ( + match c with + | 'A' -> (01, 00) + | 'B' -> (02, 00) + | 'C' -> (03, 00) + | 'D' -> (04, 00) + | 'E' -> (05, 00) + | 'F' -> (06, 00) + | 'G' -> (07, 00) + | 'H' -> (08, 00) + | 'I' -> (09, 00) + | 'K' -> (10, 00) + | 'L' -> (11, 00) + | 'M' -> (12, 00) + | 'N' -> (-01, 00) + | 'O' -> (-02, 00) + | 'P' -> (-03, 00) + | 'Q' -> (-04, 00) + | 'R' -> (-05, 00) + | 'S' -> (-06, 00) + | 'T' -> (-07, 00) + | 'U' -> (-08, 00) + | 'V' -> (-09, 00) + | 'W' -> (-10, 00) + | 'X' -> (-11, 00) + | 'Y' -> (-12, 00) + | 'Z' -> (00, 00) + | c -> Fmt.invalid_arg "Invalid military zone %c" c) let parser_tz = let open Angstrom in let is_digit = function '0' .. '9' -> true | _ -> false in option '+' (satisfy (function '+' | '-' -> true | _ -> false)) - >>= fun sign -> satisfy is_digit - >>= fun z0 -> satisfy is_digit - >>= fun z1 -> satisfy is_digit - >>= fun z2 -> satisfy is_digit - >>= fun z3 -> - let one = let res = Bytes.create 2 in Bytes.set res 0 z0 ; Bytes.set res 1 z1 ; Bytes.unsafe_to_string res in - let two = let res = Bytes.create 2 in Bytes.set res 0 z2 ; Bytes.set res 1 z3 ; Bytes.unsafe_to_string res in - let one = if sign = '-' then - int_of_string one else int_of_string one in + >>= fun sign -> + satisfy is_digit >>= fun z0 -> + satisfy is_digit >>= fun z1 -> + satisfy is_digit >>= fun z2 -> + satisfy is_digit >>= fun z3 -> + let one = + let res = Bytes.create 2 in + Bytes.set res 0 z0; + Bytes.set res 1 z1; + Bytes.unsafe_to_string res + in + let two = + let res = Bytes.create 2 in + Bytes.set res 0 z2; + Bytes.set res 1 z3; + Bytes.unsafe_to_string res + in + let one = if sign = '-' then -int_of_string one else int_of_string one in let two = int_of_string two in - if (abs one) >= 0 && (abs one) < 24 - && two >= 0 && two < 60 - then return (one, two) + if abs one >= 0 && abs one < 24 && two >= 0 && two < 60 then + return (one, two) else fail "Invalid time-zone" let of_string = function @@ -288,38 +318,48 @@ module Zone = struct | "MDT" -> Ok MDT | "PST" -> Ok PST | "PDT" -> Ok PDT - | x -> - match Angstrom.parse_string ~consume:Angstrom.Consume.All parser_tz x with - | Ok (hh, mm) -> Ok (TZ (hh, mm)) - | Error _ -> - if String.length x = 1 && is_military_zone x.[0] - then Ok (Military_zone x.[0]) - else Rresult.R.error_msgf "Invalid time-zone: %S" x - - let of_string_exn x = match of_string x with - | Ok v -> v - | Error (`Msg err) -> invalid_arg err + | x -> ( + match + Angstrom.parse_string ~consume:Angstrom.Consume.All parser_tz x + with + | Ok (hh, mm) -> Ok (TZ (hh, mm)) + | Error _ -> + if String.length x = 1 && is_military_zone x.[0] then + Ok (Military_zone x.[0]) + else Rresult.R.error_msgf "Invalid time-zone: %S" x) + + let of_string_exn x = + match of_string x with Ok v -> v | Error (`Msg err) -> invalid_arg err let v x = of_string_exn x end -type t = - { day : Day.t option - ; date : int * Month.t * int - ; time : int * int * int option - ; zone : Zone.t } +type t = { + day : Day.t option; + date : int * Month.t * int; + time : int * int * int option; + zone : Zone.t; +} let pp_ptime_day = let f = function - | `Mon -> Day.Mon | `Thu -> Day.Thu | `Tue -> Day.Tue - | `Wed -> Day.Wed | `Fri -> Day.Fri | `Sat -> Day.Sat | `Sun -> Day.Sun in + | `Mon -> Day.Mon + | `Thu -> Day.Thu + | `Tue -> Day.Tue + | `Wed -> Day.Wed + | `Fri -> Day.Fri + | `Sat -> Day.Sat + | `Sun -> Day.Sun + in Fmt.using f Day.pp let make ?day (y, m, d) (hh, mm, ss) zone = let z = let hh, mm = Zone.to_int zone in - hh * 3600 + mm * 60 in - let same_day a ptime_b = match a, ptime_b with + (hh * 3600) + (mm * 60) + in + let same_day a ptime_b = + match (a, ptime_b) with | Day.Mon, `Mon -> true | Day.Thu, `Thu -> true | Day.Tue, `Tue -> true @@ -327,44 +367,52 @@ let make ?day (y, m, d) (hh, mm, ss) zone = | Day.Fri, `Fri -> true | Day.Sat, `Sat -> true | Day.Sun, `Sun -> true - | _, _ -> false in + | _, _ -> false + in let m' = Month.to_int m in - match Ptime.of_date_time ((y, m', d), ((hh, mm, Option.value ~default:0 ss), z)) with + match + Ptime.of_date_time ((y, m', d), ((hh, mm, Option.value ~default:0 ss), z)) + with | None -> Rresult.R.error_msgf "Invalid date" - | Some t -> - let day' = Ptime.weekday ~tz_offset_s:z t in - - match day with - | None -> Ok { day= None; date= (d, m, y); time= (hh, mm, ss); zone } - | Some day -> - if same_day day day' - then Ok { day= Some day; date= (d, m, y); time= (hh, mm, ss); zone } - else Rresult.R.error_msgf "Expected day mismatch (%a <> %a)" Day.pp day pp_ptime_day day' + | Some t -> ( + let day' = Ptime.weekday ~tz_offset_s:z t in + + match day with + | None -> Ok { day = None; date = (d, m, y); time = (hh, mm, ss); zone } + | Some day -> + if same_day day day' then + Ok { day = Some day; date = (d, m, y); time = (hh, mm, ss); zone } + else + Rresult.R.error_msgf "Expected day mismatch (%a <> %a)" Day.pp day + pp_ptime_day day') let pp ppf = function - | { day = Some day; date = (d, m, y); time = (hh, mm, ss); zone; } -> - Fmt.pf ppf "{@[day = %a;@ \ - date = (@[%d,@ %a,@ %d@]);@ \ - time = (@[%d,@ %d,@ %d@]);@ \ - zone = %a@]}" - Day.pp day d Month.pp m y hh mm (Option.value ~default:0 ss) - Zone.pp zone - | { day = None; date = (d, m, y); time = (hh, mm, ss); zone; } -> - Fmt.pf ppf "{@[date = (@[%d,@ %a,@ %d@]);@ \ - time = (@[%d,@ %d,@ %d@]);@ \ - zone = %a@]}" - d Month.pp m y hh mm (Option.value ~default:0 ss) - Zone.pp zone + | { day = Some day; date = d, m, y; time = hh, mm, ss; zone } -> + Fmt.pf ppf + "{@[day = %a;@ date = (@[%d,@ %a,@ %d@]);@ time = \ + (@[%d,@ %d,@ %d@]);@ zone = %a@]}" + Day.pp day d Month.pp m y hh mm + (Option.value ~default:0 ss) + Zone.pp zone + | { day = None; date = d, m, y; time = hh, mm, ss; zone } -> + Fmt.pf ppf + "{@[date = (@[%d,@ %a,@ %d@]);@ time = (@[%d,@ %d,@ \ + %d@]);@ zone = %a@]}" + d Month.pp m y hh mm + (Option.value ~default:0 ss) + Zone.pp zone let to_ptime date = let z = let hh, mm = Zone.to_int date.zone in - hh * 3600 + mm * 60 in + (hh * 3600) + (mm * 60) + in let m = - let (_, m, _) = date.date in - Month.to_int m in - let (d, _, y) = date.date in - let (hh, mm, ss) = date.time in + let _, m, _ = date.date in + Month.to_int m + in + let d, _, y = date.date in + let hh, mm, ss = date.time in let ss = Option.value ~default:0 ss in match Ptime.of_date_time ((y, m, d), ((hh, mm, ss), z)) with | Some ptime -> Ok (ptime, z) @@ -372,18 +420,21 @@ let to_ptime date = let of_ptime ~zone ptime = let tz_offset_s = - let (hh, mm) = Zone.to_int zone in - hh * 3600 + mm * 60 in + let hh, mm = Zone.to_int zone in + (hh * 3600) + (mm * 60) + in let (y, m, d), ((hh, mm, ss), _) = Ptime.to_date_time ~tz_offset_s ptime in - let date = (y, (Month.of_int_exn m), d) in - let day = match Ptime.weekday ~tz_offset_s ptime with + let date = (y, Month.of_int_exn m, d) in + let day = + match Ptime.weekday ~tz_offset_s ptime with | `Mon -> Day.Mon | `Tue -> Day.Tue | `Wed -> Day.Wed | `Thu -> Day.Thu | `Fri -> Day.Fri | `Sat -> Day.Sat - | `Sun -> Day.Sun in + | `Sun -> Day.Sun + in match make ~day date (hh, mm, Some ss) zone with | Ok date_time -> date_time | Error _ -> assert false @@ -392,12 +443,14 @@ let of_ptime ~zone ptime = is when we define a [?day] and it does not correspond to what [Ptime] expecs. Of course, this call, we did not notice [?day]. *) -let compare a b = match to_ptime a, to_ptime b with +let compare a b = + match (to_ptime a, to_ptime b) with | Ok (a, _tz_a), Ok (b, _tz_b) -> Ptime.compare a b | Error (`Msg err), _ -> failwith err | _, Error (`Msg err) -> failwith err -let equal_option equal a b = match a, b with +let equal_option equal a b = + match (a, b) with | Some a, Some b -> equal a b | None, None -> true | _, _ -> false @@ -426,36 +479,34 @@ module Decoder = struct lift2 (fun a b -> let res = Bytes.create 2 in - Bytes.unsafe_set res 0 a ; - Bytes.unsafe_set res 1 b ; - Bytes.unsafe_to_string res ) + Bytes.unsafe_set res 0 a; + Bytes.unsafe_set res 1 b; + Bytes.unsafe_to_string res) (satisfy is_digit) (satisfy is_digit) let four_digit = lift4 (fun a b c d -> let res = Bytes.create 4 in - Bytes.unsafe_set res 0 a ; - Bytes.unsafe_set res 1 b ; - Bytes.unsafe_set res 2 c ; - Bytes.unsafe_set res 3 d ; - Bytes.unsafe_to_string res ) - (satisfy is_digit) (satisfy is_digit) (satisfy is_digit) (satisfy is_digit) + Bytes.unsafe_set res 0 a; + Bytes.unsafe_set res 1 b; + Bytes.unsafe_set res 2 c; + Bytes.unsafe_set res 3 d; + Bytes.unsafe_to_string res) + (satisfy is_digit) (satisfy is_digit) (satisfy is_digit) + (satisfy is_digit) let at_least_n_digit n = - take_while1 is_digit - >>= fun res -> + take_while1 is_digit >>= fun res -> if String.length res >= n then return res else fail "at_least_n_digit" let one_or_two_digit = - satisfy is_digit - >>= fun one -> - peek_char - >>= function + satisfy is_digit >>= fun one -> + peek_char >>= function | Some two when is_digit two -> let res = Bytes.create 2 in - Bytes.unsafe_set res 0 one ; - Bytes.unsafe_set res 1 two ; + Bytes.unsafe_set res 0 one; + Bytes.unsafe_set res 1 two; advance 1 *> return (Bytes.unsafe_to_string res) | _ -> return (String.make 1 one) @@ -478,7 +529,8 @@ module Decoder = struct obs-minute = [CFWS] 2DIGIT [CFWS] *) - let obs_minute = skip_while is_wsp *> two_digit <* skip_while is_wsp >>| int_of_string + let obs_minute = + skip_while is_wsp *> two_digit <* skip_while is_wsp >>| int_of_string (* From RFC 2822 @@ -488,7 +540,8 @@ module Decoder = struct obs-second = [CFWS] 2DIGIT [CFWS] *) - let obs_second = skip_while is_wsp *> two_digit <* skip_while is_wsp >>| int_of_string + let obs_second = + skip_while is_wsp *> two_digit <* skip_while is_wsp >>| int_of_string (* From RFC 2822 @@ -518,8 +571,9 @@ module Decoder = struct second = 2DIGIT / obs-second *) - let second = obs_second <|> (two_digit >>| int_of_string) >>= fun res -> - (option "" (char '.' *> take_while1 is_digit)) >>= fun _ns -> return res + let second = + obs_second <|> (two_digit >>| int_of_string) >>= fun res -> + option "" (char '.' *> take_while1 is_digit) >>= fun _ns -> return res (* XXX(dinosaure): On [Received] field, the date can have nano-second. Such * value does not follow any standards but we must consume it to be able to * parse then zone value. It's an hot-fix to be able to accept several wrong @@ -571,7 +625,10 @@ module Decoder = struct 16. Three digit years interpreted, but not allowed for generation.* *) let year = - skip_while is_wsp *> at_least_n_digit 4 <* skip_while is_wsp >>| int_of_string <|> obs_year + skip_while is_wsp *> at_least_n_digit 4 + <* skip_while is_wsp + >>| int_of_string + <|> obs_year (* From RFC 2822 @@ -582,8 +639,7 @@ module Decoder = struct obs-day = [CFWS] 1*2DIGIT [CFWS] *) let obs_day = - skip_while is_wsp *> one_or_two_digit <* skip_while is_wsp - >>| int_of_string + skip_while is_wsp *> one_or_two_digit <* skip_while is_wsp >>| int_of_string (* From RFC 2822 @@ -595,9 +651,9 @@ module Decoder = struct *) let day = obs_day - <|> ( skip_while is_wsp *> one_or_two_digit + <|> (skip_while is_wsp *> one_or_two_digit <* skip_while is_wsp - >>| int_of_string ) + >>| int_of_string) (* From RFC 822 @@ -665,8 +721,7 @@ module Decoder = struct obs-day-of-week = [CFWS] day-name [CFWS] *) - let obs_day_of_week = - skip_while is_wsp *> day_name <* skip_while is_wsp + let obs_day_of_week = skip_while is_wsp *> day_name <* skip_while is_wsp (* From RFC 2822 @@ -676,8 +731,7 @@ module Decoder = struct day-of-week = ([FWS] day-name) / obs-day-of-week *) - let day_of_week = - obs_day_of_week <|> skip_while is_wsp *> day_name + let day_of_week = obs_day_of_week <|> skip_while is_wsp *> day_name (* From RFC 822 @@ -692,7 +746,10 @@ module Decoder = struct date = day month year *) - let date = lift3 (fun day month year -> (day, month, year)) (day "day") (month "month") (year "year") + let date = + lift3 + (fun day month year -> (day, month, year)) + (day "day") (month "month") (year "year") (* From RFC 822 @@ -707,142 +764,141 @@ module Decoder = struct time-of-day = hour ":" minute [ ":" second ] *) let time_of_day = - hour "hour" - >>= fun hour -> - char ':' *> minute "minute" - >>= fun minute -> + hour "hour" >>= fun hour -> + char ':' *> minute "minute" >>= fun minute -> option None - (skip_while is_wsp *> char ':' *> second "second" >>| fun second -> Some second) + ( skip_while is_wsp *> char ':' *> second "second" >>| fun second -> + Some second ) >>| fun second -> (hour, minute, second) -(* From RFC 822 - - zone = "UT" / "GMT" ; Universal Time - ; North American : UT - / "EST" / "EDT" ; Eastern: - 5/ - 4 - / "CST" / "CDT" ; Central: - 6/ - 5 - / "MST" / "MDT" ; Mountain: - 7/ - 6 - / "PST" / "PDT" ; Pacific: - 8/ - 7 - / 1ALPHA ; Military: Z = UT; - ; A:-1; (J not used) - ; M:-12; N:+1; Y:+12 - / ( ("+" / "-") 4DIGIT ) ; Local differential - ; hours+min. (HHMM) - - Time zone may be indicated in several ways. "UT" is Univer- - sal Time (formerly called "Greenwich Mean Time"); "GMT" is per- - mitted as a reference to Universal Time. The military standard - uses a single character for each zone. "Z" is Universal Time. - "A" indicates one hour earlier, and "M" indicates 12 hours ear- - lier; "N" is one hour later, and "Y" is 12 hours later. The - letter "J" is not used. The other remaining two forms are taken - from ANSI standard X3.51-1975. One allows explicit indication of - the amount of offset from UT; the other uses common 3-character - strings for indicating time zones in North America. - - From RFC 2822 - - obs-zone = "UT" / "GMT" / ; Universal Time - "EST" / "EDT" / ; Eastern: - 5/ - 4 - "CST" / "CDT" / ; Central: - 6/ - 5 - "MST" / "MDT" / ; Mountain: - 7/ - 6 - "PST" / "PDT" / ; Pacific: - 8/ - 7 - ; - %d65-73 / ; Military zones - "A" - %d75-90 / ; through "I" and "K" - %d97-105 / ; through "Z", both - %d107-122 ; upper and lower case - - In the obsolete time zone, "UT" and "GMT" are indications of - "Universal Time" and "Greenwich Mean Time" respectively and are both - semantically identical to "+0000". - - The remaining three character zones are the US time zones. The first - letter, "E", "C", "M", or "P" stands for "Eastern", "Central", - "Mountain" and "Pacific". The second letter is either "S" for - "Standard" time, or "D" for "Daylight" (or summer) time. Their - interpretations are as follows: - - EDT is semantically equivalent to -0400 - EST is semantically equivalent to -0500 - CDT is semantically equivalent to -0500 - CST is semantically equivalent to -0600 - MDT is semantically equivalent to -0600 - MST is semantically equivalent to -0700 - PDT is semantically equivalent to -0700 - PST is semantically equivalent to -0800 - - The 1 character military time zones were defined in a non-standard - way in [RFC822] and are therefore unpredictable in their meaning. - The original definitions of the military zones "A" through "I" are - equivalent to "+0100" through "+0900" respectively; "K", "L", and "M" - are equivalent to "+1000", "+1100", and "+1200" respectively; "N" - through "Y" are equivalent to "-0100" through "-1200" respectively; - and "Z" is equivalent to "+0000". However, because of the error in - [RFC822], they SHOULD all be considered equivalent to "-0000" unless - there is out-of-band information confirming their meaning. - - Other multi-character (usually between 3 and 5) alphabetic time zones - have been used in Internet messages. Any such time zone whose - meaning is not known SHOULD be considered equivalent to "-0000" - unless there is out-of-band information confirming their meaning. - - From RFC 5322 - - obs-zone = "UT" / "GMT" / ; Universal Time - ; North American UT - ; offsets - "EST" / "EDT" / ; Eastern: - 5/ - 4 - "CST" / "CDT" / ; Central: - 6/ - 5 - "MST" / "MDT" / ; Mountain: - 7/ - 6 - "PST" / "PDT" / ; Pacific: - 8/ - 7 - ; - %d65-73 / ; Military zones - "A" - %d75-90 / ; through "I" and "K" - %d97-105 / ; through "Z", both - %d107-122 ; upper and lower case - - Where a two or three digit year occurs in a date, the year is to be - interpreted as follows: If a two digit year is encountered whose - value is between 00 and 49, the year is interpreted by adding 2000, - ending up with a value between 2000 and 2049. If a two digit year is - encountered with a value between 50 and 99, or any three digit year - is encountered, the year is interpreted by adding 1900. - - In the obsolete time zone, "UT" and "GMT" are indications of - "Universal Time" and "Greenwich Mean Time", respectively, and are - both semantically identical to "+0000". - - The remaining three character zones are the US time zones. The first - letter, "E", "C", "M", or "P" stands for "Eastern", "Central", - "Mountain", and "Pacific". The second letter is either "S" for - "Standard" time, or "D" for "Daylight Savings" (or summer) time. - Their interpretations are as follows: - - EDT is semantically equivalent to -0400 - EST is semantically equivalent to -0500 - CDT is semantically equivalent to -0500 - CST is semantically equivalent to -0600 - MDT is semantically equivalent to -0600 - MST is semantically equivalent to -0700 - PDT is semantically equivalent to -0700 - PST is semantically equivalent to -0800 + (* From RFC 822 + + zone = "UT" / "GMT" ; Universal Time + ; North American : UT + / "EST" / "EDT" ; Eastern: - 5/ - 4 + / "CST" / "CDT" ; Central: - 6/ - 5 + / "MST" / "MDT" ; Mountain: - 7/ - 6 + / "PST" / "PDT" ; Pacific: - 8/ - 7 + / 1ALPHA ; Military: Z = UT; + ; A:-1; (J not used) + ; M:-12; N:+1; Y:+12 + / ( ("+" / "-") 4DIGIT ) ; Local differential + ; hours+min. (HHMM) + + Time zone may be indicated in several ways. "UT" is Univer- + sal Time (formerly called "Greenwich Mean Time"); "GMT" is per- + mitted as a reference to Universal Time. The military standard + uses a single character for each zone. "Z" is Universal Time. + "A" indicates one hour earlier, and "M" indicates 12 hours ear- + lier; "N" is one hour later, and "Y" is 12 hours later. The + letter "J" is not used. The other remaining two forms are taken + from ANSI standard X3.51-1975. One allows explicit indication of + the amount of offset from UT; the other uses common 3-character + strings for indicating time zones in North America. + + From RFC 2822 + + obs-zone = "UT" / "GMT" / ; Universal Time + "EST" / "EDT" / ; Eastern: - 5/ - 4 + "CST" / "CDT" / ; Central: - 6/ - 5 + "MST" / "MDT" / ; Mountain: - 7/ - 6 + "PST" / "PDT" / ; Pacific: - 8/ - 7 + ; + %d65-73 / ; Military zones - "A" + %d75-90 / ; through "I" and "K" + %d97-105 / ; through "Z", both + %d107-122 ; upper and lower case + + In the obsolete time zone, "UT" and "GMT" are indications of + "Universal Time" and "Greenwich Mean Time" respectively and are both + semantically identical to "+0000". + + The remaining three character zones are the US time zones. The first + letter, "E", "C", "M", or "P" stands for "Eastern", "Central", + "Mountain" and "Pacific". The second letter is either "S" for + "Standard" time, or "D" for "Daylight" (or summer) time. Their + interpretations are as follows: + + EDT is semantically equivalent to -0400 + EST is semantically equivalent to -0500 + CDT is semantically equivalent to -0500 + CST is semantically equivalent to -0600 + MDT is semantically equivalent to -0600 + MST is semantically equivalent to -0700 + PDT is semantically equivalent to -0700 + PST is semantically equivalent to -0800 The 1 character military time zones were defined in a non-standard - way in [RFC0822] and are therefore unpredictable in their meaning. + way in [RFC822] and are therefore unpredictable in their meaning. The original definitions of the military zones "A" through "I" are - equivalent to "+0100" through "+0900", respectively; "K", "L", and - "M" are equivalent to "+1000", "+1100", and "+1200", respectively; - "N" through "Y" are equivalent to "-0100" through "-1200". - respectively; and "Z" is equivalent to "+0000". However, because of - the error in [RFC0822], they SHOULD all be considered equivalent to - "-0000" unless there is out-of-band information confirming their - meaning. + equivalent to "+0100" through "+0900" respectively; "K", "L", and "M" + are equivalent to "+1000", "+1100", and "+1200" respectively; "N" + through "Y" are equivalent to "-0100" through "-1200" respectively; + and "Z" is equivalent to "+0000". However, because of the error in + [RFC822], they SHOULD all be considered equivalent to "-0000" unless + there is out-of-band information confirming their meaning. Other multi-character (usually between 3 and 5) alphabetic time zones have been used in Internet messages. Any such time zone whose meaning is not known SHOULD be considered equivalent to "-0000" unless there is out-of-band information confirming their meaning. + + From RFC 5322 + + obs-zone = "UT" / "GMT" / ; Universal Time + ; North American UT + ; offsets + "EST" / "EDT" / ; Eastern: - 5/ - 4 + "CST" / "CDT" / ; Central: - 6/ - 5 + "MST" / "MDT" / ; Mountain: - 7/ - 6 + "PST" / "PDT" / ; Pacific: - 8/ - 7 + ; + %d65-73 / ; Military zones - "A" + %d75-90 / ; through "I" and "K" + %d97-105 / ; through "Z", both + %d107-122 ; upper and lower case + + Where a two or three digit year occurs in a date, the year is to be + interpreted as follows: If a two digit year is encountered whose + value is between 00 and 49, the year is interpreted by adding 2000, + ending up with a value between 2000 and 2049. If a two digit year is + encountered with a value between 50 and 99, or any three digit year + is encountered, the year is interpreted by adding 1900. + + In the obsolete time zone, "UT" and "GMT" are indications of + "Universal Time" and "Greenwich Mean Time", respectively, and are + both semantically identical to "+0000". + + The remaining three character zones are the US time zones. The first + letter, "E", "C", "M", or "P" stands for "Eastern", "Central", + "Mountain", and "Pacific". The second letter is either "S" for + "Standard" time, or "D" for "Daylight Savings" (or summer) time. + Their interpretations are as follows: + + EDT is semantically equivalent to -0400 + EST is semantically equivalent to -0500 + CDT is semantically equivalent to -0500 + CST is semantically equivalent to -0600 + MDT is semantically equivalent to -0600 + MST is semantically equivalent to -0700 + PDT is semantically equivalent to -0700 + PST is semantically equivalent to -0800 + + The 1 character military time zones were defined in a non-standard + way in [RFC0822] and are therefore unpredictable in their meaning. + The original definitions of the military zones "A" through "I" are + equivalent to "+0100" through "+0900", respectively; "K", "L", and + "M" are equivalent to "+1000", "+1100", and "+1200", respectively; + "N" through "Y" are equivalent to "-0100" through "-1200". + respectively; and "Z" is equivalent to "+0000". However, because of + the error in [RFC0822], they SHOULD all be considered equivalent to + "-0000" unless there is out-of-band information confirming their + meaning. + + Other multi-character (usually between 3 and 5) alphabetic time zones + have been used in Internet messages. Any such time zone whose + meaning is not known SHOULD be considered equivalent to "-0000" + unless there is out-of-band information confirming their meaning. *) let obs_zone = string "UT" *> return Zone.UT @@ -855,7 +911,8 @@ module Decoder = struct <|> string "MDT" *> return Zone.MDT <|> string "PST" *> return Zone.PST <|> string "PDT" *> return Zone.PDT - <|> (satisfy Zone.is_military_zone >>= fun z -> return (Zone.Military_zone z)) + <|> ( satisfy Zone.is_military_zone >>= fun z -> + return (Zone.Military_zone z) ) (* From RFC 2822 @@ -902,14 +959,17 @@ module Decoder = struct mostly because this expected space is a part of [minute] or [second]. To avoid an error, [FWS] is optional but a better way should to check if we consumed at least one space before [zone]. *) - (skip_while is_wsp *> satisfy (function '+' | '-' -> true | _ -> false) "sign" - >>= (fun sign -> - four_digit "four-digit" - >>| fun zone -> - let one = if sign = '-' then - int_of_string (String.sub zone 0 2) else int_of_string (String.sub zone 0 2) in - let two = int_of_string (String.sub zone 2 2) in - Zone.TZ (one, two))) - <|> (skip_while is_wsp *> obs_zone) + skip_while is_wsp *> satisfy (function '+' | '-' -> true | _ -> false) + "sign" + >>= (fun sign -> + four_digit "four-digit" >>| fun zone -> + let one = + if sign = '-' then -int_of_string (String.sub zone 0 2) + else int_of_string (String.sub zone 0 2) + in + let two = int_of_string (String.sub zone 2 2) in + Zone.TZ (one, two)) + <|> skip_while is_wsp *> obs_zone (* From RFC 822 @@ -923,7 +983,11 @@ module Decoder = struct time = time-of-day zone *) - let time = lift2 (fun time zone -> (time, zone)) (time_of_day "time-of-day") (zone "zone") + let time = + lift2 + (fun time zone -> (time, zone)) + (time_of_day "time-of-day") + (zone "zone") (* From RFC 822 @@ -956,7 +1020,7 @@ module Decoder = struct *) let date_time = lift3 - (fun day date (time, zone) -> {day; date; time; zone}) + (fun day date (time, zone) -> { day; date; time; zone }) (option None (day_of_week >>= fun day -> char ',' *> return (Some day))) date time <* skip_while is_wsp @@ -973,20 +1037,28 @@ module Encoder = struct let time ppf (hours, minutes, seconds) = let string_of_number = Fmt.strf "%02d" in - let number ppf x = eval ppf [ cut; !!(using string_of_number string); cut ] x in + let number ppf x = + eval ppf [ cut; !!(using string_of_number string); cut ] x + in match seconds with | Some seconds -> - eval ppf [ tbox 1; !!number; char $ ':'; !!number; char $ ':'; !!number; close ] - hours minutes seconds + eval ppf + [ + tbox 1; !!number; char $ ':'; !!number; char $ ':'; !!number; close; + ] + hours minutes seconds | None -> - eval ppf [ tbox 1; !!number; char $ ':'; !!number; close ] - hours minutes + eval ppf [ tbox 1; !!number; char $ ':'; !!number; close ] hours minutes let zone = using Zone.to_string string let int = using string_of_int string let date ppf t = - let (d, m, y) = t.date in - eval ppf [ tbox 1; !!(option day); !!int; fws; !!month; fws; !!int; fws; !!time; fws; !!zone; close ] + let d, m, y = t.date in + eval ppf + [ + tbox 1; !!(option day); !!int; fws; !!month; fws; !!int; fws; !!time; + fws; !!zone; close; + ] t.day d m y t.time t.zone end diff --git a/lib/date.mli b/lib/date.mli index ca9d6e0..1d11aae 100644 --- a/lib/date.mli +++ b/lib/date.mli @@ -16,10 +16,13 @@ module Day : sig type t = - | Mon | Tue | Wed - | Thu | Fri | Sat - | Sun - (** Type of day according RFC 822 / RFC 2822 / RFC 5322. *) + | Mon + | Tue + | Wed + | Thu + | Fri + | Sat + | Sun (** Type of day according RFC 822 / RFC 2822 / RFC 5322. *) (** {3 Constructors.} *) @@ -58,9 +61,18 @@ end module Month : sig type t = - | Jan | Feb | Mar | Apr | May | Jun - | Jul | Aug | Sep | Oct | Nov | Dec - (** Type of month according RFC 822 / RFC 2822 / RFC 5322. *) + | Jan + | Feb + | Mar + | Apr + | May + | Jun + | Jul + | Aug + | Sep + | Oct + | Nov + | Dec (** Type of month according RFC 822 / RFC 2822 / RFC 5322. *) (** {3 Constructors.} *) @@ -115,14 +127,19 @@ end module Zone : sig type t = - | UT | GMT - | EST | EDT - | CST | CDT - | MST | MDT - | PST | PDT + | UT + | GMT + | EST + | EDT + | CST + | CDT + | MST + | MDT + | PST + | PDT | Military_zone of char | TZ of int * int - (** Type of zone according RFC 822 / RFC 2822 / RFC 5322. *) + (** Type of zone according RFC 822 / RFC 2822 / RFC 5322. *) (** {3 Constructors.} *) @@ -164,16 +181,22 @@ module Zone : sig val equal : t -> t -> bool end -type t = - { day : Day.t option - ; date : int * Month.t * int - ; time : int * int * int option - ; zone : Zone.t } +type t = { + day : Day.t option; + date : int * Month.t * int; + time : int * int * int option; + zone : Zone.t; +} (** Type of date according RFC 822 / RFC 2822 / RFC 5322. *) (** {2 Constructors.} *) -val make : ?day:Day.t -> (int * Month.t * int) -> (int * int * int option) -> Zone.t -> (t, [> Rresult.R.msg ]) result +val make : + ?day:Day.t -> + int * Month.t * int -> + int * int * int option -> + Zone.t -> + (t, [> Rresult.R.msg ]) result (** [make ?day (year, month, day) (hh, mm, ss) tz] returns a date corresponding to [month/day/year hh:mm:ss] date-time with time zone [tz]. [?day] (which is the day in the 7-day week) and [day] must correspond according of timestamp diff --git a/lib/dune b/lib/dune index ae83549..25c3170 100644 --- a/lib/dune +++ b/lib/dune @@ -1,4 +1,5 @@ (library (name mrmime) (public_name mrmime) - (libraries emile unstrctrd unstrctrd.parser base64 base64.rfc2045 pecu ptime ipaddr rosetta angstrom prettym ke uutf rresult fmt)) + (libraries emile unstrctrd unstrctrd.parser base64 base64.rfc2045 pecu ptime + ipaddr rosetta angstrom prettym ke uutf rresult fmt)) diff --git a/lib/encoded_word.ml b/lib/encoded_word.ml index c39bf18..4c2d8fa 100644 --- a/lib/encoded_word.ml +++ b/lib/encoded_word.ml @@ -1,10 +1,15 @@ type uutf_charset = [ `UTF_8 | `UTF_16 | `UTF_16BE | `UTF_16LE ] -type charset = [ Rosetta.encoding | uutf_charset | `US_ASCII | `Charset of string ] + +type charset = + [ Rosetta.encoding | uutf_charset | `US_ASCII | `Charset of string ] + type encoding = Quoted_printable | Base64 -type t = - { charset : charset - ; encoding : encoding - ; data : (string, Rresult.R.msg) result } + +type t = { + charset : charset; + encoding : encoding; + data : (string, Rresult.R.msg) result; +} exception Invalid_utf8 @@ -15,51 +20,62 @@ let is_utf8_valid_string x = try Uutf.String.fold_utf_8 (fun () _pos -> function `Malformed _ -> raise Invalid_utf8 | _ -> ()) - () x ; + () x; true with Invalid_utf8 -> false -let is_normalized {charset; _} = +let is_normalized { charset; _ } = match charset with `Charset _ -> false | _ -> true -let normalize_to_utf8 ~charset raw = match charset with +let normalize_to_utf8 ~charset raw = + match charset with | `US_ASCII -> Ok raw (* XXX(dinosaure): UTF-8 is a superset of US-ASCII *) | #Rosetta.encoding as charset -> - let bf = Buffer.create (String.length raw) in - let decoder = Rosetta.decoder charset (`String raw) in - let encoder = Uutf.encoder `UTF_8 (`Buffer bf) in - let rec go () = match Rosetta.decode decoder with - | `Malformed err -> Rresult.R.error_msg err - | `Await -> assert false - | `Uchar _ as uchar -> - let[@warning "-8"] `Ok : [ `Ok | `Partial ] = Uutf.encode encoder uchar in - go () - | `End as v -> - let[@warning "-8"] `Ok : [ `Ok | `Partial ] = Uutf.encode encoder v in - Ok (Buffer.contents bf) in - go () + let bf = Buffer.create (String.length raw) in + let decoder = Rosetta.decoder charset (`String raw) in + let encoder = Uutf.encoder `UTF_8 (`Buffer bf) in + let rec go () = + match Rosetta.decode decoder with + | `Malformed err -> Rresult.R.error_msg err + | `Await -> assert false + | `Uchar _ as uchar -> + let[@warning "-8"] (`Ok : [ `Ok | `Partial ]) = + Uutf.encode encoder uchar + in + go () + | `End as v -> + let[@warning "-8"] (`Ok : [ `Ok | `Partial ]) = + Uutf.encode encoder v + in + Ok (Buffer.contents bf) + in + go () | `UTF_8 -> Ok raw (* XXX(dinosaure): check? *) | #uutf_charset as charset -> - let bf = Buffer.create (String.length raw) in - let decoder = Uutf.decoder ~encoding:charset (`String raw) in - let encoder = Uutf.encoder `UTF_8 (`Buffer bf) in - let rec go () = match Uutf.decode decoder with - | `Malformed err -> Rresult.R.error_msg err - | `Await -> assert false - | `Uchar _ as uchar -> - let[@warning "-8"] `Ok : [ `Ok | `Partial ] = Uutf.encode encoder uchar in - go () - | `End as v -> - let[@warning "-8"] `Ok : [ `Ok | `Partial ] = Uutf.encode encoder v in - Ok (Buffer.contents bf) in - go () + let bf = Buffer.create (String.length raw) in + let decoder = Uutf.decoder ~encoding:charset (`String raw) in + let encoder = Uutf.encoder `UTF_8 (`Buffer bf) in + let rec go () = + match Uutf.decode decoder with + | `Malformed err -> Rresult.R.error_msg err + | `Await -> assert false + | `Uchar _ as uchar -> + let[@warning "-8"] (`Ok : [ `Ok | `Partial ]) = + Uutf.encode encoder uchar + in + go () + | `End as v -> + let[@warning "-8"] (`Ok : [ `Ok | `Partial ]) = + Uutf.encode encoder v + in + Ok (Buffer.contents bf) + in + go () | `Charset v -> Rresult.R.error_msgf "encoding %s is not supported" v let make ~encoding value = if is_utf8_valid_string value then - Ok { charset= `UTF_8 - ; encoding - ; data= Ok value } + Ok { charset = `UTF_8; encoding; data = Ok value } else Rresult.R.error_msg "%S is not a valid UTF-8 string" let make_exn ~encoding value = @@ -85,8 +101,8 @@ let pp_encoding ppf = function | Quoted_printable -> Fmt.string ppf "quoted-printable" let pp ppf t = - Fmt.pf ppf "{ @[charset = %a;@ encoding = %a;@ data = %a;@] }" - pp_charset t.charset pp_encoding t.encoding + Fmt.pf ppf "{ @[charset = %a;@ encoding = %a;@ data = %a;@] }" pp_charset + t.charset pp_encoding t.encoding Fmt.(Dump.result ~ok:Fmt.string ~error:Rresult.R.pp_msg) t.data @@ -97,31 +113,32 @@ let equal a b = equal_charset a.charset b.charset && equal_encoding a.encoding b.encoding && Rresult.R.equal ~ok:String.equal - ~error:(fun (`Msg _) (`Msg _) -> true) (* XXX(dinosaure): or [false]? *) - a.data b.data + ~error:(fun (`Msg _) (`Msg _) -> true) (* XXX(dinosaure): or [false]? *) + a.data b.data let charset_of_uppercase_string x = match String.uppercase_ascii x with | "US-ASCII" | "iso-ir-6" | "ANSI_X3.4-1968" | "ANSI_X3.4-1986" | "ISO_646.rv:1991" | "ISO646-US" | "us" | "IBM367" | "cp367" | "csASCII" -> - `US_ASCII + `US_ASCII | x -> ( - try (Rosetta.encoding_of_string x :> charset) - with Invalid_argument _ -> - match Uutf.encoding_of_string x with - | Some (#uutf_charset as charset) -> charset - | _ -> `Charset x ) - -let charset_of_string x = match x with + try (Rosetta.encoding_of_string x :> charset) + with Invalid_argument _ -> ( + match Uutf.encoding_of_string x with + | Some (#uutf_charset as charset) -> charset + | _ -> `Charset x)) + +let charset_of_string x = + match x with | "US-ASCII" | "iso-ir-6" | "ANSI_X3.4-1968" | "ANSI_X3.4-1986" | "ISO_646.rv:1991" | "ISO646-US" | "us" | "IBM367" | "cp367" | "csASCII" -> - `US_ASCII - | x -> - try (Rosetta.encoding_of_string x :> charset) - with Invalid_argument _ -> - match Uutf.encoding_of_string x with - | Some (#uutf_charset as charset) -> charset - | _ -> charset_of_uppercase_string x + `US_ASCII + | x -> ( + try (Rosetta.encoding_of_string x :> charset) + with Invalid_argument _ -> ( + match Uutf.encoding_of_string x with + | Some (#uutf_charset as charset) -> charset + | _ -> charset_of_uppercase_string x)) let charset_to_string = Fmt.to_to_string pp_charset @@ -135,7 +152,8 @@ module Decoder = struct *) let is_especials = function | '(' | ')' | '<' | '>' | '@' | ',' | ';' | ':' | '"' | '/' | '[' | ']' - | '?' | '.' | '=' -> true + | '?' | '.' | '=' -> + true | _ -> false let is_ctl = function '\000' .. '\031' -> true | _ -> false @@ -146,9 +164,10 @@ module Decoder = struct token = 1* *) let token = - take_while1 (fun chr -> not (is_especials chr || is_ctl chr || is_space chr)) + take_while1 (fun chr -> + not (is_especials chr || is_ctl chr || is_space chr)) - type end_or_uchar = [`End | `Uchar of Uchar.t] + type end_or_uchar = [ `End | `Uchar of Uchar.t ] let normalize_quoted_printable_with_rosetta ?(chunk = 512) ~charset raw = let tmp = Bytes.create chunk in @@ -170,19 +189,23 @@ module Decoder = struct | `Malformed _ as v -> v and go_qp_decode () = match Pecu.Inline.decode qp_decoder with - | `Await -> assert false (* XXX(dinosaure): [Pecu.Inline.decoder_src qp_decoder <> `Manual]. *) + | `Await -> + assert false + (* XXX(dinosaure): [Pecu.Inline.decoder_src qp_decoder <> `Manual]. *) | `Char chr -> - Bytes.unsafe_set tmp !pos chr ; - if !pos + 1 = chunk - then ( pos := 0 ; Rosetta.src decoder tmp 0 chunk ) - else incr pos ; - go_decode () + Bytes.unsafe_set tmp !pos chr; + if !pos + 1 = chunk then ( + pos := 0; + Rosetta.src decoder tmp 0 chunk) + else incr pos; + go_decode () | `End -> - ( let i = !pos in - pos := 0 ; - Rosetta.src decoder tmp 0 i) ; - go_decode () - | `Malformed _ as v -> v in + (let i = !pos in + pos := 0; + Rosetta.src decoder tmp 0 i); + go_decode () + | `Malformed _ as v -> v + in match go_qp_decode () with | `Ok -> Ok (Buffer.contents res) | `Malformed data -> Rresult.R.error_msgf "Malformed input: %S" data @@ -207,19 +230,23 @@ module Decoder = struct | `Malformed _ as v -> v and go_qp_decode () = match Pecu.Inline.decode qp_decoder with - | `Await -> assert false (* XXX(dinosaure): [Pecu.Inline.decoder_src qp_decoder <> `Manual]. *) + | `Await -> + assert false + (* XXX(dinosaure): [Pecu.Inline.decoder_src qp_decoder <> `Manual]. *) | `Char chr -> - Bytes.unsafe_set tmp !pos chr ; - if !pos + 1 = chunk - then ( pos := 0 ; Uutf.Manual.src decoder tmp 0 chunk ) - else incr pos ; - go_decode () + Bytes.unsafe_set tmp !pos chr; + if !pos + 1 = chunk then ( + pos := 0; + Uutf.Manual.src decoder tmp 0 chunk) + else incr pos; + go_decode () | `End -> - ( let i = !pos in - pos := 0 ; - Uutf.Manual.src decoder tmp 0 i) ; - go_decode () - | `Malformed _ as v -> v in + (let i = !pos in + pos := 0; + Uutf.Manual.src decoder tmp 0 i); + go_decode () + | `Malformed _ as v -> v + in match go_qp_decode () with | `Ok -> Ok (Buffer.contents res) | `Malformed data -> Rresult.R.error_msgf "Malformed input: %S" data @@ -228,50 +255,56 @@ module Decoder = struct let res = Buffer.create chunk in match Base64.decode raw with | Error _ as err -> err - | Ok decoded -> - let decoder = Rosetta.decoder charset (`String decoded) in - let encoder = Uutf.encoder `UTF_8 (`Buffer res) in - let rec go_encode v = - match (v, Uutf.encode encoder v) with - | `End, `Ok -> `Ok - | _, `Ok -> go_decode () - | _, `Partial -> assert false - (* XXX(dinosaure): [Uutf.encoder_dst encoder <> `Manual] *) - and go_decode () = - match Rosetta.decode decoder with - | `Await -> assert false (* XXX(dinosaure): [Rosetta.decoder_src decoder <> `Manual] *) - | #end_or_uchar as v -> go_encode v - | `Malformed _ as v -> v in - match go_decode () with - | `Ok -> Ok (Buffer.contents res) - | `Malformed data -> Rresult.R.error_msgf "Malformed input: %S" data + | Ok decoded -> ( + let decoder = Rosetta.decoder charset (`String decoded) in + let encoder = Uutf.encoder `UTF_8 (`Buffer res) in + let rec go_encode v = + match (v, Uutf.encode encoder v) with + | `End, `Ok -> `Ok + | _, `Ok -> go_decode () + | _, `Partial -> assert false + (* XXX(dinosaure): [Uutf.encoder_dst encoder <> `Manual] *) + and go_decode () = + match Rosetta.decode decoder with + | `Await -> + assert false + (* XXX(dinosaure): [Rosetta.decoder_src decoder <> `Manual] *) + | #end_or_uchar as v -> go_encode v + | `Malformed _ as v -> v + in + match go_decode () with + | `Ok -> Ok (Buffer.contents res) + | `Malformed data -> Rresult.R.error_msgf "Malformed input: %S" data) let normalize_base64_with_uutf ?(chunk = 512) ~charset raw = let res = Buffer.create chunk in match Base64.decode raw with | Error _ as err -> err - | Ok decoded -> - let decoder = Uutf.decoder ~encoding:charset (`String decoded) in - let encoder = Uutf.encoder `UTF_8 (`Buffer res) in - let rec go_encode v = - match (v, Uutf.encode encoder v) with - | `End, `Ok -> `Ok - | _, `Ok -> go_decode () - | _, `Partial -> assert false - (* XXX(dinosaure): [Uutf.encoder_dst encoder <> `Manual] *) - and go_decode () = - match Uutf.decode decoder with - | `Await -> assert false (* XXX(dinosaure): [Rosetta.decoder_src decoder <> `Manual] *) - | #end_or_uchar as v -> go_encode v - | `Malformed _ as v -> v in - match go_decode () with - | `Ok -> Ok (Buffer.contents res) - | `Malformed data -> Rresult.R.error_msgf "Malformed input: %S" data + | Ok decoded -> ( + let decoder = Uutf.decoder ~encoding:charset (`String decoded) in + let encoder = Uutf.encoder `UTF_8 (`Buffer res) in + let rec go_encode v = + match (v, Uutf.encode encoder v) with + | `End, `Ok -> `Ok + | _, `Ok -> go_decode () + | _, `Partial -> assert false + (* XXX(dinosaure): [Uutf.encoder_dst encoder <> `Manual] *) + and go_decode () = + match Uutf.decode decoder with + | `Await -> + assert false + (* XXX(dinosaure): [Rosetta.decoder_src decoder <> `Manual] *) + | #end_or_uchar as v -> go_encode v + | `Malformed _ as v -> v + in + match go_decode () with + | `Ok -> Ok (Buffer.contents res) + | `Malformed data -> Rresult.R.error_msgf "Malformed input: %S" data) let normalize_quoted_printable ?(chunk = 512) ~charset raw = match charset with - | `US_ASCII -> - ( let res = Buffer.create chunk in + | `US_ASCII -> ( + let res = Buffer.create chunk in let qp_decoder = Pecu.Inline.decoder (`String raw) in let encoder = Uutf.encoder `UTF_8 (`Buffer res) in let rec go_encode v = @@ -288,31 +321,37 @@ module Decoder = struct in match go_qp_decode () with | `Ok -> Ok (Buffer.contents res) - | `Malformed data -> Rresult.R.error_msgf "Malformed input: %S" data ) - | #uutf_charset as charset -> normalize_quoted_printable_with_uutf ~chunk ~charset raw - | #Rosetta.encoding as charset -> normalize_quoted_printable_with_rosetta ~chunk ~charset raw - | `Charset _ -> - ( let res = Buffer.create chunk in + | `Malformed data -> Rresult.R.error_msgf "Malformed input: %S" data) + | #uutf_charset as charset -> + normalize_quoted_printable_with_uutf ~chunk ~charset raw + | #Rosetta.encoding as charset -> + normalize_quoted_printable_with_rosetta ~chunk ~charset raw + | `Charset _ -> ( + let res = Buffer.create chunk in let qp_decoder = Pecu.Inline.decoder (`String raw) in let rec go_qp_decode () = match Pecu.Inline.decode qp_decoder with | `Await -> assert false - | `Char chr -> Buffer.add_char res chr ; go_qp_decode () + | `Char chr -> + Buffer.add_char res chr; + go_qp_decode () | `End -> `Ok - | `Malformed _ as v -> v in + | `Malformed _ as v -> v + in match go_qp_decode () with | `Ok -> Ok (Buffer.contents res) - | `Malformed data -> Rresult.R.error_msgf "Malformed input: %S" data ) + | `Malformed data -> Rresult.R.error_msgf "Malformed input: %S" data) let normalize_base64 ?chunk ~charset raw = match charset with | `US_ASCII -> - (* XXX(dinosaure): UTF-8 is a superset of ASCII. Then, we probably need to - check if characters are between '\000' and '\127' but it's probably ok. - paranoid mode or not? TODO! *) - Base64.decode raw + (* XXX(dinosaure): UTF-8 is a superset of ASCII. Then, we probably need to + check if characters are between '\000' and '\127' but it's probably ok. + paranoid mode or not? TODO! *) + Base64.decode raw | #uutf_charset as charset -> normalize_base64_with_uutf ?chunk ~charset raw - | #Rosetta.encoding as charset -> normalize_base64_with_rosetta ?chunk ~charset raw + | #Rosetta.encoding as charset -> + normalize_base64_with_rosetta ?chunk ~charset raw | `Charset _ -> Base64.decode raw let normalize ?chunk ~charset ~encoding raw = @@ -362,21 +401,24 @@ module Decoder = struct See [pecu], [rosetta] and [ocaml-base64] for more details about encoding. *) let encoded_word = - string "=?" *> token - >>| charset_of_string - >>= fun charset -> + string "=?" *> token >>| charset_of_string >>= fun charset -> char '?' *> satisfy (function 'Q' | 'q' | 'B' | 'b' -> true | _ -> false) - >>= fun encoding_raw -> (match encoding_raw with - | 'Q' | 'q' -> return Quoted_printable - | 'B' | 'b' -> return Base64 - | encoding -> invalid_encoding encoding) + >>= fun encoding_raw -> + (match encoding_raw with + | 'Q' | 'q' -> return Quoted_printable + | 'B' | 'b' -> return Base64 + | encoding -> invalid_encoding encoding) >>= fun encoding -> - char '?' *> encoded_text - >>= fun raw -> return (normalize ~chunk:512 ~charset:charset ~encoding raw) - >>= fun data -> string "?=" *> return { charset; encoding; data; } + char '?' *> encoded_text >>= fun raw -> + return (normalize ~chunk:512 ~charset ~encoding raw) >>= fun data -> + string "?=" *> return { charset; encoding; data } end -let of_string x = match Angstrom.parse_string ~consume:Angstrom.Consume.Prefix Decoder.encoded_word x with +let of_string x = + match + Angstrom.parse_string ~consume:Angstrom.Consume.Prefix Decoder.encoded_word + x + with | Ok v -> Ok v | Error _ -> Rresult.R.error_msgf "%S is not a valid encoded-word" x @@ -392,22 +434,29 @@ module Encoder = struct let to_quoted_printable input = let buffer = Stdlib.Buffer.create (String.length input) in let encoder = Pecu.Inline.encoder (`Buffer buffer) in - String.iter (fun chr -> ignore @@ Pecu.Inline.encode encoder (`Char chr)) input ; - ignore @@ Pecu.Inline.encode encoder `End ; + String.iter + (fun chr -> ignore @@ Pecu.Inline.encode encoder (`Char chr)) + input; + ignore @@ Pecu.Inline.encode encoder `End; Stdlib.Buffer.contents buffer let quoted_printable = using to_quoted_printable string let base64 = using (fun x -> Base64.encode_exn ~pad:true x) string - - let is_base64 = function - | Base64 -> true | _ -> false + let is_base64 = function Base64 -> true | _ -> false let encoded_word ppf t = match t.data with | Ok data -> - let fmt = [ bbox; string $ "=?"; !!charset; char $ '?'; !!encoding; char $ '?'; a; string $ "?="; close ] in - let encoder = if is_base64 t.encoding then base64 else quoted_printable in - eval ppf fmt t.charset t.encoding encoder data + let fmt = + [ + bbox; string $ "=?"; !!charset; char $ '?'; !!encoding; char $ '?'; + a; string $ "?="; close; + ] + in + let encoder = + if is_base64 t.encoding then base64 else quoted_printable + in + eval ppf fmt t.charset t.encoding encoder data | Error (`Msg err) -> - Fmt.invalid_arg "Impossible to encode an invalid encoded-word: %s" err + Fmt.invalid_arg "Impossible to encode an invalid encoded-word: %s" err end diff --git a/lib/encoded_word.mli b/lib/encoded_word.mli index b39d096..1d9c9a3 100644 --- a/lib/encoded_word.mli +++ b/lib/encoded_word.mli @@ -31,10 +31,11 @@ val b : encoding val q : encoding (** Inline quoted-printable encoding. *) -type t = - { charset : charset - ; encoding : encoding - ; data : (string, Rresult.R.msg) result } +type t = { + charset : charset; + encoding : encoding; + data : (string, Rresult.R.msg) result; +} val is_normalized : t -> bool @@ -74,7 +75,8 @@ val charset_of_string : string -> charset val charset_to_string : charset -> string -val normalize_to_utf8 : charset:charset -> string -> (string, Rresult.R.msg) result +val normalize_to_utf8 : + charset:charset -> string -> (string, Rresult.R.msg) result (** [normalize_to_utf8 ~charset s] maps a source [s] which is encoded with the charset {!charset} and try to map/normalize it to UTF-8. *) diff --git a/lib/field.ml b/lib/field.ml index c5488ed..efe520a 100644 --- a/lib/field.ml +++ b/lib/field.ml @@ -1,26 +1,27 @@ type 'a t = - | Date : Date.t t - | Mailboxes : Mailbox.t list t - | Mailbox : Mailbox.t t - | Addresses : Address.t list t - | MessageID : MessageID.t t + | Date : Date.t t + | Mailboxes : Mailbox.t list t + | Mailbox : Mailbox.t t + | Addresses : Address.t list t + | MessageID : MessageID.t t | Unstructured : Unstructured.t t - | Phrases : Emile.phrase list t - | Content : Content_type.t t - | Encoding : Content_encoding.t t + | Phrases : Emile.phrase list t + | Content : Content_type.t t + | Encoding : Content_encoding.t t type witness = Witness : 'a t -> witness type field = Field : Field_name.t * 'a t * 'a -> field let make : type a. Field_name.t -> a t -> a -> field = - fun field_name w v -> Field (field_name, w, v) + fun field_name w v -> Field (field_name, w, v) let pp ppf (Field (field_name, w, v)) = let of_witness : type a. a t -> a Fmt.t = function - | Date -> - (fun ppf v -> match Date.to_ptime v with - | Ok (v, tz_offset_s) -> Ptime.pp_human ~tz_offset_s () ppf v - | Error _ -> Date.pp ppf v) + | Date -> ( + fun ppf v -> + match Date.to_ptime v with + | Ok (v, tz_offset_s) -> Ptime.pp_human ~tz_offset_s () ppf v + | Error _ -> Date.pp ppf v) | Mailboxes -> Fmt.list Mailbox.pp | Mailbox -> Mailbox.pp | Addresses -> Fmt.list Address.pp @@ -28,24 +29,26 @@ let pp ppf (Field (field_name, w, v)) = | Unstructured -> Unstructured.pp | Phrases -> Fmt.list Emile.pp_phrase | Content -> Content_type.pp - | Encoding -> Content_encoding.pp in + | Encoding -> Content_encoding.pp + in Fmt.pf ppf "%a: @[%a@]" Field_name.pp field_name (of_witness w) v let of_field_name : Field_name.t -> witness = - fun field_name -> match String.lowercase_ascii (field_name :> string) with - | "date" -> Witness Date - | "from" -> Witness Mailboxes - | "sender" -> Witness Mailbox - | "reply-to" -> Witness Addresses - | "to" -> Witness Addresses - | "cc" -> Witness Addresses - | "bcc" -> Witness Addresses - | "subject" -> Witness Unstructured - | "message-id" -> Witness MessageID - | "comments" -> Witness Unstructured - | "content-type" -> Witness Content - | "content-transfer-encoding" -> Witness Encoding - | _ -> Witness Unstructured + fun field_name -> + match String.lowercase_ascii (field_name :> string) with + | "date" -> Witness Date + | "from" -> Witness Mailboxes + | "sender" -> Witness Mailbox + | "reply-to" -> Witness Addresses + | "to" -> Witness Addresses + | "cc" -> Witness Addresses + | "bcc" -> Witness Addresses + | "subject" -> Witness Unstructured + | "message-id" -> Witness MessageID + | "comments" -> Witness Unstructured + | "content-type" -> Witness Content + | "content-transfer-encoding" -> Witness Encoding + | _ -> Witness Unstructured let parser : type a. a t -> a Angstrom.t = function | Date -> Date.Decoder.date_time @@ -54,8 +57,9 @@ let parser : type a. a t -> a Angstrom.t = function | Addresses -> Address.Decoder.address_list | MessageID -> MessageID.Decoder.message_id | Unstructured -> - let open Angstrom in - Unstructured.Decoder.unstructured () >>= fun v -> return (v :> Unstructured.t) + let open Angstrom in + Unstructured.Decoder.unstructured () >>= fun v -> + return (v :> Unstructured.t) | Content -> Content_type.Decoder.content | Encoding -> Content_encoding.Decoder.mechanism | _ -> assert false @@ -71,38 +75,49 @@ let encoder : type a. a t -> a Prettym.t = function | Encoding -> Content_encoding.Encoder.mechanism | _ -> assert false -let ( <.> ) f g = fun x -> f (g x) +let ( <.> ) f g x = f (g x) module Decoder = struct open Angstrom let field ?g field_name = - let buf = Bytes.create 0x7f in (* XXX(dinosaure): fast allocation. *) + let buf = Bytes.create 0x7f in + (* XXX(dinosaure): fast allocation. *) Unstrctrd_parser.unstrctrd buf >>= fun v -> - let Witness w = match Option.bind (Field_name.Map.find_opt field_name) g with + let (Witness w) = + match Option.bind (Field_name.Map.find_opt field_name) g with | None -> of_field_name field_name - | Some w -> w in + | Some w -> w + in let parser = parser w in let res = let open Rresult in Unstrctrd.without_comments v >>| Unstrctrd.fold_fws >>| Unstrctrd.to_utf_8_string - (* XXX(dinosaure): normalized value can have trailing whitespace - * such as "value (comment)" returns "value ". Given parser can - * ignore it (and it does not consume all inputs finally). *) - >>= ( R.reword_error R.msg <.> (parse_string ~consume:Consume.Prefix) parser ) - >>| fun v -> Field (field_name, w, v) in + (* XXX(dinosaure): normalized value can have trailing whitespace + * such as "value (comment)" returns "value ". Given parser can + * ignore it (and it does not consume all inputs finally). *) + >>= (R.reword_error R.msg + <.> (parse_string ~consume:Consume.Prefix) parser) + >>| fun v -> Field (field_name, w, v) + in match res with | Ok v -> return v - | Error _ -> return (Field (field_name, Unstructured, (v :> Unstructured.t))) + | Error _ -> + return (Field (field_name, Unstructured, (v :> Unstructured.t))) end module Encoder = struct open Prettym let field ppf field = - let Field (field_name, w, v) = field in + let (Field (field_name, w, v)) = field in let e = encoder w in - eval ppf [ tbox 1; !!Field_name.Encoder.field_name; string $ ":"; spaces 1; !!e; close; new_line; ] field_name v + eval ppf + [ + tbox 1; !!Field_name.Encoder.field_name; string $ ":"; spaces 1; !!e; + close; new_line; + ] + field_name v end diff --git a/lib/field.mli b/lib/field.mli index 1a1b43e..4071198 100644 --- a/lib/field.mli +++ b/lib/field.mli @@ -15,22 +15,22 @@ *) type 'a t = - | Date : Date.t t - | Mailboxes : Mailbox.t list t - | Mailbox : Mailbox.t t - | Addresses : Address.t list t - | MessageID : MessageID.t t + | Date : Date.t t + | Mailboxes : Mailbox.t list t + | Mailbox : Mailbox.t t + | Addresses : Address.t list t + | MessageID : MessageID.t t | Unstructured : Unstructured.t t - | Phrases : Emile.phrase list t - | Content : Content_type.t t - | Encoding : Content_encoding.t t -(** Type of kind of values according RFC2045/RFC5322. *) + | Phrases : Emile.phrase list t + | Content : Content_type.t t + | Encoding : Content_encoding.t t + (** Type of kind of values according RFC2045/RFC5322. *) -type witness = Witness : 'a t -> witness -(** Witness type to be able to manipulate {!t}. *) +type witness = + | Witness : 'a t -> witness + (** Witness type to be able to manipulate {!t}. *) -type field = Field : Field_name.t * 'a t * 'a -> field -(** Type of field. *) +type field = Field : Field_name.t * 'a t * 'a -> field (** Type of field. *) val make : Field_name.t -> 'a t -> 'a -> field (** [make field_name w v] returns a field. *) diff --git a/lib/field_name.ml b/lib/field_name.ml index 8c934b0..2293b2f 100644 --- a/lib/field_name.ml +++ b/lib/field_name.ml @@ -9,15 +9,19 @@ let equal a b = compare a b = 0 let capitalize x = let capitalize res idx = - let map = function 'a' .. 'z' as chr -> Char.unsafe_chr (Char.code chr - 32) | chr -> chr in - Bytes.set res idx (map (Bytes.get res idx)) in + let map = function + | 'a' .. 'z' as chr -> Char.unsafe_chr (Char.code chr - 32) + | chr -> chr + in + Bytes.set res idx (map (Bytes.get res idx)) + in let is_dash_or_space = function ' ' | '-' -> true | _ -> false in let res = Bytes.of_string x in for i = 0 to String.length x - 1 do - if i > 0 && is_dash_or_space x.[i - 1] - then capitalize res i + if i > 0 && is_dash_or_space x.[i - 1] then capitalize res i else if i = 0 then capitalize res i - done ; Bytes.unsafe_to_string res + done; + Bytes.unsafe_to_string res let canonicalize = String.lowercase_ascii @@ -29,21 +33,23 @@ let is_ftext = function let of_string x = try - for i = 0 to String.length x - 1 - do if not (is_ftext x.[i]) then raise Break done ; + for i = 0 to String.length x - 1 do + if not (is_ftext x.[i]) then raise Break + done; Ok x with Break -> Rresult.R.error_msgf "Invalid field: %S" x -let of_string_exn x = match of_string x with +let of_string_exn x = + match of_string x with | Ok x -> x | Error (`Msg err) -> Fmt.invalid_arg "%s" err let v = of_string_exn - let pp = Fmt.using capitalize Fmt.string let prefixed_by prefix field = - if String.contains prefix '-' then Fmt.invalid_arg "Field.prefixed_by: %s contains '-'" prefix ; + if String.contains prefix '-' then + Fmt.invalid_arg "Field.prefixed_by: %s contains '-'" prefix; match String.(split_on_char '-' (lowercase_ascii field)) with | [] -> assert false (* XXX(dinosaure): see invariants of [split_on_char]. *) | [ _ ] -> false @@ -75,13 +81,11 @@ let comments = v "Comments" let keywords = v "Keywords" let received = v "Received" let return_path = v "Return-Path" - let content_type = v "Content-Type" let content_encoding = v "Content-Transfer-Encoding" let mime_version = v "MIME-Version" let content_id = v "Content-ID" let content_description = v "Content-Description" - let resent_date = v "Resent-Date" let resent_from = v "Resent-From" let resent_sender = v "Resent-Sender" @@ -91,4 +95,8 @@ let resent_bcc = v "Resent-Bcc" let resent_message_id = v "Resent-Message-ID" let resent_reply_to = v "Resent-Reply-To" -module Map = Map.Make(struct type nonrec t = t let compare = compare end) +module Map = Map.Make (struct + type nonrec t = t + + let compare = compare +end) diff --git a/lib/group.ml b/lib/group.ml index 2b16276..8f05810 100644 --- a/lib/group.ml +++ b/lib/group.ml @@ -21,27 +21,29 @@ module Phrase = Mailbox.Phrase let equal = Emile.equal_group let make ~name:group mailboxes = - if List.length mailboxes = 0 then None - else Some { Emile.group; mailboxes; } + if List.length mailboxes = 0 then None else Some { Emile.group; mailboxes } -let v ~name mailboxes = match make ~name mailboxes with +let v ~name mailboxes = + match make ~name mailboxes with | None -> Fmt.invalid_arg "A group contains at least one mailbox" | Some t -> t let pp = Emile.pp_group -module Decoder = struct - let group = Emile.Parser.group -end +module Decoder = struct let group = Emile.Parser.group end module Encoder = struct open Prettym - let comma = (fun ppf () -> eval ppf [ char $ ','; fws ]), () + let comma = ((fun ppf () -> eval ppf [ char $ ','; fws ]), ()) let phrase = Mailbox.Encoder.phrase let mailbox = Mailbox.Encoder.mailbox let group ppf t = - eval ppf [ box; !!phrase; char $ ':'; spaces 1; box; !!(list ~sep:comma mailbox); close; char $ ';'; close ] + eval ppf + [ + box; !!phrase; char $ ':'; spaces 1; box; !!(list ~sep:comma mailbox); + close; char $ ';'; close; + ] t.Emile.group t.Emile.mailboxes end diff --git a/lib/group.mli b/lib/group.mli index 7005f6b..39f1f27 100644 --- a/lib/group.mli +++ b/lib/group.mli @@ -26,7 +26,6 @@ module Phrase : sig val e : encoding:Encoded_word.encoding -> string -> elt val q : Encoded_word.encoding val b : Encoded_word.encoding - val word : string -> (elt, [> Rresult.R.msg ]) result val word_exn : string -> elt val coerce : 'a Peano.s t -> Emile.phrase diff --git a/lib/hd.ml b/lib/hd.ml index a3c0302..a022463 100644 --- a/lib/hd.ml +++ b/lib/hd.ml @@ -3,17 +3,16 @@ module Q = Ke.Rke.Weighted type q = (char, Bigarray.int8_unsigned_elt) Q.t type g = Field.witness G.t -type v = - [ `Field of Field.field Location.with_location - | `End ] +type v = [ `Field of Field.field Location.with_location | `End ] type s = v Angstrom.Unbuffered.state -type decoder = - { q : q - ; b : Bigstringaf.t - ; p : g - ; mutable c : bool - ; mutable s : s } +type decoder = { + q : q; + b : Bigstringaf.t; + p : g; + mutable c : bool; + mutable s : s; +} let field g = let open Angstrom in @@ -23,22 +22,25 @@ let field g = let with_location p = let open Angstrom in - pos >>= fun a -> p >>= fun v -> pos >>| fun b -> + pos >>= fun a -> + p >>= fun v -> + pos >>| fun b -> let location = Location.make a b in Location.inj ~location v let parser g = let open Angstrom in let crlf = char '\r' *> char '\n' in - (with_location (field g) >>| fun v -> `Field v) - <|> (crlf *> return `End) + with_location (field g) >>| (fun v -> `Field v) <|> crlf *> return `End -let decoder ?(p= G.empty) buffer = - { q= Q.from buffer - ; b= buffer - ; p - ; c= false - ; s= Angstrom.Unbuffered.parse (parser p) } +let decoder ?(p = G.empty) buffer = + { + q = Q.from buffer; + b = buffer; + p; + c = false; + s = Angstrom.Unbuffered.parse (parser p); + } type decode = [ `Field of Field.field Location.with_location @@ -47,49 +49,51 @@ type decode = | `Malformed of string ] let rec decode : decoder -> decode = - fun decoder -> match decoder.s with - | Angstrom.Unbuffered.Partial { committed; continue; } -> - Q.N.shift_exn decoder.q committed ; - Q.compress decoder.q ; - let more = - if decoder.c - then Angstrom.Unbuffered.Complete - else Angstrom.Unbuffered.Incomplete in - let off = 0 and len = Q.length decoder.q in - if len > 0 || decoder.c - then ( decoder.s <- continue decoder.b ~off ~len more ; protect decoder ) - else `Await + fun decoder -> + match decoder.s with + | Angstrom.Unbuffered.Partial { committed; continue } -> + Q.N.shift_exn decoder.q committed; + Q.compress decoder.q; + let more = + if decoder.c then Angstrom.Unbuffered.Complete + else Angstrom.Unbuffered.Incomplete + in + let off = 0 and len = Q.length decoder.q in + if len > 0 || decoder.c then ( + decoder.s <- continue decoder.b ~off ~len more; + protect decoder) + else `Await | Angstrom.Unbuffered.Fail (committed, _, err) -> - Q.N.shift_exn decoder.q committed ; - `Malformed err - | Angstrom.Unbuffered.Done (committed, `End) -> - Q.N.shift_exn decoder.q committed ; - Q.compress decoder.q ; - ( match Q.N.peek decoder.q with + Q.N.shift_exn decoder.q committed; + `Malformed err + | Angstrom.Unbuffered.Done (committed, `End) -> ( + Q.N.shift_exn decoder.q committed; + Q.compress decoder.q; + match Q.N.peek decoder.q with | [ x ] -> `End (Bigstringaf.to_string x) | [] -> `End "" - | _ -> assert false ) + | _ -> assert false) | Angstrom.Unbuffered.Done (committed, `Field v) -> - Q.N.shift_exn decoder.q committed ; - decoder.s <- Angstrom.Unbuffered.parse (parser decoder.p) ; - `Field v + Q.N.shift_exn decoder.q committed; + decoder.s <- Angstrom.Unbuffered.parse (parser decoder.p); + `Field v -and protect decoder = match decoder.s with - | Angstrom.Unbuffered.Partial { committed= 0; _ } -> `Await +and protect decoder = + match decoder.s with + | Angstrom.Unbuffered.Partial { committed = 0; _ } -> `Await | _ -> decode decoder let blit_from_string src src_off dst dst_off len = Bigstringaf.blit_from_string src ~src_off dst ~dst_off ~len let src decoder source off len = - if off < 0 || len < 0 || off + len > String.length source - then Fmt.invalid_arg "Invalid bounds" - else Q.N.push decoder.q - ~blit:blit_from_string - ~length:String.length - ~off ~len source |> function - | Some _ -> - if len = 0 then decoder.c <- true ; - Rresult.R.ok () - | None -> - Rresult.R.error_msg "Input is too much bigger" + if off < 0 || len < 0 || off + len > String.length source then + Fmt.invalid_arg "Invalid bounds" + else + Q.N.push decoder.q ~blit:blit_from_string ~length:String.length ~off ~len + source + |> function + | Some _ -> + if len = 0 then decoder.c <- true; + Rresult.R.ok () + | None -> Rresult.R.error_msg "Input is too much bigger" diff --git a/lib/header.ml b/lib/header.ml index 563e1d2..57294cf 100644 --- a/lib/header.ml +++ b/lib/header.ml @@ -4,65 +4,70 @@ let pp = Fmt.(list ~sep:(always "@\n") (using Location.prj Field.pp)) let assoc field_name header = let f acc (Field.Field (field_name', _, _) as field) = - if Field_name.equal field_name field_name' - then field :: acc - else acc in + if Field_name.equal field_name field_name' then field :: acc else acc + in List.fold_left f [] (List.map Location.prj header) |> List.rev let remove_assoc field_name header = let f acc x = - let Field.Field (field_name', _, _) = Location.prj x in - if Field_name.equal field_name field_name' - then acc else x :: acc in + let (Field.Field (field_name', _, _)) = Location.prj x in + if Field_name.equal field_name field_name' then acc else x :: acc + in List.fold_left f [] header |> List.rev let exists field_name t = - List.exists (fun (Field.Field (field_name', _, _)) -> Field_name.equal field_name field_name') (List.map Location.prj t) + List.exists + (fun (Field.Field (field_name', _, _)) -> + Field_name.equal field_name field_name') + (List.map Location.prj t) let empty = [] - let concat a b = a @ b +let concat a b = a @ b -let add - : type a. Field_name.t -> (a Field.t * a) -> t -> t - = fun field_name (w, v) t -> - let field = Field.Field (field_name, w, v) in - Location.inj ~location:Location.none field :: t +let add : type a. Field_name.t -> a Field.t * a -> t -> t = + fun field_name (w, v) t -> + let field = Field.Field (field_name, w, v) in + Location.inj ~location:Location.none field :: t -let replace - : type a. Field_name.t -> (a Field.t * a) -> t -> t - = fun field_name (w, v) t -> - let header = remove_assoc field_name t in - let field = Field.Field (field_name, w, v) in - Location.inj ~location:Location.none field :: header +let replace : type a. Field_name.t -> a Field.t * a -> t -> t = + fun field_name (w, v) t -> + let header = remove_assoc field_name t in + let field = Field.Field (field_name, w, v) in + Location.inj ~location:Location.none field :: header let of_list = List.map (Location.inj ~location:Location.none) let of_list_with_location x = x let content_type header = let content : Content_type.t ref = ref Content_type.default in - List.iter (function + List.iter + (function | Field.Field (field_name, Field.Content, v) -> - if Field_name.equal field_name Field_name.content_type - then content := v - | _ -> ()) (List.map Location.prj header) ; + if Field_name.equal field_name Field_name.content_type then + content := v + | _ -> ()) + (List.map Location.prj header); !content let content_encoding header = let mechanism : Content_encoding.t ref = ref Content_encoding.default in - List.iter (function + List.iter + (function | Field.Field (field_name, Field.Encoding, v) -> - if Field_name.equal field_name Field_name.content_encoding - then mechanism := v - | _ -> ()) (List.map Location.prj header) ; + if Field_name.equal field_name Field_name.content_encoding then + mechanism := v + | _ -> ()) + (List.map Location.prj header); !mechanism let message_id header = let rec go = function | [] -> None | Field.Field (field_name, Field.MessageID, (v : MessageID.t)) :: tl -> - if Field_name.equal field_name Field_name.message_id then Some v - else go tl - | _ :: tl -> go tl in + if Field_name.equal field_name Field_name.message_id then Some v + else go tl + | _ :: tl -> go tl + in go (List.map Location.prj header) module Decoder = struct @@ -75,7 +80,9 @@ module Decoder = struct skip_while is_wsp *> char ':' *> Field.Decoder.field field_name let with_location p = - pos >>= fun a -> p >>= fun v -> pos >>| fun b -> + pos >>= fun a -> + p >>= fun v -> + pos >>| fun b -> let location = Location.make a b in Location.inj ~location v @@ -85,7 +92,7 @@ end module Encoder = struct include Prettym - let noop = (fun ppf () -> ppf), () + let noop = ((fun ppf () -> ppf), ()) let field ppf x = Field.Encoder.field ppf x let header ppf x = (list ~sep:noop field) ppf (List.map Location.prj x) end diff --git a/lib/header.mli b/lib/header.mli index 6e8ce74..69cf23e 100644 --- a/lib/header.mli +++ b/lib/header.mli @@ -35,11 +35,11 @@ val concat : t -> t -> t val exists : Field_name.t -> t -> bool (** [exists field_name t] is [true] if [field_name] exists in [t]. *) -val add : Field_name.t -> ('a Field.t * 'a) -> t -> t +val add : Field_name.t -> 'a Field.t * 'a -> t -> t (** [add field_name (w, v) t] adds a new field-name with value v. [add] does not replace [field_name] if it already exists into [t]. *) -val replace : Field_name.t -> ('a Field.t * 'a) -> t -> t +val replace : Field_name.t -> 'a Field.t * 'a -> t -> t (** [replace field_name (w, v) t] replaces existing field-name [field_name] in [t] by the new value [v]. If [field_name] does not exist, it adds it. *) @@ -75,6 +75,6 @@ module Encoder : sig val header : t Prettym.t end -val to_stream : t -> (unit -> string option) +val to_stream : t -> unit -> string option (** [to_stream header] returns a stream of the given header which can be used into protocol like SMTP. *) diff --git a/lib/iana.ml b/lib/iana.ml index 16eff19..15914c3 100644 --- a/lib/iana.ml +++ b/lib/iana.ml @@ -6,858 +6,866 @@ module Set = Set.Make (String) let database = Map.add "video" (Set.of_list - [ "1d-interleaved-parityfec"; "3gpp"; "3gpp-tt"; "3gpp2"; "BMPEG" - ; "BT656"; "CelB"; "DV"; "H261"; "H263"; "H263-1998"; "H263-2000" - ; "H264"; "H264-RCDO"; "H264-SVC"; "H265"; "JPEG"; "MP1S"; "MP2P" - ; "MP2T"; "MP4V-ES"; "MPV"; "SMPTE292M"; "VP8"; "encaprtp"; "example" - ; "iso.segment"; "jpeg2000"; "mj2"; "mp4"; "mpeg"; "mpeg4-generic"; "nv" - ; "ogg"; "parityfec"; "pointer"; "quicktime"; "raptorfec"; "raw" - ; "rtp-enc-aescm128"; "rtploopback"; "rtx"; "ulpfec"; "vc1"; "vnd.CCTV" - ; "vnd.dece.hd"; "vnd.dece.mobile"; "vnd.dece.mp4"; "vnd.dece.pd" - ; "vnd.dece.sd"; "vnd.dece.video"; "vnd.directv.mpeg" - ; "vnd.directv.mpeg-tts"; "vnd.dlna.mpeg-tts"; "vnd.dvb.file"; "vnd.fvt" - ; "vnd.hns.video"; "vnd.iptvforum.1dparityfec-1010" - ; "vnd.iptvforum.1dparityfec-2005"; "vnd.iptvforum.2dparityfec-1010" - ; "vnd.iptvforum.2dparityfec-2005"; "vnd.iptvforum.ttsavc" - ; "vnd.iptvforum.ttsmpeg2"; "vnd.motorola.video"; "vnd.motorola.videop" - ; "vnd.mpegurl"; "vnd.ms-playready.media.pyv" - ; "vnd.nokia.interleaved-multimedia"; "vnd.nokia.videovoip" - ; "vnd.objectvideo"; "vnd.radgamettools.bink" - ; "vnd.radgamettools.smacker"; "vnd.sealed.mpeg1"; "vnd.sealed.mpeg4" - ; "vnd.sealed.swf"; "vnd.sealedmedia.softseal.mov"; "vnd.uvvu.mp4" - ; "vnd.vivo" ]) + [ + "1d-interleaved-parityfec"; "3gpp"; "3gpp-tt"; "3gpp2"; "BMPEG"; + "BT656"; "CelB"; "DV"; "H261"; "H263"; "H263-1998"; "H263-2000"; + "H264"; "H264-RCDO"; "H264-SVC"; "H265"; "JPEG"; "MP1S"; "MP2P"; + "MP2T"; "MP4V-ES"; "MPV"; "SMPTE292M"; "VP8"; "encaprtp"; "example"; + "iso.segment"; "jpeg2000"; "mj2"; "mp4"; "mpeg"; "mpeg4-generic"; "nv"; + "ogg"; "parityfec"; "pointer"; "quicktime"; "raptorfec"; "raw"; + "rtp-enc-aescm128"; "rtploopback"; "rtx"; "ulpfec"; "vc1"; "vnd.CCTV"; + "vnd.dece.hd"; "vnd.dece.mobile"; "vnd.dece.mp4"; "vnd.dece.pd"; + "vnd.dece.sd"; "vnd.dece.video"; "vnd.directv.mpeg"; + "vnd.directv.mpeg-tts"; "vnd.dlna.mpeg-tts"; "vnd.dvb.file"; "vnd.fvt"; + "vnd.hns.video"; "vnd.iptvforum.1dparityfec-1010"; + "vnd.iptvforum.1dparityfec-2005"; "vnd.iptvforum.2dparityfec-1010"; + "vnd.iptvforum.2dparityfec-2005"; "vnd.iptvforum.ttsavc"; + "vnd.iptvforum.ttsmpeg2"; "vnd.motorola.video"; "vnd.motorola.videop"; + "vnd.mpegurl"; "vnd.ms-playready.media.pyv"; + "vnd.nokia.interleaved-multimedia"; "vnd.nokia.videovoip"; + "vnd.objectvideo"; "vnd.radgamettools.bink"; + "vnd.radgamettools.smacker"; "vnd.sealed.mpeg1"; "vnd.sealed.mpeg4"; + "vnd.sealed.swf"; "vnd.sealedmedia.softseal.mov"; "vnd.uvvu.mp4"; + "vnd.vivo"; + ]) (Map.add "text" (Set.of_list - [ "1d-interleaved-parityfec"; "RED"; "cache-manifest"; "calendar" - ; "css"; "csv"; "csv-schema"; "directory" - ; "dns"; "ecmascript" - ; "encaprtp"; "enriched"; "example"; "fwdred"; "grammar-ref-list" - ; "html"; "javascript" - ; "jcr-cnd"; "markdown"; "mizar"; "n3"; "parameters"; "parityfec" - ; "plain"; "provenance-notation"; "prs.fallenstein.rst" - ; "prs.lines.tag"; "prs.prop.logic"; "raptorfec"; "rfc822-headers" - ; "richtext"; "rtf"; "rtp-enc-aescm128"; "rtploopback"; "rtx"; "sgml" - ; "t140"; "tab-separated-values"; "troff"; "turtle"; "ulpfec" - ; "uri-list"; "vcard"; "vnd.DMClientScript"; "vnd.IPTC.NITF" - ; "vnd.IPTC.NewsML"; "vnd.a"; "vnd.abc"; "vnd.curl" - ; "vnd.debian.copyright"; "vnd.dvb.subtitle" - ; "vnd.esmertec.theme-descriptor"; "vnd.fly"; "vnd.fmi.flexstor" - ; "vnd.graphviz"; "vnd.in3d.3dml"; "vnd.in3d.spot"; "vnd.latex-z" - ; "vnd.motorola.reflex"; "vnd.ms-mediapackage" - ; "vnd.net2phone.commcenter.command"; "vnd.radisys.msml-basic-layout" - ; "vnd.si.uricatalogue" - ; "vnd.sun.j2me.app-descriptor"; "vnd.trolltech.linguist" - ; "vnd.wap.si"; "vnd.wap.sl"; "vnd.wap.wml"; "vnd.wap.wmlscript" - ; "xml"; "xml-external-parsed-entity" ]) + [ + "1d-interleaved-parityfec"; "RED"; "cache-manifest"; "calendar"; + "css"; "csv"; "csv-schema"; "directory"; "dns"; "ecmascript"; + "encaprtp"; "enriched"; "example"; "fwdred"; "grammar-ref-list"; + "html"; "javascript"; "jcr-cnd"; "markdown"; "mizar"; "n3"; + "parameters"; "parityfec"; "plain"; "provenance-notation"; + "prs.fallenstein.rst"; "prs.lines.tag"; "prs.prop.logic"; + "raptorfec"; "rfc822-headers"; "richtext"; "rtf"; + "rtp-enc-aescm128"; "rtploopback"; "rtx"; "sgml"; "t140"; + "tab-separated-values"; "troff"; "turtle"; "ulpfec"; "uri-list"; + "vcard"; "vnd.DMClientScript"; "vnd.IPTC.NITF"; "vnd.IPTC.NewsML"; + "vnd.a"; "vnd.abc"; "vnd.curl"; "vnd.debian.copyright"; + "vnd.dvb.subtitle"; "vnd.esmertec.theme-descriptor"; "vnd.fly"; + "vnd.fmi.flexstor"; "vnd.graphviz"; "vnd.in3d.3dml"; + "vnd.in3d.spot"; "vnd.latex-z"; "vnd.motorola.reflex"; + "vnd.ms-mediapackage"; "vnd.net2phone.commcenter.command"; + "vnd.radisys.msml-basic-layout"; "vnd.si.uricatalogue"; + "vnd.sun.j2me.app-descriptor"; "vnd.trolltech.linguist"; + "vnd.wap.si"; "vnd.wap.sl"; "vnd.wap.wml"; "vnd.wap.wmlscript"; + "xml"; "xml-external-parsed-entity"; + ]) (Map.add "multipart" (Set.of_list - [ "alternative"; "appledouble"; "byteranges"; "digest"; "encrypted" - ; "example"; "form-data"; "header-set"; "mixed"; "parallel" - ; "related"; "report"; "signed"; "voice-message" - ; "x-mixed-replace" ]) + [ + "alternative"; "appledouble"; "byteranges"; "digest"; + "encrypted"; "example"; "form-data"; "header-set"; "mixed"; + "parallel"; "related"; "report"; "signed"; "voice-message"; + "x-mixed-replace"; + ]) (Map.add "model" (Set.of_list - [ "example"; "iges"; "mesh"; "vnd.collada+xml"; "vnd.dwf" - ; "vnd.flatland.3dml"; "vnd.gdl"; "vnd.gs-gdl"; "vnd.gtw" - ; "vnd.moml+xml"; "vnd.mts"; "vnd.opengex" - ; "vnd.parasolid.transmit.binary" - ; "vnd.parasolid.transmit.text" - ; "vnd.rosette.annotated-data-model" - ; "vnd.valve.source.compiled-map"; "vnd.vtu"; "vrml" - ; "x3d+fastinfoset"; "x3d+xml"; "x3d-vrml" ]) + [ + "example"; "iges"; "mesh"; "vnd.collada+xml"; "vnd.dwf"; + "vnd.flatland.3dml"; "vnd.gdl"; "vnd.gs-gdl"; "vnd.gtw"; + "vnd.moml+xml"; "vnd.mts"; "vnd.opengex"; + "vnd.parasolid.transmit.binary"; + "vnd.parasolid.transmit.text"; + "vnd.rosette.annotated-data-model"; + "vnd.valve.source.compiled-map"; "vnd.vtu"; "vrml"; + "x3d+fastinfoset"; "x3d+xml"; "x3d-vrml"; + ]) (Map.add "message" (Set.of_list - [ "CPIM"; "delivery-status"; "disposition-notification" - ; "example"; "external-body"; "feedback-report"; "global" - ; "global-delivery-status" - ; "global-disposition-notification"; "global-headers" - ; "http"; "imdn+xml"; "news" - ; "partial"; "rfc822"; "s-http"; "sip"; "sipfrag" - ; "tracking-status"; "vnd.si.simp" - ; "vnd.wfa.wsc" ]) + [ + "CPIM"; "delivery-status"; "disposition-notification"; + "example"; "external-body"; "feedback-report"; "global"; + "global-delivery-status"; + "global-disposition-notification"; "global-headers"; + "http"; "imdn+xml"; "news"; "partial"; "rfc822"; "s-http"; + "sip"; "sipfrag"; "tracking-status"; "vnd.si.simp"; + "vnd.wfa.wsc"; + ]) (Map.add "image" (Set.of_list - [ "bmp"; "cgm"; "emf"; "example"; "fits"; "g3fax"; "gif" - ; "ief"; "jp2"; "jpeg"; "jpm"; "jpx"; "ktx"; "naplps" - ; "png"; "prs.btif"; "prs.pti"; "pwg-raster"; "svg+xml" - ; "t38"; "tiff"; "tiff-fx"; "vnd.adobe.photoshop" - ; "vnd.airzip.accelerator.azv"; "vnd.cns.inf2" - ; "vnd.dece.graphic"; "vnd.djvu"; "vnd.dvb.subtitle" - ; "vnd.dwg"; "vnd.dxf"; "vnd.fastbidsheet"; "vnd.fpx" - ; "vnd.fst"; "vnd.fujixerox.edmics-mmr" - ; "vnd.fujixerox.edmics-rlc"; "vnd.globalgraphics.pgb" - ; "vnd.microsoft.icon"; "vnd.mix"; "vnd.mozilla.apng" - ; "vnd.ms-modi"; "vnd.net-fpx"; "vnd.radiance" - ; "vnd.sealed.png"; "vnd.sealedmedia.softseal.gif" - ; "vnd.sealedmedia.softseal.jpg"; "vnd.svf" - ; "vnd.tencent.tap"; "vnd.valve.source.texture" - ; "vnd.wap.wbmp"; "vnd.xiff"; "vnd.zbrush.pcx"; "wmf" - ; "x-emf" - ; "x-wmf" ]) + [ + "bmp"; "cgm"; "emf"; "example"; "fits"; "g3fax"; "gif"; + "ief"; "jp2"; "jpeg"; "jpm"; "jpx"; "ktx"; "naplps"; + "png"; "prs.btif"; "prs.pti"; "pwg-raster"; "svg+xml"; + "t38"; "tiff"; "tiff-fx"; "vnd.adobe.photoshop"; + "vnd.airzip.accelerator.azv"; "vnd.cns.inf2"; + "vnd.dece.graphic"; "vnd.djvu"; "vnd.dvb.subtitle"; + "vnd.dwg"; "vnd.dxf"; "vnd.fastbidsheet"; "vnd.fpx"; + "vnd.fst"; "vnd.fujixerox.edmics-mmr"; + "vnd.fujixerox.edmics-rlc"; "vnd.globalgraphics.pgb"; + "vnd.microsoft.icon"; "vnd.mix"; "vnd.mozilla.apng"; + "vnd.ms-modi"; "vnd.net-fpx"; "vnd.radiance"; + "vnd.sealed.png"; "vnd.sealedmedia.softseal.gif"; + "vnd.sealedmedia.softseal.jpg"; "vnd.svf"; + "vnd.tencent.tap"; "vnd.valve.source.texture"; + "vnd.wap.wbmp"; "vnd.xiff"; "vnd.zbrush.pcx"; "wmf"; + "x-emf"; "x-wmf"; + ]) (Map.add "examples" (Set.of_list []) (Map.add "audio" (Set.of_list - [ "1d-interleaved-parityfec"; "32kadpcm"; "3gpp" - ; "3gpp2"; "AMR"; "AMR-WB" - ; "ATRAC-ADVANCED-LOSSLESS"; "ATRAC-X"; "ATRAC3" - ; "BV16"; "BV32"; "CN"; "DAT12"; "DV"; "DVI4" - ; "EVRC"; "EVRC-QCP"; "EVRC0"; "EVRC1"; "EVRCB" - ; "EVRCB0"; "EVRCB1"; "EVRCNW"; "EVRCNW0" - ; "EVRCNW1"; "EVRCWB"; "EVRCWB0"; "EVRCWB1"; "EVS" - ; "G711-0"; "G719"; "G722"; "G7221"; "G723" - ; "G726-16"; "G726-24"; "G726-32"; "G726-40" - ; "G728"; "G729"; "G7291"; "G729D"; "G729E"; "GSM" - ; "GSM-EFR"; "GSM-HR-08"; "L16"; "L20"; "L24"; "L8" - ; "LPC"; "MP4A-LATM"; "MPA"; "PCMA"; "PCMA-WB" - ; "PCMU"; "PCMU-WB"; "QCELP"; "RED"; "SMV" - ; "SMV-QCP"; "SMV0"; "UEMCLIP"; "VDVI"; "VMR-WB" - ; "ac3"; "amr-wb+"; "aptx"; "asc"; "basic" - ; "clearmode"; "dls"; "dsr-es201108" - ; "dsr-es202050"; "dsr-es202211"; "dsr-es202212" - ; "eac3"; "encaprtp"; "example"; "fwdred"; "iLBC" - ; "ip-mr_v2.5"; "mobile-xmf"; "mp4"; "mpa-robust" - ; "mpeg"; "mpeg4-generic"; "ogg"; "opus" - ; "parityfec"; "prs.sid"; "raptorfec" - ; "rtp-enc-aescm128"; "rtp-midi"; "rtploopback" - ; "rtx"; "sp-midi"; "speex"; "t140c"; "t38" - ; "telephone-event"; "tone"; "ulpfec" - ; "vnd.3gpp.iufp"; "vnd.4SB"; "vnd.CELP" - ; "vnd.audiokoz"; "vnd.cisco.nse" - ; "vnd.cmles.radio-events"; "vnd.cns.anp1" - ; "vnd.cns.inf1"; "vnd.dece.audio" - ; "vnd.digital-winds"; "vnd.dlna.adts" - ; "vnd.dolby.heaac.1"; "vnd.dolby.heaac.2" - ; "vnd.dolby.mlp"; "vnd.dolby.mps"; "vnd.dolby.pl2" - ; "vnd.dolby.pl2x"; "vnd.dolby.pl2z" - ; "vnd.dolby.pulse.1"; "vnd.dra"; "vnd.dts" - ; "vnd.dts.hd"; "vnd.dvb.file"; "vnd.everad.plj" - ; "vnd.hns.audio"; "vnd.lucent.voice" - ; "vnd.ms-playready.media.pya" - ; "vnd.nokia.mobile-xmf"; "vnd.nortel.vbk" - ; "vnd.nuera.ecelp4800"; "vnd.nuera.ecelp7470" - ; "vnd.nuera.ecelp9600"; "vnd.octel.sbc" - ; "vnd.qcelp" - ; "vnd.rhetorex.32kadpcm"; "vnd.rip" - ; "vnd.sealedmedia.softseal.mpeg"; "vnd.vmx.cvsd" - ; "vorbis"; "vorbis-config" ]) + [ + "1d-interleaved-parityfec"; "32kadpcm"; "3gpp"; + "3gpp2"; "AMR"; "AMR-WB"; + "ATRAC-ADVANCED-LOSSLESS"; "ATRAC-X"; "ATRAC3"; + "BV16"; "BV32"; "CN"; "DAT12"; "DV"; "DVI4"; + "EVRC"; "EVRC-QCP"; "EVRC0"; "EVRC1"; "EVRCB"; + "EVRCB0"; "EVRCB1"; "EVRCNW"; "EVRCNW0"; + "EVRCNW1"; "EVRCWB"; "EVRCWB0"; "EVRCWB1"; "EVS"; + "G711-0"; "G719"; "G722"; "G7221"; "G723"; + "G726-16"; "G726-24"; "G726-32"; "G726-40"; + "G728"; "G729"; "G7291"; "G729D"; "G729E"; "GSM"; + "GSM-EFR"; "GSM-HR-08"; "L16"; "L20"; "L24"; "L8"; + "LPC"; "MP4A-LATM"; "MPA"; "PCMA"; "PCMA-WB"; + "PCMU"; "PCMU-WB"; "QCELP"; "RED"; "SMV"; + "SMV-QCP"; "SMV0"; "UEMCLIP"; "VDVI"; "VMR-WB"; + "ac3"; "amr-wb+"; "aptx"; "asc"; "basic"; + "clearmode"; "dls"; "dsr-es201108"; + "dsr-es202050"; "dsr-es202211"; "dsr-es202212"; + "eac3"; "encaprtp"; "example"; "fwdred"; "iLBC"; + "ip-mr_v2.5"; "mobile-xmf"; "mp4"; "mpa-robust"; + "mpeg"; "mpeg4-generic"; "ogg"; "opus"; + "parityfec"; "prs.sid"; "raptorfec"; + "rtp-enc-aescm128"; "rtp-midi"; "rtploopback"; + "rtx"; "sp-midi"; "speex"; "t140c"; "t38"; + "telephone-event"; "tone"; "ulpfec"; + "vnd.3gpp.iufp"; "vnd.4SB"; "vnd.CELP"; + "vnd.audiokoz"; "vnd.cisco.nse"; + "vnd.cmles.radio-events"; "vnd.cns.anp1"; + "vnd.cns.inf1"; "vnd.dece.audio"; + "vnd.digital-winds"; "vnd.dlna.adts"; + "vnd.dolby.heaac.1"; "vnd.dolby.heaac.2"; + "vnd.dolby.mlp"; "vnd.dolby.mps"; "vnd.dolby.pl2"; + "vnd.dolby.pl2x"; "vnd.dolby.pl2z"; + "vnd.dolby.pulse.1"; "vnd.dra"; "vnd.dts"; + "vnd.dts.hd"; "vnd.dvb.file"; "vnd.everad.plj"; + "vnd.hns.audio"; "vnd.lucent.voice"; + "vnd.ms-playready.media.pya"; + "vnd.nokia.mobile-xmf"; "vnd.nortel.vbk"; + "vnd.nuera.ecelp4800"; "vnd.nuera.ecelp7470"; + "vnd.nuera.ecelp9600"; "vnd.octel.sbc"; + "vnd.qcelp"; "vnd.rhetorex.32kadpcm"; "vnd.rip"; + "vnd.sealedmedia.softseal.mpeg"; "vnd.vmx.cvsd"; + "vorbis"; "vorbis-config"; + ]) (Map.add "application" (Set.of_list - [ "1d-interleaved-parityfec" - ; "3gpdash-qoe-report+xml"; "3gpp-ims+xml" - ; "A2L"; "AML"; "ATF"; "ATFX"; "ATXML" - ; "CALS-1840"; "CDFX+XML"; "CEA"; "CSTAdata+xml" - ; "DCD"; "DII"; "DIT"; "EDI-X12"; "EDI-consent" - ; "EDIFACT"; "EmergencyCallData.Comment+xml" - ; "EmergencyCallData.DeviceInfo+xml" - ; "EmergencyCallData.ProviderInfo+xml" - ; "EmergencyCallData.ServiceInfo+xml" - ; "EmergencyCallData.SubscriberInfo+xml"; "H224" - ; "LXF"; "MF4"; "ODX"; "PDX"; "activemessage" - ; "alto-costmap+json"; "alto-costmapfilter+json" - ; "alto-directory+json" - ; "alto-endpointcost+json" - ; "alto-endpointcostparams+json" - ; "alto-endpointprop+json" - ; "alto-endpointpropparams+json" - ; "alto-error+json"; "alto-networkmap+json" - ; "alto-networkmapfilter+json"; "andrew-inset" - ; "applefile"; "atom+xml"; "atomcat+xml" - ; "atomdeleted+xml"; "atomicmail"; "atomsvc+xml" - ; "auth-policy+xml"; "bacnet-xdd+zip" - ; "batch-SMTP"; "beep+xml"; "calendar+json" - ; "calendar+xml"; "call-completion"; "cbor" - ; "ccmp+xml"; "ccxml+xml"; "cdmi-capability" - ; "cdmi-container"; "cdmi-domain"; "cdmi-object" - ; "cdmi-queue"; "cdni"; "cea-2018+xml" - ; "cellml+xml"; "cfw"; "cms"; "cnrp+xml" - ; "coap-group+json"; "commonground" - ; "conference-info+xml"; "cpl+xml"; "csrattrs" - ; "csta+xml"; "csvm+json"; "cybercash" - ; "dash+xml"; "dashdelta"; "davmount+xml" - ; "dca-rft"; "dec-dx"; "dialog-info+xml" - ; "dicom"; "dns"; "dskpp+xml"; "dssc+der" - ; "dssc+xml"; "dvcs"; "ecmascript"; "efi" - ; "emma+xml"; "emotionml+xml"; "encaprtp" - ; "epp+xml"; "epub+zip"; "eshop"; "example" - ; "exi"; "fastinfoset"; "fastsoap"; "fdt+xml" - ; "fits"; "font-sfnt"; "font-tdpfr"; "font-woff" - ; "framework-attributes+xml"; "gzip"; "held+xml" - ; "http"; "hyperstudio"; "ibe-key-request+xml" - ; "ibe-pkg-reply+xml"; "ibe-pp-data"; "iges" - ; "im-iscomposing+xml"; "index"; "index.cmd" - ; "index.obj"; "index.response"; "index.vnd" - ; "inkml+xml"; "iotp"; "ipfix"; "ipp"; "isup" - ; "its+xml"; "javascript"; "jose"; "jose+json" - ; "jrd+json"; "json"; "json-patch+json" - ; "json-seq"; "jwk+json"; "jwk-set+json"; "jwt" - ; "kpml-request+xml"; "kpml-response+xml" - ; "ld+json"; "lgr+xml"; "link-format" - ; "load-control+xml"; "lost+xml"; "lostsync+xml" - ; "mac-binhex40"; "macwriteii"; "mads+xml" - ; "marc"; "marcxml+xml"; "mathematica" - ; "mathml+xml"; "mathml-content+xml" - ; "mathml-presentation+xml" - ; "mbms-associated-procedure-description+xml" - ; "mbms-deregister+xml"; "mbms-envelope+xml" - ; "mbms-msk+xml"; "mbms-msk-response+xml" - ; "mbms-protection-description+xml" - ; "mbms-reception-report+xml" - ; "mbms-register+xml" - ; "mbms-register-response+xml" - ; "mbms-schedule+xml" - ; "mbms-user-service-description+xml"; "mbox" - ; "media-policy-dataset+xml" - ; "media_control+xml"; "mediaservercontrol+xml" - ; "merge-patch+json"; "metalink4+xml" - ; "mets+xml"; "mikey"; "mods+xml"; "moss-keys" - ; "moss-signature"; "mosskey-data" - ; "mosskey-request"; "mp21"; "mp4" - ; "mpeg4-generic"; "mpeg4-iod"; "mpeg4-iod-xmt" - ; "mrb-consumer+xml"; "mrb-publish+xml" - ; "msc-ivr+xml"; "msc-mixer+xml"; "msword" - ; "mxf"; "nasdata"; "news-checkgroups" - ; "news-groupinfo"; "news-transmission" - ; "nlsml+xml"; "nss"; "ocsp-request" - ; "ocsp-response"; "octet-stream"; "oda" - ; "oebps-package+xml"; "ogg"; "oxps" - ; "p2p-overlay+xml"; "parityfec" - ; "patch-ops-error+xml"; "pdf"; "pgp-encrypted" - ; "pgp-keys"; "pgp-signature"; "pidf+xml" - ; "pidf-diff+xml"; "pkcs10"; "pkcs12" - ; "pkcs7-mime"; "pkcs7-signature"; "pkcs8" - ; "pkix-attr-cert"; "pkix-cert"; "pkix-crl" - ; "pkix-pkipath"; "pkixcmp"; "pls+xml" - ; "poc-settings+xml"; "postscript" - ; "ppsp-tracker+json"; "problem+json" - ; "problem+xml"; "provenance+xml" - ; "prs.alvestrand.titrax-sheet"; "prs.cww" - ; "prs.hpub+zip"; "prs.nprend"; "prs.plucker" - ; "prs.rdf-xml-crypt"; "prs.xsf+xml"; "pskc+xml" - ; "qsig"; "raptorfec"; "rdap+json"; "rdf+xml" - ; "reginfo+xml"; "relax-ng-compact-syntax" - ; "remote-printing"; "reputon+json" - ; "resource-lists+xml" - ; "resource-lists-diff+xml"; "rfc+xml"; "riscos" - ; "rlmi+xml"; "rls-services+xml" - ; "rpki-ghostbusters"; "rpki-manifest" - ; "rpki-roa"; "rpki-updown"; "rtf" - ; "rtploopback"; "rtx"; "samlassertion+xml" - ; "samlmetadata+xml"; "sbml+xml"; "scaip+xml" - ; "scim+json"; "scvp-cv-request" - ; "scvp-cv-response"; "scvp-vp-request" - ; "scvp-vp-response"; "sdp"; "sep+xml" - ; "sep-exi"; "session-info"; "set-payment" - ; "set-payment-initiation"; "set-registration" - ; "set-registration-initiation"; "sgml" - ; "sgml-open-catalog"; "shf+xml"; "sieve" - ; "simple-filter+xml"; "simple-message-summary" - ; "simpleSymbolContainer"; "slate" - ; "smil" - ; "smpte336m"; "soap+fastinfoset"; "soap+xml" - ; "sparql-query"; "sparql-results+xml" - ; "spirits-event+xml"; "sql"; "srgs"; "srgs+xml" - ; "sru+xml"; "ssml+xml"; "tamp-apex-update" - ; "tamp-apex-update-confirm" - ; "tamp-community-update" - ; "tamp-community-update-confirm"; "tamp-error" - ; "tamp-sequence-adjust" - ; "tamp-sequence-adjust-confirm" - ; "tamp-status-query"; "tamp-status-response" - ; "tamp-update"; "tamp-update-confirm" - ; "tei+xml"; "thraud+xml"; "timestamp-query" - ; "timestamp-reply"; "timestamped-data" - ; "ttml+xml"; "tve-trigger"; "ulpfec" - ; "urc-grpsheet+xml"; "urc-ressheet+xml" - ; "urc-targetdesc+xml"; "urc-uisocketdesc+xml" - ; "vcard+json"; "vcard+xml"; "vemmi" - ; "vnd.3M.Post-it-Notes"; "vnd.3gpp-prose+xml" - ; "vnd.3gpp-prose-pc3ch+xml" - ; "vnd.3gpp.SRVCC-info+xml" - ; "vnd.3gpp.access-transfer-events+xml" - ; "vnd.3gpp.bsf+xml"; "vnd.3gpp.mid-call+xml" - ; "vnd.3gpp.pic-bw-large" - ; "vnd.3gpp.pic-bw-small"; "vnd.3gpp.pic-bw-var" - ; "vnd.3gpp.sms"; "vnd.3gpp.sms+xml" - ; "vnd.3gpp.srvcc-ext+xml" - ; "vnd.3gpp.state-and-event-info+xml" - ; "vnd.3gpp.ussd+xml"; "vnd.3gpp2.bcmcsinfo+xml" - ; "vnd.3gpp2.sms"; "vnd.3gpp2.tcap" - ; "vnd.3lightssoftware.imagescal" - ; "vnd.FloGraphIt" - ; "vnd.HandHeld-Entertainment+xml"; "vnd.Kinar" - ; "vnd.MFER"; "vnd.Mobius.DAF"; "vnd.Mobius.DIS" - ; "vnd.Mobius.MBK"; "vnd.Mobius.MQY" - ; "vnd.Mobius.MSL"; "vnd.Mobius.PLC" - ; "vnd.Mobius.TXF"; "vnd.Quark.QuarkXPress" - ; "vnd.RenLearn.rlprint" - ; "vnd.SimTech-MindMapper" - ; "vnd.accpac.simply.aso" - ; "vnd.accpac.simply.imp"; "vnd.acucobol" - ; "vnd.acucorp"; "vnd.adobe.flash.movie" - ; "vnd.adobe.formscentral.fcdt"; "vnd.adobe.fxp" - ; "vnd.adobe.partial-upload" - ; "vnd.adobe.xdp+xml"; "vnd.adobe.xfdf" - ; "vnd.aether.imp"; "vnd.ah-barcode" - ; "vnd.ahead.space"; "vnd.airzip.filesecure.azf" - ; "vnd.airzip.filesecure.azs" - ; "vnd.americandynamics.acc"; "vnd.amiga.ami" - ; "vnd.amundsen.maze+xml"; "vnd.anki" - ; "vnd.anser-web-certificate-issue-initiation" - ; "vnd.antix.game-component" - ; "vnd.apache.thrift.binary" - ; "vnd.apache.thrift.compact" - ; "vnd.apache.thrift.json"; "vnd.api+json" - ; "vnd.apple.installer+xml"; "vnd.apple.mpegurl" - ; "vnd.arastra.swi" - ; "vnd.aristanetworks.swi"; "vnd.artsquare" - ; "vnd.astraea-software.iota"; "vnd.audiograph" - ; "vnd.autopackage"; "vnd.avistar+xml" - ; "vnd.balsamiq.bmml+xml"; "vnd.balsamiq.bmpr" - ; "vnd.bekitzur-stech+json" - ; "vnd.biopax.rdf+xml"; "vnd.blueice.multipass" - ; "vnd.bluetooth.ep.oob"; "vnd.bluetooth.le.oob" - ; "vnd.bmi"; "vnd.businessobjects" - ; "vnd.cab-jscript"; "vnd.canon-cpdl" - ; "vnd.canon-lips" - ; "vnd.cendio.thinlinc.clientconf" - ; "vnd.century-systems.tcp_stream" - ; "vnd.chemdraw+xml"; "vnd.chipnuts.karaoke-mmd" - ; "vnd.cinderella"; "vnd.cirpack.isdn-ext" - ; "vnd.citationstyles.style+xml"; "vnd.claymore" - ; "vnd.cloanto.rp9"; "vnd.clonk.c4group" - ; "vnd.cluetrust.cartomobile-config" - ; "vnd.cluetrust.cartomobile-config-pkg" - ; "vnd.coffeescript"; "vnd.collection+json" - ; "vnd.collection.doc+json" - ; "vnd.collection.next+json" - ; "vnd.commerce-battelle"; "vnd.commonspace" - ; "vnd.contact.cmsg"; "vnd.coreos.ignition+json" - ; "vnd.cosmocaller"; "vnd.crick.clicker" - ; "vnd.crick.clicker.keyboard" - ; "vnd.crick.clicker.palette" - ; "vnd.crick.clicker.template" - ; "vnd.crick.clicker.wordbank" - ; "vnd.criticaltools.wbs+xml"; "vnd.ctc-posml" - ; "vnd.ctct.ws+xml"; "vnd.cups-pdf" - ; "vnd.cups-postscript"; "vnd.cups-ppd" - ; "vnd.cups-raster"; "vnd.cups-raw"; "vnd.curl" - ; "vnd.cyan.dean.root+xml"; "vnd.cybank" - ; "vnd.dart"; "vnd.data-vision.rdz" - ; "vnd.debian.binary-package"; "vnd.dece.data" - ; "vnd.dece.ttml+xml"; "vnd.dece.unspecified" - ; "vnd.dece.zip"; "vnd.denovo.fcselayout-link" - ; "vnd.desmume.movie" - ; "vnd.dir-bi.plate-dl-nosuffix" - ; "vnd.dm.delegation+xml"; "vnd.dna" - ; "vnd.document+json"; "vnd.dolby.mobile.1" - ; "vnd.dolby.mobile.2" - ; "vnd.doremir.scorecloud-binary-document" - ; "vnd.dpgraph"; "vnd.dreamfactory" - ; "vnd.drive+json"; "vnd.dtg.local" - ; "vnd.dtg.local.flash"; "vnd.dtg.local.html" - ; "vnd.dvb.ait"; "vnd.dvb.dvbj" - ; "vnd.dvb.esgcontainer" - ; "vnd.dvb.ipdcdftnotifaccess" - ; "vnd.dvb.ipdcesgaccess" - ; "vnd.dvb.ipdcesgaccess2"; "vnd.dvb.ipdcesgpdd" - ; "vnd.dvb.ipdcroaming" - ; "vnd.dvb.iptv.alfec-base" - ; "vnd.dvb.iptv.alfec-enhancement" - ; "vnd.dvb.notif-aggregate-root+xml" - ; "vnd.dvb.notif-container+xml" - ; "vnd.dvb.notif-generic+xml" - ; "vnd.dvb.notif-ia-msglist+xml" - ; "vnd.dvb.notif-ia-registration-request+xml" - ; "vnd.dvb.notif-ia-registration-response+xml" - ; "vnd.dvb.notif-init+xml"; "vnd.dvb.pfr" - ; "vnd.dvb.service"; "vnd.dxr"; "vnd.dynageo" - ; "vnd.dzr"; "vnd.easykaraoke.cdgdownload" - ; "vnd.ecdis-update"; "vnd.ecowin.chart" - ; "vnd.ecowin.filerequest" - ; "vnd.ecowin.fileupdate"; "vnd.ecowin.series" - ; "vnd.ecowin.seriesrequest" - ; "vnd.ecowin.seriesupdate" - ; "vnd.emclient.accessrequest+xml" - ; "vnd.enliven"; "vnd.enphase.envoy" - ; "vnd.eprints.data+xml"; "vnd.epson.esf" - ; "vnd.epson.msf"; "vnd.epson.quickanime" - ; "vnd.epson.salt"; "vnd.epson.ssf" - ; "vnd.ericsson.quickcall"; "vnd.eszigno3+xml" - ; "vnd.etsi.aoc+xml"; "vnd.etsi.asic-e+zip" - ; "vnd.etsi.asic-s+zip"; "vnd.etsi.cug+xml" - ; "vnd.etsi.iptvcommand+xml" - ; "vnd.etsi.iptvdiscovery+xml" - ; "vnd.etsi.iptvprofile+xml" - ; "vnd.etsi.iptvsad-bc+xml" - ; "vnd.etsi.iptvsad-cod+xml" - ; "vnd.etsi.iptvsad-npvr+xml" - ; "vnd.etsi.iptvservice+xml" - ; "vnd.etsi.iptvsync+xml" - ; "vnd.etsi.iptvueprofile+xml" - ; "vnd.etsi.mcid+xml"; "vnd.etsi.mheg5" - ; "vnd.etsi.overload-control-policy-dataset+xml" - ; "vnd.etsi.pstn+xml"; "vnd.etsi.sci+xml" - ; "vnd.etsi.simservs+xml" - ; "vnd.etsi.timestamp-token"; "vnd.etsi.tsl+xml" - ; "vnd.etsi.tsl.der"; "vnd.eudora.data" - ; "vnd.ezpix-album"; "vnd.ezpix-package" - ; "vnd.f-secure.mobile" - ; "vnd.fastcopy-disk-image"; "vnd.fdf" - ; "vnd.fdsn.mseed"; "vnd.fdsn.seed"; "vnd.ffsns" - ; "vnd.filmit.zfc"; "vnd.fints" - ; "vnd.firemonkeys.cloudcell" - ; "vnd.fluxtime.clip"; "vnd.font-fontforge-sfd" - ; "vnd.framemaker"; "vnd.frogans.fnc" - ; "vnd.frogans.ltf"; "vnd.fsc.weblaunch" - ; "vnd.fujitsu.oasys"; "vnd.fujitsu.oasys2" - ; "vnd.fujitsu.oasys3"; "vnd.fujitsu.oasysgp" - ; "vnd.fujitsu.oasysprs"; "vnd.fujixerox.ART-EX" - ; "vnd.fujixerox.ART4"; "vnd.fujixerox.HBPL" - ; "vnd.fujixerox.ddd"; "vnd.fujixerox.docuworks" - ; "vnd.fujixerox.docuworks.binder" - ; "vnd.fujixerox.docuworks.container" - ; "vnd.fut-misnet"; "vnd.fuzzysheet" - ; "vnd.genomatix.tuxedo"; "vnd.geo+json" - ; "vnd.geocube+xml" - ; "vnd.geogebra.file"; "vnd.geogebra.tool" - ; "vnd.geometry-explorer"; "vnd.geonext" - ; "vnd.geoplan"; "vnd.geospace"; "vnd.gerber" - ; "vnd.globalplatform.card-content-mgt" - ; "vnd.globalplatform.card-content-mgt-response" - ; "vnd.gmx" - ; "vnd.google-earth.kml+xml" - ; "vnd.google-earth.kmz" - ; "vnd.gov.sk.e-form+xml" - ; "vnd.gov.sk.e-form+zip" - ; "vnd.gov.sk.xmldatacontainer+xml" - ; "vnd.grafeq"; "vnd.gridmp" - ; "vnd.groove-account"; "vnd.groove-help" - ; "vnd.groove-identity-message" - ; "vnd.groove-injector" - ; "vnd.groove-tool-message" - ; "vnd.groove-tool-template"; "vnd.groove-vcard" - ; "vnd.hal+json"; "vnd.hal+xml"; "vnd.hbci" - ; "vnd.hcl-bireports"; "vnd.hdt" - ; "vnd.heroku+json"; "vnd.hhe.lesson-player" - ; "vnd.hp-HPGL"; "vnd.hp-PCL"; "vnd.hp-PCLXL" - ; "vnd.hp-hpid"; "vnd.hp-hps"; "vnd.hp-jlyt" - ; "vnd.httphone"; "vnd.hydrostatix.sof-data" - ; "vnd.hyperdrive+json"; "vnd.hzn-3d-crossword" - ; "vnd.ibm.MiniPay"; "vnd.ibm.afplinedata" - ; "vnd.ibm.electronic-media"; "vnd.ibm.modcap" - ; "vnd.ibm.rights-management" - ; "vnd.ibm.secure-container"; "vnd.iccprofile" - ; "vnd.ieee.1905"; "vnd.igloader" - ; "vnd.immervision-ivp"; "vnd.immervision-ivu" - ; "vnd.ims.imsccv1p1"; "vnd.ims.imsccv1p2" - ; "vnd.ims.imsccv1p3" - ; "vnd.ims.lis.v2.result+json" - ; "vnd.ims.lti.v2.toolconsumerprofile+json" - ; "vnd.ims.lti.v2.toolproxy+json" - ; "vnd.ims.lti.v2.toolproxy.id+json" - ; "vnd.ims.lti.v2.toolsettings+json" - ; "vnd.ims.lti.v2.toolsettings.simple+json" - ; "vnd.informedcontrol.rms+xml" - ; "vnd.informix-visionary" - ; "vnd.infotech.project" - ; "vnd.infotech.project+xml" - ; "vnd.innopath.wamp.notification" - ; "vnd.insors.igm"; "vnd.intercon.formnet" - ; "vnd.intergeo"; "vnd.intertrust.digibox" - ; "vnd.intertrust.nncp"; "vnd.intu.qbo" - ; "vnd.intu.qfx"; "vnd.iptc.g2.catalogitem+xml" - ; "vnd.iptc.g2.conceptitem+xml" - ; "vnd.iptc.g2.knowledgeitem+xml" - ; "vnd.iptc.g2.newsitem+xml" - ; "vnd.iptc.g2.newsmessage+xml" - ; "vnd.iptc.g2.packageitem+xml" - ; "vnd.iptc.g2.planningitem+xml" - ; "vnd.ipunplugged.rcprofile" - ; "vnd.irepository.package+xml"; "vnd.is-xpr" - ; "vnd.isac.fcs"; "vnd.jam" - ; "vnd.japannet-directory-service" - ; "vnd.japannet-jpnstore-wakeup" - ; "vnd.japannet-payment-wakeup" - ; "vnd.japannet-registration" - ; "vnd.japannet-registration-wakeup" - ; "vnd.japannet-setstore-wakeup" - ; "vnd.japannet-verification" - ; "vnd.japannet-verification-wakeup" - ; "vnd.jcp.javame.midlet-rms"; "vnd.jisp" - ; "vnd.joost.joda-archive"; "vnd.jsk.isdn-ngn" - ; "vnd.kahootz"; "vnd.kde.karbon" - ; "vnd.kde.kchart"; "vnd.kde.kformula" - ; "vnd.kde.kivio"; "vnd.kde.kontour" - ; "vnd.kde.kpresenter"; "vnd.kde.kspread" - ; "vnd.kde.kword"; "vnd.kenameaapp" - ; "vnd.kidspiration"; "vnd.koan" - ; "vnd.kodak-descriptor"; "vnd.las.las+xml" - ; "vnd.liberty-request+xml" - ; "vnd.llamagraphics.life-balance.desktop" - ; "vnd.llamagraphics.life-balance.exchange+xml" - ; "vnd.lotus-1-2-3"; "vnd.lotus-approach" - ; "vnd.lotus-freelance"; "vnd.lotus-notes" - ; "vnd.lotus-organizer"; "vnd.lotus-screencam" - ; "vnd.lotus-wordpro"; "vnd.macports.portpkg" - ; "vnd.mapbox-vector-tile" - ; "vnd.marlin.drm.actiontoken+xml" - ; "vnd.marlin.drm.conftoken+xml" - ; "vnd.marlin.drm.license+xml" - ; "vnd.marlin.drm.mdcf"; "vnd.mason+json" - ; "vnd.maxmind.maxmind-db"; "vnd.mcd" - ; "vnd.medcalcdata"; "vnd.mediastation.cdkey" - ; "vnd.meridian-slingshot"; "vnd.mfmp" - ; "vnd.micro+json"; "vnd.micrografx.flo" - ; "vnd.micrografx.igx" - ; "vnd.microsoft.portable-executable" - ; "vnd.miele+json"; "vnd.mif" - ; "vnd.minisoft-hp3000-save" - ; "vnd.mitsubishi.misty-guard.trustweb" - ; "vnd.mophun.application" - ; "vnd.mophun.certificate" - ; "vnd.motorola.flexsuite" - ; "vnd.motorola.flexsuite.adsi" - ; "vnd.motorola.flexsuite.fis" - ; "vnd.motorola.flexsuite.gotap" - ; "vnd.motorola.flexsuite.kmr" - ; "vnd.motorola.flexsuite.ttc" - ; "vnd.motorola.flexsuite.wem" - ; "vnd.motorola.iprm"; "vnd.mozilla.xul+xml" - ; "vnd.ms-3mfdocument" - ; "vnd.ms-PrintDeviceCapabilities+xml" - ; "vnd.ms-PrintSchemaTicket+xml" - ; "vnd.ms-artgalry"; "vnd.ms-asf" - ; "vnd.ms-cab-compressed"; "vnd.ms-excel" - ; "vnd.ms-excel.addin.macroEnabled.12" - ; "vnd.ms-excel.sheet.binary.macroEnabled.12" - ; "vnd.ms-excel.sheet.macroEnabled.12" - ; "vnd.ms-excel.template.macroEnabled.12" - ; "vnd.ms-fontobject"; "vnd.ms-htmlhelp" - ; "vnd.ms-ims"; "vnd.ms-lrm" - ; "vnd.ms-office.activeX+xml" - ; "vnd.ms-officetheme" - ; "vnd.ms-playready.initiator+xml" - ; "vnd.ms-powerpoint" - ; "vnd.ms-powerpoint.addin.macroEnabled.12" - ; "vnd.ms-powerpoint.presentation.macroEnabled.12" - ; "vnd.ms-powerpoint.slide.macroEnabled.12" - ; "vnd.ms-powerpoint.slideshow.macroEnabled.12" - ; "vnd.ms-powerpoint.template.macroEnabled.12" - ; "vnd.ms-project"; "vnd.ms-tnef" - ; "vnd.ms-windows.devicepairing" - ; "vnd.ms-windows.nwprinting.oob" - ; "vnd.ms-windows.printerpairing" - ; "vnd.ms-windows.wsd.oob" - ; "vnd.ms-wmdrm.lic-chlg-req" - ; "vnd.ms-wmdrm.lic-resp" - ; "vnd.ms-wmdrm.meter-chlg-req" - ; "vnd.ms-wmdrm.meter-resp" - ; "vnd.ms-word.document.macroEnabled.12" - ; "vnd.ms-word.template.macroEnabled.12" - ; "vnd.ms-works"; "vnd.ms-wpl" - ; "vnd.ms-xpsdocument"; "vnd.msa-disk-image" - ; "vnd.mseq"; "vnd.msign"; "vnd.multiad.creator" - ; "vnd.multiad.creator.cif"; "vnd.music-niff" - ; "vnd.musician"; "vnd.muvee.style"; "vnd.mynfc" - ; "vnd.ncd.control"; "vnd.ncd.reference" - ; "vnd.nervana"; "vnd.netfpx" - ; "vnd.neurolanguage.nlu" - ; "vnd.nintendo.nitro.rom" - ; "vnd.nintendo.snes.rom"; "vnd.nitf" - ; "vnd.noblenet-directory" - ; "vnd.noblenet-sealer"; "vnd.noblenet-web" - ; "vnd.nokia.catalogs"; "vnd.nokia.conml+wbxml" - ; "vnd.nokia.conml+xml" - ; "vnd.nokia.iSDS-radio-presets" - ; "vnd.nokia.iptv.config+xml" - ; "vnd.nokia.landmark+wbxml" - ; "vnd.nokia.landmark+xml" - ; "vnd.nokia.landmarkcollection+xml" - ; "vnd.nokia.n-gage.ac+xml" - ; "vnd.nokia.n-gage.data" - ; "vnd.nokia.n-gage.symbian.install" - ; "vnd.nokia.ncd"; "vnd.nokia.pcd+wbxml" - ; "vnd.nokia.pcd+xml"; "vnd.nokia.radio-preset" - ; "vnd.nokia.radio-presets"; "vnd.novadigm.EDM" - ; "vnd.novadigm.EDX"; "vnd.novadigm.EXT" - ; "vnd.ntt-local.content-share" - ; "vnd.ntt-local.file-transfer" - ; "vnd.ntt-local.ogw_remote-access" - ; "vnd.ntt-local.sip-ta_remote" - ; "vnd.ntt-local.sip-ta_tcp_stream" - ; "vnd.oasis.opendocument.chart" - ; "vnd.oasis.opendocument.chart-template" - ; "vnd.oasis.opendocument.database" - ; "vnd.oasis.opendocument.formula" - ; "vnd.oasis.opendocument.formula-template" - ; "vnd.oasis.opendocument.graphics" - ; "vnd.oasis.opendocument.graphics-template" - ; "vnd.oasis.opendocument.image" - ; "vnd.oasis.opendocument.image-template" - ; "vnd.oasis.opendocument.presentation" - ; "vnd.oasis.opendocument.presentation-template" - ; "vnd.oasis.opendocument.spreadsheet" - ; "vnd.oasis.opendocument.spreadsheet-template" - ; "vnd.oasis.opendocument.text" - ; "vnd.oasis.opendocument.text-master" - ; "vnd.oasis.opendocument.text-template" - ; "vnd.oasis.opendocument.text-web"; "vnd.obn" - ; "vnd.oftn.l10n+json" - ; "vnd.oipf.contentaccessdownload+xml" - ; "vnd.oipf.contentaccessstreaming+xml" - ; "vnd.oipf.cspg-hexbinary" - ; "vnd.oipf.dae.svg+xml" - ; "vnd.oipf.dae.xhtml+xml" - ; "vnd.oipf.mippvcontrolmessage+xml" - ; "vnd.oipf.pae.gem"; "vnd.oipf.spdiscovery+xml" - ; "vnd.oipf.spdlist+xml" - ; "vnd.oipf.ueprofile+xml" - ; "vnd.oipf.userprofile+xml"; "vnd.olpc-sugar" - ; "vnd.oma-scws-config" - ; "vnd.oma-scws-http-request" - ; "vnd.oma-scws-http-response" - ; "vnd.oma.bcast.associated-procedure-parameter+xml" - ; "vnd.oma.bcast.drm-trigger+xml" - ; "vnd.oma.bcast.imd+xml"; "vnd.oma.bcast.ltkm" - ; "vnd.oma.bcast.notification+xml" - ; "vnd.oma.bcast.provisioningtrigger" - ; "vnd.oma.bcast.sgboot" - ; "vnd.oma.bcast.sgdd+xml"; "vnd.oma.bcast.sgdu" - ; "vnd.oma.bcast.simple-symbol-container" - ; "vnd.oma.bcast.smartcard-trigger+xml" - ; "vnd.oma.bcast.sprov+xml" - ; "vnd.oma.bcast.stkm" - ; "vnd.oma.cab-address-book+xml" - ; "vnd.oma.cab-feature-handler+xml" - ; "vnd.oma.cab-pcc+xml" - ; "vnd.oma.cab-subs-invite+xml" - ; "vnd.oma.cab-user-prefs+xml"; "vnd.oma.dcd" - ; "vnd.oma.dcdc"; "vnd.oma.dd2+xml" - ; "vnd.oma.drm.risd+xml" - ; "vnd.oma.group-usage-list+xml" - ; "vnd.oma.lwm2m+json"; "vnd.oma.pal+xml" - ; "vnd.oma.poc.detailed-progress-report+xml" - ; "vnd.oma.poc.final-report+xml" - ; "vnd.oma.poc.groups+xml" - ; "vnd.oma.poc.invocation-descriptor+xml" - ; "vnd.oma.poc.optimized-progress-report+xml" - ; "vnd.oma.push"; "vnd.oma.scidm.messages+xml" - ; "vnd.oma.xcap-directory+xml" - ; "vnd.omads-email+xml"; "vnd.omads-file+xml" - ; "vnd.omads-folder+xml"; "vnd.omaloc-supl-init" - ; "vnd.onepager"; "vnd.openblox.game+xml" - ; "vnd.openblox.game-binary"; "vnd.openeye.oeb" - ; "vnd.openxmlformats-officedocument.custom-properties+xml" - ; "vnd.openxmlformats-officedocument.customXmlProperties+xml" - ; "vnd.openxmlformats-officedocument.drawing+xml" - ; "vnd.openxmlformats-officedocument.drawingml.chart+xml" - ; "vnd.openxmlformats-officedocument.drawingml.chartshapes+xml" - ; "vnd.openxmlformats-officedocument.drawingml.diagramColors+xml" - ; "vnd.openxmlformats-officedocument.drawingml.diagramData+xml" - ; "vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml" - ; "vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml" - ; "vnd.openxmlformats-officedocument.extended-properties+xml" - ; "vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml" - ; "vnd.openxmlformats-officedocument.presentationml.comments+xml" - ; "vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml" - ; "vnd.openxmlformats-officedocument.presentationml.notesMaster+xml" - ; "vnd.openxmlformats-officedocument.presentationml.notesSlide+xml" - ; "vnd.openxmlformats-officedocument.presentationml.presProps+xml" - ; "vnd.openxmlformats-officedocument.presentationml.presentation" - ; "vnd.openxmlformats-officedocument.presentationml.presentation.main+xml" - ; "vnd.openxmlformats-officedocument.presentationml.slide" - ; "vnd.openxmlformats-officedocument.presentationml.slide+xml" - ; "vnd.openxmlformats-officedocument.presentationml.slideLayout+xml" - ; "vnd.openxmlformats-officedocument.presentationml.slideMaster+xml" - ; "vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo+xml" - ; "vnd.openxmlformats-officedocument.presentationml.slideshow" - ; "vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml" - ; "vnd.openxmlformats-officedocument.presentationml.tableStyles+xml" - ; "vnd.openxmlformats-officedocument.presentationml.tags+xml" - ; "vnd.openxmlformats-officedocument.presentationml.template" - ; "vnd.openxmlformats-officedocument.presentationml.template.main+xml" - ; "vnd.openxmlformats-officedocument.presentationml.viewProps+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.comments+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.connections+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.sheet" - ; "vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.styles+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.table+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.template" - ; "vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml" - ; "vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml" - ; "vnd.openxmlformats-officedocument.theme+xml" - ; "vnd.openxmlformats-officedocument.themeOverride+xml" - ; "vnd.openxmlformats-officedocument.vmlDrawing" - ; "vnd.openxmlformats-officedocument.wordprocessingml.comments+xml" - ; "vnd.openxmlformats-officedocument.wordprocessingml.document" - ; "vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml" - ; "vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml" - ; "vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml" - ; "vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml" - ; "vnd.openxmlformats-officedocument.wordprocessingml.footer+xml" - ; "vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml" - ; "vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml" - ; "vnd.openxmlformats-officedocument.wordprocessingml.settings+xml" - ; "vnd.openxmlformats-officedocument.wordprocessingml.styles+xml" - ; "vnd.openxmlformats-officedocument.wordprocessingml.template" - ; "vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml" - ; "vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml" - ; "vnd.openxmlformats-package.core-properties+xml" - ; "vnd.openxmlformats-package.digital-signature-xmlsignature+xml" - ; "vnd.openxmlformats-package.relationships+xml" - ; "vnd.oracle.resource+json" - ; "vnd.orange.indata"; "vnd.osa.netdeploy" - ; "vnd.osgeo.mapguide.package" - ; "vnd.osgi.bundle"; "vnd.osgi.dp" - ; "vnd.osgi.subsystem"; "vnd.otps.ct-kip+xml" - ; "vnd.oxli.countgraph"; "vnd.pagerduty+json" - ; "vnd.palm"; "vnd.panoply"; "vnd.paos.xml" - ; "vnd.pawaafile"; "vnd.pcos"; "vnd.pg.format" - ; "vnd.pg.osasli" - ; "vnd.piaccess.application-licence" - ; "vnd.picsel"; "vnd.pmi.widget" - ; "vnd.poc.group-advertisement+xml" - ; "vnd.pocketlearn"; "vnd.powerbuilder6" - ; "vnd.powerbuilder6-s"; "vnd.powerbuilder7" - ; "vnd.powerbuilder7-s"; "vnd.powerbuilder75" - ; "vnd.powerbuilder75-s"; "vnd.preminet" - ; "vnd.previewsystems.box" - ; "vnd.proteus.magazine" - ; "vnd.publishare-delta-tree"; "vnd.pvi.ptid1" - ; "vnd.pwg-multiplexed" - ; "vnd.pwg-xhtml-print+xml" - ; "vnd.qualcomm.brew-app-res" - ; "vnd.quarantainenet" - ; "vnd.quobject-quoxdocument" - ; "vnd.radisys.moml+xml"; "vnd.radisys.msml+xml" - ; "vnd.radisys.msml-audit+xml" - ; "vnd.radisys.msml-audit-conf+xml" - ; "vnd.radisys.msml-audit-conn+xml" - ; "vnd.radisys.msml-audit-dialog+xml" - ; "vnd.radisys.msml-audit-stream+xml" - ; "vnd.radisys.msml-conf+xml" - ; "vnd.radisys.msml-dialog+xml" - ; "vnd.radisys.msml-dialog-base+xml" - ; "vnd.radisys.msml-dialog-fax-detect+xml" - ; "vnd.radisys.msml-dialog-fax-sendrecv+xml" - ; "vnd.radisys.msml-dialog-group+xml" - ; "vnd.radisys.msml-dialog-speech+xml" - ; "vnd.radisys.msml-dialog-transform+xml" - ; "vnd.rainstor.data"; "vnd.rapid" - ; "vnd.realvnc.bed"; "vnd.recordare.musicxml" - ; "vnd.recordare.musicxml+xml" - ; "vnd.rig.cryptonote"; "vnd.route66.link66+xml" - ; "vnd.rs-274x"; "vnd.ruckus.download" - ; "vnd.s3sms"; "vnd.sailingtracker.track" - ; "vnd.sbm.cid"; "vnd.sbm.mid2"; "vnd.scribus" - ; "vnd.sealed.3df"; "vnd.sealed.csf" - ; "vnd.sealed.doc"; "vnd.sealed.eml" - ; "vnd.sealed.mht"; "vnd.sealed.net" - ; "vnd.sealed.ppt"; "vnd.sealed.tiff" - ; "vnd.sealed.xls" - ; "vnd.sealedmedia.softseal.html" - ; "vnd.sealedmedia.softseal.pdf"; "vnd.seemail" - ; "vnd.sema"; "vnd.semd"; "vnd.semf" - ; "vnd.shana.informed.formdata" - ; "vnd.shana.informed.formtemplate" - ; "vnd.shana.informed.interchange" - ; "vnd.shana.informed.package"; "vnd.siren+json" - ; "vnd.smaf"; "vnd.smart.notebook" - ; "vnd.smart.teacher" - ; "vnd.software602.filler.form+xml" - ; "vnd.software602.filler.form-xml-zip" - ; "vnd.solent.sdkm+xml"; "vnd.spotfire.dxp" - ; "vnd.spotfire.sfs"; "vnd.sss-cod" - ; "vnd.sss-dtf"; "vnd.sss-ntf" - ; "vnd.stepmania.package" - ; "vnd.stepmania.stepchart"; "vnd.street-stream" - ; "vnd.sun.wadl+xml"; "vnd.sus-calendar" - ; "vnd.svd"; "vnd.swiftview-ics" - ; "vnd.syncml+xml"; "vnd.syncml.dm+wbxml" - ; "vnd.syncml.dm+xml" - ; "vnd.syncml.dm.notification" - ; "vnd.syncml.dmddf+wbxml" - ; "vnd.syncml.dmddf+xml" - ; "vnd.syncml.dmtnds+wbxml" - ; "vnd.syncml.dmtnds+xml" - ; "vnd.syncml.ds.notification" - ; "vnd.tao.intent-module-archive" - ; "vnd.tcpdump.pcap" - ; "vnd.tmd.mediaflex.api+xml"; "vnd.tml" - ; "vnd.tmobile-livetv"; "vnd.trid.tpt" - ; "vnd.triscape.mxs"; "vnd.trueapp" - ; "vnd.truedoc"; "vnd.ubisoft.webplayer" - ; "vnd.ufdl"; "vnd.uiq.theme"; "vnd.umajin" - ; "vnd.unity"; "vnd.uoml+xml" - ; "vnd.uplanet.alert"; "vnd.uplanet.alert-wbxml" - ; "vnd.uplanet.bearer-choice" - ; "vnd.uplanet.bearer-choice-wbxml" - ; "vnd.uplanet.cacheop" - ; "vnd.uplanet.cacheop-wbxml" - ; "vnd.uplanet.channel" - ; "vnd.uplanet.channel-wbxml" - ; "vnd.uplanet.list"; "vnd.uplanet.list-wbxml" - ; "vnd.uplanet.listcmd" - ; "vnd.uplanet.listcmd-wbxml" - ; "vnd.uplanet.signal"; "vnd.uri-map" - ; "vnd.valve.source.material"; "vnd.vcx" - ; "vnd.vd-study"; "vnd.vectorworks" - ; "vnd.vel+json"; "vnd.verimatrix.vcas" - ; "vnd.vidsoft.vidconference"; "vnd.visio" - ; "vnd.visionary"; "vnd.vividence.scriptfile" - ; "vnd.vsf"; "vnd.wap.sic"; "vnd.wap.slc" - ; "vnd.wap.wbxml"; "vnd.wap.wmlc" - ; "vnd.wap.wmlscriptc"; "vnd.webturbo" - ; "vnd.wfa.p2p"; "vnd.wfa.wsc" - ; "vnd.windows.devicepairing"; "vnd.wmc" - ; "vnd.wmf.bootstrap"; "vnd.wolfram.mathematica" - ; "vnd.wolfram.mathematica.package" - ; "vnd.wolfram.player"; "vnd.wordperfect" - ; "vnd.wqd"; "vnd.wrq-hp3000-labelled" - ; "vnd.wt.stf"; "vnd.wv.csp+wbxml" - ; "vnd.wv.csp+xml"; "vnd.wv.ssp+xml" - ; "vnd.xacml+json"; "vnd.xara"; "vnd.xfdl" - ; "vnd.xfdl.webform"; "vnd.xmi+xml" - ; "vnd.xmpie.cpkg"; "vnd.xmpie.dpkg" - ; "vnd.xmpie.plan"; "vnd.xmpie.ppkg" - ; "vnd.xmpie.xlim"; "vnd.yamaha.hv-dic" - ; "vnd.yamaha.hv-script"; "vnd.yamaha.hv-voice" - ; "vnd.yamaha.openscoreformat" - ; "vnd.yamaha.openscoreformat.osfpvg+xml" - ; "vnd.yamaha.remote-setup" - ; "vnd.yamaha.smaf-audio" - ; "vnd.yamaha.smaf-phrase" - ; "vnd.yamaha.through-ngn" - ; "vnd.yamaha.tunnel-udpencap"; "vnd.yaoweme" - ; "vnd.yellowriver-custom-menu"; "vnd.zul" - ; "vnd.zzazz.deck+xml"; "voicexml+xml" - ; "vq-rtcpxr"; "watcherinfo+xml" - ; "whoispp-query"; "whoispp-response"; "widget" - ; "wita"; "wordperfect5.1"; "wsdl+xml" - ; "wspolicy+xml"; "x-www-form-urlencoded" - ; "x400-bp"; "xacml+xml"; "xcap-att+xml" - ; "xcap-caps+xml"; "xcap-diff+xml" - ; "xcap-el+xml"; "xcap-error+xml"; "xcap-ns+xml" - ; "xcon-conference-info+xml" - ; "xcon-conference-info-diff+xml"; "xenc+xml" - ; "xhtml+xml"; "xml"; "xml-dtd" - ; "xml-external-parsed-entity"; "xml-patch+xml" - ; "xmpp+xml"; "xop+xml"; "xslt+xml"; "xv+xml" - ; "yang"; "yin+xml"; "zip"; "zlib" ]) + [ + "1d-interleaved-parityfec"; + "3gpdash-qoe-report+xml"; "3gpp-ims+xml"; + "A2L"; "AML"; "ATF"; "ATFX"; "ATXML"; + "CALS-1840"; "CDFX+XML"; "CEA"; "CSTAdata+xml"; + "DCD"; "DII"; "DIT"; "EDI-X12"; "EDI-consent"; + "EDIFACT"; "EmergencyCallData.Comment+xml"; + "EmergencyCallData.DeviceInfo+xml"; + "EmergencyCallData.ProviderInfo+xml"; + "EmergencyCallData.ServiceInfo+xml"; + "EmergencyCallData.SubscriberInfo+xml"; "H224"; + "LXF"; "MF4"; "ODX"; "PDX"; "activemessage"; + "alto-costmap+json"; "alto-costmapfilter+json"; + "alto-directory+json"; + "alto-endpointcost+json"; + "alto-endpointcostparams+json"; + "alto-endpointprop+json"; + "alto-endpointpropparams+json"; + "alto-error+json"; "alto-networkmap+json"; + "alto-networkmapfilter+json"; "andrew-inset"; + "applefile"; "atom+xml"; "atomcat+xml"; + "atomdeleted+xml"; "atomicmail"; "atomsvc+xml"; + "auth-policy+xml"; "bacnet-xdd+zip"; + "batch-SMTP"; "beep+xml"; "calendar+json"; + "calendar+xml"; "call-completion"; "cbor"; + "ccmp+xml"; "ccxml+xml"; "cdmi-capability"; + "cdmi-container"; "cdmi-domain"; "cdmi-object"; + "cdmi-queue"; "cdni"; "cea-2018+xml"; + "cellml+xml"; "cfw"; "cms"; "cnrp+xml"; + "coap-group+json"; "commonground"; + "conference-info+xml"; "cpl+xml"; "csrattrs"; + "csta+xml"; "csvm+json"; "cybercash"; + "dash+xml"; "dashdelta"; "davmount+xml"; + "dca-rft"; "dec-dx"; "dialog-info+xml"; + "dicom"; "dns"; "dskpp+xml"; "dssc+der"; + "dssc+xml"; "dvcs"; "ecmascript"; "efi"; + "emma+xml"; "emotionml+xml"; "encaprtp"; + "epp+xml"; "epub+zip"; "eshop"; "example"; + "exi"; "fastinfoset"; "fastsoap"; "fdt+xml"; + "fits"; "font-sfnt"; "font-tdpfr"; "font-woff"; + "framework-attributes+xml"; "gzip"; "held+xml"; + "http"; "hyperstudio"; "ibe-key-request+xml"; + "ibe-pkg-reply+xml"; "ibe-pp-data"; "iges"; + "im-iscomposing+xml"; "index"; "index.cmd"; + "index.obj"; "index.response"; "index.vnd"; + "inkml+xml"; "iotp"; "ipfix"; "ipp"; "isup"; + "its+xml"; "javascript"; "jose"; "jose+json"; + "jrd+json"; "json"; "json-patch+json"; + "json-seq"; "jwk+json"; "jwk-set+json"; "jwt"; + "kpml-request+xml"; "kpml-response+xml"; + "ld+json"; "lgr+xml"; "link-format"; + "load-control+xml"; "lost+xml"; "lostsync+xml"; + "mac-binhex40"; "macwriteii"; "mads+xml"; + "marc"; "marcxml+xml"; "mathematica"; + "mathml+xml"; "mathml-content+xml"; + "mathml-presentation+xml"; + "mbms-associated-procedure-description+xml"; + "mbms-deregister+xml"; "mbms-envelope+xml"; + "mbms-msk+xml"; "mbms-msk-response+xml"; + "mbms-protection-description+xml"; + "mbms-reception-report+xml"; + "mbms-register+xml"; + "mbms-register-response+xml"; + "mbms-schedule+xml"; + "mbms-user-service-description+xml"; "mbox"; + "media-policy-dataset+xml"; + "media_control+xml"; "mediaservercontrol+xml"; + "merge-patch+json"; "metalink4+xml"; + "mets+xml"; "mikey"; "mods+xml"; "moss-keys"; + "moss-signature"; "mosskey-data"; + "mosskey-request"; "mp21"; "mp4"; + "mpeg4-generic"; "mpeg4-iod"; "mpeg4-iod-xmt"; + "mrb-consumer+xml"; "mrb-publish+xml"; + "msc-ivr+xml"; "msc-mixer+xml"; "msword"; + "mxf"; "nasdata"; "news-checkgroups"; + "news-groupinfo"; "news-transmission"; + "nlsml+xml"; "nss"; "ocsp-request"; + "ocsp-response"; "octet-stream"; "oda"; + "oebps-package+xml"; "ogg"; "oxps"; + "p2p-overlay+xml"; "parityfec"; + "patch-ops-error+xml"; "pdf"; "pgp-encrypted"; + "pgp-keys"; "pgp-signature"; "pidf+xml"; + "pidf-diff+xml"; "pkcs10"; "pkcs12"; + "pkcs7-mime"; "pkcs7-signature"; "pkcs8"; + "pkix-attr-cert"; "pkix-cert"; "pkix-crl"; + "pkix-pkipath"; "pkixcmp"; "pls+xml"; + "poc-settings+xml"; "postscript"; + "ppsp-tracker+json"; "problem+json"; + "problem+xml"; "provenance+xml"; + "prs.alvestrand.titrax-sheet"; "prs.cww"; + "prs.hpub+zip"; "prs.nprend"; "prs.plucker"; + "prs.rdf-xml-crypt"; "prs.xsf+xml"; "pskc+xml"; + "qsig"; "raptorfec"; "rdap+json"; "rdf+xml"; + "reginfo+xml"; "relax-ng-compact-syntax"; + "remote-printing"; "reputon+json"; + "resource-lists+xml"; + "resource-lists-diff+xml"; "rfc+xml"; "riscos"; + "rlmi+xml"; "rls-services+xml"; + "rpki-ghostbusters"; "rpki-manifest"; + "rpki-roa"; "rpki-updown"; "rtf"; + "rtploopback"; "rtx"; "samlassertion+xml"; + "samlmetadata+xml"; "sbml+xml"; "scaip+xml"; + "scim+json"; "scvp-cv-request"; + "scvp-cv-response"; "scvp-vp-request"; + "scvp-vp-response"; "sdp"; "sep+xml"; + "sep-exi"; "session-info"; "set-payment"; + "set-payment-initiation"; "set-registration"; + "set-registration-initiation"; "sgml"; + "sgml-open-catalog"; "shf+xml"; "sieve"; + "simple-filter+xml"; "simple-message-summary"; + "simpleSymbolContainer"; "slate"; "smil"; + "smpte336m"; "soap+fastinfoset"; "soap+xml"; + "sparql-query"; "sparql-results+xml"; + "spirits-event+xml"; "sql"; "srgs"; "srgs+xml"; + "sru+xml"; "ssml+xml"; "tamp-apex-update"; + "tamp-apex-update-confirm"; + "tamp-community-update"; + "tamp-community-update-confirm"; "tamp-error"; + "tamp-sequence-adjust"; + "tamp-sequence-adjust-confirm"; + "tamp-status-query"; "tamp-status-response"; + "tamp-update"; "tamp-update-confirm"; + "tei+xml"; "thraud+xml"; "timestamp-query"; + "timestamp-reply"; "timestamped-data"; + "ttml+xml"; "tve-trigger"; "ulpfec"; + "urc-grpsheet+xml"; "urc-ressheet+xml"; + "urc-targetdesc+xml"; "urc-uisocketdesc+xml"; + "vcard+json"; "vcard+xml"; "vemmi"; + "vnd.3M.Post-it-Notes"; "vnd.3gpp-prose+xml"; + "vnd.3gpp-prose-pc3ch+xml"; + "vnd.3gpp.SRVCC-info+xml"; + "vnd.3gpp.access-transfer-events+xml"; + "vnd.3gpp.bsf+xml"; "vnd.3gpp.mid-call+xml"; + "vnd.3gpp.pic-bw-large"; + "vnd.3gpp.pic-bw-small"; "vnd.3gpp.pic-bw-var"; + "vnd.3gpp.sms"; "vnd.3gpp.sms+xml"; + "vnd.3gpp.srvcc-ext+xml"; + "vnd.3gpp.state-and-event-info+xml"; + "vnd.3gpp.ussd+xml"; "vnd.3gpp2.bcmcsinfo+xml"; + "vnd.3gpp2.sms"; "vnd.3gpp2.tcap"; + "vnd.3lightssoftware.imagescal"; + "vnd.FloGraphIt"; + "vnd.HandHeld-Entertainment+xml"; "vnd.Kinar"; + "vnd.MFER"; "vnd.Mobius.DAF"; "vnd.Mobius.DIS"; + "vnd.Mobius.MBK"; "vnd.Mobius.MQY"; + "vnd.Mobius.MSL"; "vnd.Mobius.PLC"; + "vnd.Mobius.TXF"; "vnd.Quark.QuarkXPress"; + "vnd.RenLearn.rlprint"; + "vnd.SimTech-MindMapper"; + "vnd.accpac.simply.aso"; + "vnd.accpac.simply.imp"; "vnd.acucobol"; + "vnd.acucorp"; "vnd.adobe.flash.movie"; + "vnd.adobe.formscentral.fcdt"; "vnd.adobe.fxp"; + "vnd.adobe.partial-upload"; + "vnd.adobe.xdp+xml"; "vnd.adobe.xfdf"; + "vnd.aether.imp"; "vnd.ah-barcode"; + "vnd.ahead.space"; "vnd.airzip.filesecure.azf"; + "vnd.airzip.filesecure.azs"; + "vnd.americandynamics.acc"; "vnd.amiga.ami"; + "vnd.amundsen.maze+xml"; "vnd.anki"; + "vnd.anser-web-certificate-issue-initiation"; + "vnd.antix.game-component"; + "vnd.apache.thrift.binary"; + "vnd.apache.thrift.compact"; + "vnd.apache.thrift.json"; "vnd.api+json"; + "vnd.apple.installer+xml"; "vnd.apple.mpegurl"; + "vnd.arastra.swi"; "vnd.aristanetworks.swi"; + "vnd.artsquare"; "vnd.astraea-software.iota"; + "vnd.audiograph"; "vnd.autopackage"; + "vnd.avistar+xml"; "vnd.balsamiq.bmml+xml"; + "vnd.balsamiq.bmpr"; "vnd.bekitzur-stech+json"; + "vnd.biopax.rdf+xml"; "vnd.blueice.multipass"; + "vnd.bluetooth.ep.oob"; "vnd.bluetooth.le.oob"; + "vnd.bmi"; "vnd.businessobjects"; + "vnd.cab-jscript"; "vnd.canon-cpdl"; + "vnd.canon-lips"; + "vnd.cendio.thinlinc.clientconf"; + "vnd.century-systems.tcp_stream"; + "vnd.chemdraw+xml"; "vnd.chipnuts.karaoke-mmd"; + "vnd.cinderella"; "vnd.cirpack.isdn-ext"; + "vnd.citationstyles.style+xml"; "vnd.claymore"; + "vnd.cloanto.rp9"; "vnd.clonk.c4group"; + "vnd.cluetrust.cartomobile-config"; + "vnd.cluetrust.cartomobile-config-pkg"; + "vnd.coffeescript"; "vnd.collection+json"; + "vnd.collection.doc+json"; + "vnd.collection.next+json"; + "vnd.commerce-battelle"; "vnd.commonspace"; + "vnd.contact.cmsg"; "vnd.coreos.ignition+json"; + "vnd.cosmocaller"; "vnd.crick.clicker"; + "vnd.crick.clicker.keyboard"; + "vnd.crick.clicker.palette"; + "vnd.crick.clicker.template"; + "vnd.crick.clicker.wordbank"; + "vnd.criticaltools.wbs+xml"; "vnd.ctc-posml"; + "vnd.ctct.ws+xml"; "vnd.cups-pdf"; + "vnd.cups-postscript"; "vnd.cups-ppd"; + "vnd.cups-raster"; "vnd.cups-raw"; "vnd.curl"; + "vnd.cyan.dean.root+xml"; "vnd.cybank"; + "vnd.dart"; "vnd.data-vision.rdz"; + "vnd.debian.binary-package"; "vnd.dece.data"; + "vnd.dece.ttml+xml"; "vnd.dece.unspecified"; + "vnd.dece.zip"; "vnd.denovo.fcselayout-link"; + "vnd.desmume.movie"; + "vnd.dir-bi.plate-dl-nosuffix"; + "vnd.dm.delegation+xml"; "vnd.dna"; + "vnd.document+json"; "vnd.dolby.mobile.1"; + "vnd.dolby.mobile.2"; + "vnd.doremir.scorecloud-binary-document"; + "vnd.dpgraph"; "vnd.dreamfactory"; + "vnd.drive+json"; "vnd.dtg.local"; + "vnd.dtg.local.flash"; "vnd.dtg.local.html"; + "vnd.dvb.ait"; "vnd.dvb.dvbj"; + "vnd.dvb.esgcontainer"; + "vnd.dvb.ipdcdftnotifaccess"; + "vnd.dvb.ipdcesgaccess"; + "vnd.dvb.ipdcesgaccess2"; "vnd.dvb.ipdcesgpdd"; + "vnd.dvb.ipdcroaming"; + "vnd.dvb.iptv.alfec-base"; + "vnd.dvb.iptv.alfec-enhancement"; + "vnd.dvb.notif-aggregate-root+xml"; + "vnd.dvb.notif-container+xml"; + "vnd.dvb.notif-generic+xml"; + "vnd.dvb.notif-ia-msglist+xml"; + "vnd.dvb.notif-ia-registration-request+xml"; + "vnd.dvb.notif-ia-registration-response+xml"; + "vnd.dvb.notif-init+xml"; "vnd.dvb.pfr"; + "vnd.dvb.service"; "vnd.dxr"; "vnd.dynageo"; + "vnd.dzr"; "vnd.easykaraoke.cdgdownload"; + "vnd.ecdis-update"; "vnd.ecowin.chart"; + "vnd.ecowin.filerequest"; + "vnd.ecowin.fileupdate"; "vnd.ecowin.series"; + "vnd.ecowin.seriesrequest"; + "vnd.ecowin.seriesupdate"; + "vnd.emclient.accessrequest+xml"; + "vnd.enliven"; "vnd.enphase.envoy"; + "vnd.eprints.data+xml"; "vnd.epson.esf"; + "vnd.epson.msf"; "vnd.epson.quickanime"; + "vnd.epson.salt"; "vnd.epson.ssf"; + "vnd.ericsson.quickcall"; "vnd.eszigno3+xml"; + "vnd.etsi.aoc+xml"; "vnd.etsi.asic-e+zip"; + "vnd.etsi.asic-s+zip"; "vnd.etsi.cug+xml"; + "vnd.etsi.iptvcommand+xml"; + "vnd.etsi.iptvdiscovery+xml"; + "vnd.etsi.iptvprofile+xml"; + "vnd.etsi.iptvsad-bc+xml"; + "vnd.etsi.iptvsad-cod+xml"; + "vnd.etsi.iptvsad-npvr+xml"; + "vnd.etsi.iptvservice+xml"; + "vnd.etsi.iptvsync+xml"; + "vnd.etsi.iptvueprofile+xml"; + "vnd.etsi.mcid+xml"; "vnd.etsi.mheg5"; + "vnd.etsi.overload-control-policy-dataset+xml"; + "vnd.etsi.pstn+xml"; "vnd.etsi.sci+xml"; + "vnd.etsi.simservs+xml"; + "vnd.etsi.timestamp-token"; "vnd.etsi.tsl+xml"; + "vnd.etsi.tsl.der"; "vnd.eudora.data"; + "vnd.ezpix-album"; "vnd.ezpix-package"; + "vnd.f-secure.mobile"; + "vnd.fastcopy-disk-image"; "vnd.fdf"; + "vnd.fdsn.mseed"; "vnd.fdsn.seed"; "vnd.ffsns"; + "vnd.filmit.zfc"; "vnd.fints"; + "vnd.firemonkeys.cloudcell"; + "vnd.fluxtime.clip"; "vnd.font-fontforge-sfd"; + "vnd.framemaker"; "vnd.frogans.fnc"; + "vnd.frogans.ltf"; "vnd.fsc.weblaunch"; + "vnd.fujitsu.oasys"; "vnd.fujitsu.oasys2"; + "vnd.fujitsu.oasys3"; "vnd.fujitsu.oasysgp"; + "vnd.fujitsu.oasysprs"; "vnd.fujixerox.ART-EX"; + "vnd.fujixerox.ART4"; "vnd.fujixerox.HBPL"; + "vnd.fujixerox.ddd"; "vnd.fujixerox.docuworks"; + "vnd.fujixerox.docuworks.binder"; + "vnd.fujixerox.docuworks.container"; + "vnd.fut-misnet"; "vnd.fuzzysheet"; + "vnd.genomatix.tuxedo"; "vnd.geo+json"; + "vnd.geocube+xml"; "vnd.geogebra.file"; + "vnd.geogebra.tool"; "vnd.geometry-explorer"; + "vnd.geonext"; "vnd.geoplan"; "vnd.geospace"; + "vnd.gerber"; + "vnd.globalplatform.card-content-mgt"; + "vnd.globalplatform.card-content-mgt-response"; + "vnd.gmx"; "vnd.google-earth.kml+xml"; + "vnd.google-earth.kmz"; + "vnd.gov.sk.e-form+xml"; + "vnd.gov.sk.e-form+zip"; + "vnd.gov.sk.xmldatacontainer+xml"; + "vnd.grafeq"; "vnd.gridmp"; + "vnd.groove-account"; "vnd.groove-help"; + "vnd.groove-identity-message"; + "vnd.groove-injector"; + "vnd.groove-tool-message"; + "vnd.groove-tool-template"; "vnd.groove-vcard"; + "vnd.hal+json"; "vnd.hal+xml"; "vnd.hbci"; + "vnd.hcl-bireports"; "vnd.hdt"; + "vnd.heroku+json"; "vnd.hhe.lesson-player"; + "vnd.hp-HPGL"; "vnd.hp-PCL"; "vnd.hp-PCLXL"; + "vnd.hp-hpid"; "vnd.hp-hps"; "vnd.hp-jlyt"; + "vnd.httphone"; "vnd.hydrostatix.sof-data"; + "vnd.hyperdrive+json"; "vnd.hzn-3d-crossword"; + "vnd.ibm.MiniPay"; "vnd.ibm.afplinedata"; + "vnd.ibm.electronic-media"; "vnd.ibm.modcap"; + "vnd.ibm.rights-management"; + "vnd.ibm.secure-container"; "vnd.iccprofile"; + "vnd.ieee.1905"; "vnd.igloader"; + "vnd.immervision-ivp"; "vnd.immervision-ivu"; + "vnd.ims.imsccv1p1"; "vnd.ims.imsccv1p2"; + "vnd.ims.imsccv1p3"; + "vnd.ims.lis.v2.result+json"; + "vnd.ims.lti.v2.toolconsumerprofile+json"; + "vnd.ims.lti.v2.toolproxy+json"; + "vnd.ims.lti.v2.toolproxy.id+json"; + "vnd.ims.lti.v2.toolsettings+json"; + "vnd.ims.lti.v2.toolsettings.simple+json"; + "vnd.informedcontrol.rms+xml"; + "vnd.informix-visionary"; + "vnd.infotech.project"; + "vnd.infotech.project+xml"; + "vnd.innopath.wamp.notification"; + "vnd.insors.igm"; "vnd.intercon.formnet"; + "vnd.intergeo"; "vnd.intertrust.digibox"; + "vnd.intertrust.nncp"; "vnd.intu.qbo"; + "vnd.intu.qfx"; "vnd.iptc.g2.catalogitem+xml"; + "vnd.iptc.g2.conceptitem+xml"; + "vnd.iptc.g2.knowledgeitem+xml"; + "vnd.iptc.g2.newsitem+xml"; + "vnd.iptc.g2.newsmessage+xml"; + "vnd.iptc.g2.packageitem+xml"; + "vnd.iptc.g2.planningitem+xml"; + "vnd.ipunplugged.rcprofile"; + "vnd.irepository.package+xml"; "vnd.is-xpr"; + "vnd.isac.fcs"; "vnd.jam"; + "vnd.japannet-directory-service"; + "vnd.japannet-jpnstore-wakeup"; + "vnd.japannet-payment-wakeup"; + "vnd.japannet-registration"; + "vnd.japannet-registration-wakeup"; + "vnd.japannet-setstore-wakeup"; + "vnd.japannet-verification"; + "vnd.japannet-verification-wakeup"; + "vnd.jcp.javame.midlet-rms"; "vnd.jisp"; + "vnd.joost.joda-archive"; "vnd.jsk.isdn-ngn"; + "vnd.kahootz"; "vnd.kde.karbon"; + "vnd.kde.kchart"; "vnd.kde.kformula"; + "vnd.kde.kivio"; "vnd.kde.kontour"; + "vnd.kde.kpresenter"; "vnd.kde.kspread"; + "vnd.kde.kword"; "vnd.kenameaapp"; + "vnd.kidspiration"; "vnd.koan"; + "vnd.kodak-descriptor"; "vnd.las.las+xml"; + "vnd.liberty-request+xml"; + "vnd.llamagraphics.life-balance.desktop"; + "vnd.llamagraphics.life-balance.exchange+xml"; + "vnd.lotus-1-2-3"; "vnd.lotus-approach"; + "vnd.lotus-freelance"; "vnd.lotus-notes"; + "vnd.lotus-organizer"; "vnd.lotus-screencam"; + "vnd.lotus-wordpro"; "vnd.macports.portpkg"; + "vnd.mapbox-vector-tile"; + "vnd.marlin.drm.actiontoken+xml"; + "vnd.marlin.drm.conftoken+xml"; + "vnd.marlin.drm.license+xml"; + "vnd.marlin.drm.mdcf"; "vnd.mason+json"; + "vnd.maxmind.maxmind-db"; "vnd.mcd"; + "vnd.medcalcdata"; "vnd.mediastation.cdkey"; + "vnd.meridian-slingshot"; "vnd.mfmp"; + "vnd.micro+json"; "vnd.micrografx.flo"; + "vnd.micrografx.igx"; + "vnd.microsoft.portable-executable"; + "vnd.miele+json"; "vnd.mif"; + "vnd.minisoft-hp3000-save"; + "vnd.mitsubishi.misty-guard.trustweb"; + "vnd.mophun.application"; + "vnd.mophun.certificate"; + "vnd.motorola.flexsuite"; + "vnd.motorola.flexsuite.adsi"; + "vnd.motorola.flexsuite.fis"; + "vnd.motorola.flexsuite.gotap"; + "vnd.motorola.flexsuite.kmr"; + "vnd.motorola.flexsuite.ttc"; + "vnd.motorola.flexsuite.wem"; + "vnd.motorola.iprm"; "vnd.mozilla.xul+xml"; + "vnd.ms-3mfdocument"; + "vnd.ms-PrintDeviceCapabilities+xml"; + "vnd.ms-PrintSchemaTicket+xml"; + "vnd.ms-artgalry"; "vnd.ms-asf"; + "vnd.ms-cab-compressed"; "vnd.ms-excel"; + "vnd.ms-excel.addin.macroEnabled.12"; + "vnd.ms-excel.sheet.binary.macroEnabled.12"; + "vnd.ms-excel.sheet.macroEnabled.12"; + "vnd.ms-excel.template.macroEnabled.12"; + "vnd.ms-fontobject"; "vnd.ms-htmlhelp"; + "vnd.ms-ims"; "vnd.ms-lrm"; + "vnd.ms-office.activeX+xml"; + "vnd.ms-officetheme"; + "vnd.ms-playready.initiator+xml"; + "vnd.ms-powerpoint"; + "vnd.ms-powerpoint.addin.macroEnabled.12"; + "vnd.ms-powerpoint.presentation.macroEnabled.12"; + "vnd.ms-powerpoint.slide.macroEnabled.12"; + "vnd.ms-powerpoint.slideshow.macroEnabled.12"; + "vnd.ms-powerpoint.template.macroEnabled.12"; + "vnd.ms-project"; "vnd.ms-tnef"; + "vnd.ms-windows.devicepairing"; + "vnd.ms-windows.nwprinting.oob"; + "vnd.ms-windows.printerpairing"; + "vnd.ms-windows.wsd.oob"; + "vnd.ms-wmdrm.lic-chlg-req"; + "vnd.ms-wmdrm.lic-resp"; + "vnd.ms-wmdrm.meter-chlg-req"; + "vnd.ms-wmdrm.meter-resp"; + "vnd.ms-word.document.macroEnabled.12"; + "vnd.ms-word.template.macroEnabled.12"; + "vnd.ms-works"; "vnd.ms-wpl"; + "vnd.ms-xpsdocument"; "vnd.msa-disk-image"; + "vnd.mseq"; "vnd.msign"; "vnd.multiad.creator"; + "vnd.multiad.creator.cif"; "vnd.music-niff"; + "vnd.musician"; "vnd.muvee.style"; "vnd.mynfc"; + "vnd.ncd.control"; "vnd.ncd.reference"; + "vnd.nervana"; "vnd.netfpx"; + "vnd.neurolanguage.nlu"; + "vnd.nintendo.nitro.rom"; + "vnd.nintendo.snes.rom"; "vnd.nitf"; + "vnd.noblenet-directory"; + "vnd.noblenet-sealer"; "vnd.noblenet-web"; + "vnd.nokia.catalogs"; "vnd.nokia.conml+wbxml"; + "vnd.nokia.conml+xml"; + "vnd.nokia.iSDS-radio-presets"; + "vnd.nokia.iptv.config+xml"; + "vnd.nokia.landmark+wbxml"; + "vnd.nokia.landmark+xml"; + "vnd.nokia.landmarkcollection+xml"; + "vnd.nokia.n-gage.ac+xml"; + "vnd.nokia.n-gage.data"; + "vnd.nokia.n-gage.symbian.install"; + "vnd.nokia.ncd"; "vnd.nokia.pcd+wbxml"; + "vnd.nokia.pcd+xml"; "vnd.nokia.radio-preset"; + "vnd.nokia.radio-presets"; "vnd.novadigm.EDM"; + "vnd.novadigm.EDX"; "vnd.novadigm.EXT"; + "vnd.ntt-local.content-share"; + "vnd.ntt-local.file-transfer"; + "vnd.ntt-local.ogw_remote-access"; + "vnd.ntt-local.sip-ta_remote"; + "vnd.ntt-local.sip-ta_tcp_stream"; + "vnd.oasis.opendocument.chart"; + "vnd.oasis.opendocument.chart-template"; + "vnd.oasis.opendocument.database"; + "vnd.oasis.opendocument.formula"; + "vnd.oasis.opendocument.formula-template"; + "vnd.oasis.opendocument.graphics"; + "vnd.oasis.opendocument.graphics-template"; + "vnd.oasis.opendocument.image"; + "vnd.oasis.opendocument.image-template"; + "vnd.oasis.opendocument.presentation"; + "vnd.oasis.opendocument.presentation-template"; + "vnd.oasis.opendocument.spreadsheet"; + "vnd.oasis.opendocument.spreadsheet-template"; + "vnd.oasis.opendocument.text"; + "vnd.oasis.opendocument.text-master"; + "vnd.oasis.opendocument.text-template"; + "vnd.oasis.opendocument.text-web"; "vnd.obn"; + "vnd.oftn.l10n+json"; + "vnd.oipf.contentaccessdownload+xml"; + "vnd.oipf.contentaccessstreaming+xml"; + "vnd.oipf.cspg-hexbinary"; + "vnd.oipf.dae.svg+xml"; + "vnd.oipf.dae.xhtml+xml"; + "vnd.oipf.mippvcontrolmessage+xml"; + "vnd.oipf.pae.gem"; "vnd.oipf.spdiscovery+xml"; + "vnd.oipf.spdlist+xml"; + "vnd.oipf.ueprofile+xml"; + "vnd.oipf.userprofile+xml"; "vnd.olpc-sugar"; + "vnd.oma-scws-config"; + "vnd.oma-scws-http-request"; + "vnd.oma-scws-http-response"; + "vnd.oma.bcast.associated-procedure-parameter+xml"; + "vnd.oma.bcast.drm-trigger+xml"; + "vnd.oma.bcast.imd+xml"; "vnd.oma.bcast.ltkm"; + "vnd.oma.bcast.notification+xml"; + "vnd.oma.bcast.provisioningtrigger"; + "vnd.oma.bcast.sgboot"; + "vnd.oma.bcast.sgdd+xml"; "vnd.oma.bcast.sgdu"; + "vnd.oma.bcast.simple-symbol-container"; + "vnd.oma.bcast.smartcard-trigger+xml"; + "vnd.oma.bcast.sprov+xml"; + "vnd.oma.bcast.stkm"; + "vnd.oma.cab-address-book+xml"; + "vnd.oma.cab-feature-handler+xml"; + "vnd.oma.cab-pcc+xml"; + "vnd.oma.cab-subs-invite+xml"; + "vnd.oma.cab-user-prefs+xml"; "vnd.oma.dcd"; + "vnd.oma.dcdc"; "vnd.oma.dd2+xml"; + "vnd.oma.drm.risd+xml"; + "vnd.oma.group-usage-list+xml"; + "vnd.oma.lwm2m+json"; "vnd.oma.pal+xml"; + "vnd.oma.poc.detailed-progress-report+xml"; + "vnd.oma.poc.final-report+xml"; + "vnd.oma.poc.groups+xml"; + "vnd.oma.poc.invocation-descriptor+xml"; + "vnd.oma.poc.optimized-progress-report+xml"; + "vnd.oma.push"; "vnd.oma.scidm.messages+xml"; + "vnd.oma.xcap-directory+xml"; + "vnd.omads-email+xml"; "vnd.omads-file+xml"; + "vnd.omads-folder+xml"; "vnd.omaloc-supl-init"; + "vnd.onepager"; "vnd.openblox.game+xml"; + "vnd.openblox.game-binary"; "vnd.openeye.oeb"; + "vnd.openxmlformats-officedocument.custom-properties+xml"; + "vnd.openxmlformats-officedocument.customXmlProperties+xml"; + "vnd.openxmlformats-officedocument.drawing+xml"; + "vnd.openxmlformats-officedocument.drawingml.chart+xml"; + "vnd.openxmlformats-officedocument.drawingml.chartshapes+xml"; + "vnd.openxmlformats-officedocument.drawingml.diagramColors+xml"; + "vnd.openxmlformats-officedocument.drawingml.diagramData+xml"; + "vnd.openxmlformats-officedocument.drawingml.diagramLayout+xml"; + "vnd.openxmlformats-officedocument.drawingml.diagramStyle+xml"; + "vnd.openxmlformats-officedocument.extended-properties+xml"; + "vnd.openxmlformats-officedocument.presentationml.commentAuthors+xml"; + "vnd.openxmlformats-officedocument.presentationml.comments+xml"; + "vnd.openxmlformats-officedocument.presentationml.handoutMaster+xml"; + "vnd.openxmlformats-officedocument.presentationml.notesMaster+xml"; + "vnd.openxmlformats-officedocument.presentationml.notesSlide+xml"; + "vnd.openxmlformats-officedocument.presentationml.presProps+xml"; + "vnd.openxmlformats-officedocument.presentationml.presentation"; + "vnd.openxmlformats-officedocument.presentationml.presentation.main+xml"; + "vnd.openxmlformats-officedocument.presentationml.slide"; + "vnd.openxmlformats-officedocument.presentationml.slide+xml"; + "vnd.openxmlformats-officedocument.presentationml.slideLayout+xml"; + "vnd.openxmlformats-officedocument.presentationml.slideMaster+xml"; + "vnd.openxmlformats-officedocument.presentationml.slideUpdateInfo+xml"; + "vnd.openxmlformats-officedocument.presentationml.slideshow"; + "vnd.openxmlformats-officedocument.presentationml.slideshow.main+xml"; + "vnd.openxmlformats-officedocument.presentationml.tableStyles+xml"; + "vnd.openxmlformats-officedocument.presentationml.tags+xml"; + "vnd.openxmlformats-officedocument.presentationml.template"; + "vnd.openxmlformats-officedocument.presentationml.template.main+xml"; + "vnd.openxmlformats-officedocument.presentationml.viewProps+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.calcChain+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.comments+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.connections+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.dialogsheet+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.externalLink+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheDefinition+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.pivotCacheRecords+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.pivotTable+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.queryTable+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.revisionHeaders+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.revisionLog+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + "vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.sheetMetadata+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.table+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.tableSingleCells+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.template"; + "vnd.openxmlformats-officedocument.spreadsheetml.template.main+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.userNames+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.volatileDependencies+xml"; + "vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"; + "vnd.openxmlformats-officedocument.theme+xml"; + "vnd.openxmlformats-officedocument.themeOverride+xml"; + "vnd.openxmlformats-officedocument.vmlDrawing"; + "vnd.openxmlformats-officedocument.wordprocessingml.comments+xml"; + "vnd.openxmlformats-officedocument.wordprocessingml.document"; + "vnd.openxmlformats-officedocument.wordprocessingml.document.glossary+xml"; + "vnd.openxmlformats-officedocument.wordprocessingml.document.main+xml"; + "vnd.openxmlformats-officedocument.wordprocessingml.endnotes+xml"; + "vnd.openxmlformats-officedocument.wordprocessingml.fontTable+xml"; + "vnd.openxmlformats-officedocument.wordprocessingml.footer+xml"; + "vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml"; + "vnd.openxmlformats-officedocument.wordprocessingml.numbering+xml"; + "vnd.openxmlformats-officedocument.wordprocessingml.settings+xml"; + "vnd.openxmlformats-officedocument.wordprocessingml.styles+xml"; + "vnd.openxmlformats-officedocument.wordprocessingml.template"; + "vnd.openxmlformats-officedocument.wordprocessingml.template.main+xml"; + "vnd.openxmlformats-officedocument.wordprocessingml.webSettings+xml"; + "vnd.openxmlformats-package.core-properties+xml"; + "vnd.openxmlformats-package.digital-signature-xmlsignature+xml"; + "vnd.openxmlformats-package.relationships+xml"; + "vnd.oracle.resource+json"; + "vnd.orange.indata"; "vnd.osa.netdeploy"; + "vnd.osgeo.mapguide.package"; + "vnd.osgi.bundle"; "vnd.osgi.dp"; + "vnd.osgi.subsystem"; "vnd.otps.ct-kip+xml"; + "vnd.oxli.countgraph"; "vnd.pagerduty+json"; + "vnd.palm"; "vnd.panoply"; "vnd.paos.xml"; + "vnd.pawaafile"; "vnd.pcos"; "vnd.pg.format"; + "vnd.pg.osasli"; + "vnd.piaccess.application-licence"; + "vnd.picsel"; "vnd.pmi.widget"; + "vnd.poc.group-advertisement+xml"; + "vnd.pocketlearn"; "vnd.powerbuilder6"; + "vnd.powerbuilder6-s"; "vnd.powerbuilder7"; + "vnd.powerbuilder7-s"; "vnd.powerbuilder75"; + "vnd.powerbuilder75-s"; "vnd.preminet"; + "vnd.previewsystems.box"; + "vnd.proteus.magazine"; + "vnd.publishare-delta-tree"; "vnd.pvi.ptid1"; + "vnd.pwg-multiplexed"; + "vnd.pwg-xhtml-print+xml"; + "vnd.qualcomm.brew-app-res"; + "vnd.quarantainenet"; + "vnd.quobject-quoxdocument"; + "vnd.radisys.moml+xml"; "vnd.radisys.msml+xml"; + "vnd.radisys.msml-audit+xml"; + "vnd.radisys.msml-audit-conf+xml"; + "vnd.radisys.msml-audit-conn+xml"; + "vnd.radisys.msml-audit-dialog+xml"; + "vnd.radisys.msml-audit-stream+xml"; + "vnd.radisys.msml-conf+xml"; + "vnd.radisys.msml-dialog+xml"; + "vnd.radisys.msml-dialog-base+xml"; + "vnd.radisys.msml-dialog-fax-detect+xml"; + "vnd.radisys.msml-dialog-fax-sendrecv+xml"; + "vnd.radisys.msml-dialog-group+xml"; + "vnd.radisys.msml-dialog-speech+xml"; + "vnd.radisys.msml-dialog-transform+xml"; + "vnd.rainstor.data"; "vnd.rapid"; + "vnd.realvnc.bed"; "vnd.recordare.musicxml"; + "vnd.recordare.musicxml+xml"; + "vnd.rig.cryptonote"; "vnd.route66.link66+xml"; + "vnd.rs-274x"; "vnd.ruckus.download"; + "vnd.s3sms"; "vnd.sailingtracker.track"; + "vnd.sbm.cid"; "vnd.sbm.mid2"; "vnd.scribus"; + "vnd.sealed.3df"; "vnd.sealed.csf"; + "vnd.sealed.doc"; "vnd.sealed.eml"; + "vnd.sealed.mht"; "vnd.sealed.net"; + "vnd.sealed.ppt"; "vnd.sealed.tiff"; + "vnd.sealed.xls"; + "vnd.sealedmedia.softseal.html"; + "vnd.sealedmedia.softseal.pdf"; "vnd.seemail"; + "vnd.sema"; "vnd.semd"; "vnd.semf"; + "vnd.shana.informed.formdata"; + "vnd.shana.informed.formtemplate"; + "vnd.shana.informed.interchange"; + "vnd.shana.informed.package"; "vnd.siren+json"; + "vnd.smaf"; "vnd.smart.notebook"; + "vnd.smart.teacher"; + "vnd.software602.filler.form+xml"; + "vnd.software602.filler.form-xml-zip"; + "vnd.solent.sdkm+xml"; "vnd.spotfire.dxp"; + "vnd.spotfire.sfs"; "vnd.sss-cod"; + "vnd.sss-dtf"; "vnd.sss-ntf"; + "vnd.stepmania.package"; + "vnd.stepmania.stepchart"; "vnd.street-stream"; + "vnd.sun.wadl+xml"; "vnd.sus-calendar"; + "vnd.svd"; "vnd.swiftview-ics"; + "vnd.syncml+xml"; "vnd.syncml.dm+wbxml"; + "vnd.syncml.dm+xml"; + "vnd.syncml.dm.notification"; + "vnd.syncml.dmddf+wbxml"; + "vnd.syncml.dmddf+xml"; + "vnd.syncml.dmtnds+wbxml"; + "vnd.syncml.dmtnds+xml"; + "vnd.syncml.ds.notification"; + "vnd.tao.intent-module-archive"; + "vnd.tcpdump.pcap"; + "vnd.tmd.mediaflex.api+xml"; "vnd.tml"; + "vnd.tmobile-livetv"; "vnd.trid.tpt"; + "vnd.triscape.mxs"; "vnd.trueapp"; + "vnd.truedoc"; "vnd.ubisoft.webplayer"; + "vnd.ufdl"; "vnd.uiq.theme"; "vnd.umajin"; + "vnd.unity"; "vnd.uoml+xml"; + "vnd.uplanet.alert"; "vnd.uplanet.alert-wbxml"; + "vnd.uplanet.bearer-choice"; + "vnd.uplanet.bearer-choice-wbxml"; + "vnd.uplanet.cacheop"; + "vnd.uplanet.cacheop-wbxml"; + "vnd.uplanet.channel"; + "vnd.uplanet.channel-wbxml"; + "vnd.uplanet.list"; "vnd.uplanet.list-wbxml"; + "vnd.uplanet.listcmd"; + "vnd.uplanet.listcmd-wbxml"; + "vnd.uplanet.signal"; "vnd.uri-map"; + "vnd.valve.source.material"; "vnd.vcx"; + "vnd.vd-study"; "vnd.vectorworks"; + "vnd.vel+json"; "vnd.verimatrix.vcas"; + "vnd.vidsoft.vidconference"; "vnd.visio"; + "vnd.visionary"; "vnd.vividence.scriptfile"; + "vnd.vsf"; "vnd.wap.sic"; "vnd.wap.slc"; + "vnd.wap.wbxml"; "vnd.wap.wmlc"; + "vnd.wap.wmlscriptc"; "vnd.webturbo"; + "vnd.wfa.p2p"; "vnd.wfa.wsc"; + "vnd.windows.devicepairing"; "vnd.wmc"; + "vnd.wmf.bootstrap"; "vnd.wolfram.mathematica"; + "vnd.wolfram.mathematica.package"; + "vnd.wolfram.player"; "vnd.wordperfect"; + "vnd.wqd"; "vnd.wrq-hp3000-labelled"; + "vnd.wt.stf"; "vnd.wv.csp+wbxml"; + "vnd.wv.csp+xml"; "vnd.wv.ssp+xml"; + "vnd.xacml+json"; "vnd.xara"; "vnd.xfdl"; + "vnd.xfdl.webform"; "vnd.xmi+xml"; + "vnd.xmpie.cpkg"; "vnd.xmpie.dpkg"; + "vnd.xmpie.plan"; "vnd.xmpie.ppkg"; + "vnd.xmpie.xlim"; "vnd.yamaha.hv-dic"; + "vnd.yamaha.hv-script"; "vnd.yamaha.hv-voice"; + "vnd.yamaha.openscoreformat"; + "vnd.yamaha.openscoreformat.osfpvg+xml"; + "vnd.yamaha.remote-setup"; + "vnd.yamaha.smaf-audio"; + "vnd.yamaha.smaf-phrase"; + "vnd.yamaha.through-ngn"; + "vnd.yamaha.tunnel-udpencap"; "vnd.yaoweme"; + "vnd.yellowriver-custom-menu"; "vnd.zul"; + "vnd.zzazz.deck+xml"; "voicexml+xml"; + "vq-rtcpxr"; "watcherinfo+xml"; + "whoispp-query"; "whoispp-response"; "widget"; + "wita"; "wordperfect5.1"; "wsdl+xml"; + "wspolicy+xml"; "x-www-form-urlencoded"; + "x400-bp"; "xacml+xml"; "xcap-att+xml"; + "xcap-caps+xml"; "xcap-diff+xml"; + "xcap-el+xml"; "xcap-error+xml"; "xcap-ns+xml"; + "xcon-conference-info+xml"; + "xcon-conference-info-diff+xml"; "xenc+xml"; + "xhtml+xml"; "xml"; "xml-dtd"; + "xml-external-parsed-entity"; "xml-patch+xml"; + "xmpp+xml"; "xop+xml"; "xslt+xml"; "xv+xml"; + "yang"; "yin+xml"; "zip"; "zlib"; + ]) Map.empty)))))))) diff --git a/lib/location.ml b/lib/location.ml index 1644102..0a6c776 100644 --- a/lib/location.ml +++ b/lib/location.ml @@ -1,63 +1,54 @@ type point = int -type zone = { a : point - ; b : point } +type zone = { a : point; b : point } type t = zone option - type 'a with_location = { value : 'a; location : t } type 'a w = 'a with_location = { value : 'a; location : t } let make a b = - if a < 0 || b < 0 then Fmt.invalid_arg "A point must be positive" ; - if a > b then Fmt.invalid_arg "[a] must be lower or equal to [b]" ; - Some { a; b; } + if a < 0 || b < 0 then Fmt.invalid_arg "A point must be positive"; + if a > b then Fmt.invalid_arg "[a] must be lower or equal to [b]"; + Some { a; b } let some zone = Some zone let none = None -let union a b = match a, b with +let union a b = + match (a, b) with | None, None -> None | Some _, None -> a | None, Some _ -> b - | Some { a; b; }, Some { a= x; b= y} -> - let a = (min : int -> int -> int) a x in - let b = (max : int -> int -> int) b y in - Some { a; b; } + | Some { a; b }, Some { a = x; b = y } -> + let a = (min : int -> int -> int) a x in + let b = (max : int -> int -> int) b y in + Some { a; b } let pp ppf = function - | Some { a; b; } -> Fmt.pf ppf "%d:%d" a b + | Some { a; b } -> Fmt.pf ppf "%d:%d" a b | None -> Fmt.string ppf "" -let left = function - | Some { a; _ } -> Some a - | None -> None +let left = function Some { a; _ } -> Some a | None -> None -let left_exn t = match left t with +let left_exn t = + match left t with | Some left -> left | None -> Fmt.invalid_arg "" -let right = function - | Some { b; _ } -> Some b - | None -> None +let right = function Some { b; _ } -> Some b | None -> None -let right_exn t = match right t with +let right_exn t = + match right t with | Some right -> right | None -> Fmt.invalid_arg "" -let length = function - | Some { a; b; } -> Some (b - a) - | None -> None +let length = function Some { a; b } -> Some (b - a) | None -> None -let length_exn t = match length t with +let length_exn t = + match length t with | Some length -> length | None -> Fmt.invalid_arg "" -let without_location : 'a with_location -> 'a = - fun { value; _ } -> value - +let without_location : 'a with_location -> 'a = fun { value; _ } -> value let location { location; _ } = location - -let with_location ~location v = - { value= v; location; } - +let with_location ~location v = { value = v; location } let inj = with_location let prj = without_location diff --git a/lib/location.mli b/lib/location.mli index 8abc931..66aa783 100644 --- a/lib/location.mli +++ b/lib/location.mli @@ -19,7 +19,7 @@ type point = int (** Type of point to a flow. *) -type zone = { a : int; b : int; } +type zone = { a : int; b : int } (** Type of zone to a flow. *) type t @@ -69,7 +69,7 @@ val inj : location:t -> 'a -> 'a with_location (** Alias of {!with_location}. *) val without_location : 'a with_location -> 'a - (** [without_location x] extracts value without location meta-data. *) +(** [without_location x] extracts value without location meta-data. *) val prj : 'a with_location -> 'a (** Alias of {!without_location}. *) diff --git a/lib/mail.ml b/lib/mail.ml index e88eceb..1214896 100644 --- a/lib/mail.ml +++ b/lib/mail.ml @@ -1,4 +1,4 @@ -type 'a elt = { header : Header.t; body : 'a; } +type 'a elt = { header : Header.t; body : 'a } type 'a t = | Leaf of 'a elt @@ -9,32 +9,35 @@ let parser ~write_line end_of_body = let open Angstrom in let check_end_of_body = let expected_len = String.length end_of_body in - Unsafe.peek expected_len - (fun ba ~off ~len -> - let raw = Bigstringaf.substring ba ~off ~len in - String.equal raw end_of_body) in + Unsafe.peek expected_len (fun ba ~off ~len -> + let raw = Bigstringaf.substring ba ~off ~len in + String.equal raw end_of_body) + in fix @@ fun m -> let choose chunk = function | true -> - let chunk = Bytes.sub_string chunk 0 (Bytes.length chunk - 1) in - write_line chunk ; commit + let chunk = Bytes.sub_string chunk 0 (Bytes.length chunk - 1) in + write_line chunk; + commit | false -> - Bytes.set chunk (Bytes.length chunk - 1) end_of_body.[0] ; - write_line (Bytes.unsafe_to_string chunk) ; - advance 1 *> m in + Bytes.set chunk (Bytes.length chunk - 1) end_of_body.[0]; + write_line (Bytes.unsafe_to_string chunk); + advance 1 *> m + in - Unsafe.take_while ((<>) end_of_body.[0]) Bigstringaf.substring + Unsafe.take_while (( <> ) end_of_body.[0]) Bigstringaf.substring >>= fun chunk -> let chunk' = Bytes.create (String.length chunk + 1) in - Bytes.blit_string chunk 0 chunk' 0 (String.length chunk) ; + Bytes.blit_string chunk 0 chunk' 0 (String.length chunk); check_end_of_body >>= choose chunk' let with_buffer ?(end_of_line = "\n") end_of_body = let buf = Buffer.create 0x100 in let write_line x = - Buffer.add_string buf x ; - Buffer.add_string buf end_of_line in + Buffer.add_string buf x; + Buffer.add_string buf end_of_line + in let open Angstrom in parser ~write_line end_of_body >>| fun () -> Buffer.contents buf @@ -45,72 +48,73 @@ let with_emitter ?(end_of_line = "\n") ~emitter end_of_body = let to_end_of_input ~write_data = let open Angstrom in - fix @@ fun m -> peek_char >>= function | None -> commit | Some _ -> - available >>= fun n -> Unsafe.take n - (fun ba ~off ~len -> - let chunk = Bytes.create len in - Bigstringaf.blit_to_bytes ba ~src_off:off chunk ~dst_off:0 ~len ; - write_data (Bytes.unsafe_to_string chunk)) - >>= fun () -> m + available >>= fun n -> + Unsafe.take n (fun ba ~off ~len -> + let chunk = Bytes.create len in + Bigstringaf.blit_to_bytes ba ~src_off:off chunk ~dst_off:0 ~len; + write_data (Bytes.unsafe_to_string chunk)) + >>= fun () -> m let heavy_octet boundary header = let open Angstrom in match boundary with | None -> - let buf = Buffer.create 0x800 in - let write_line line = - Buffer.add_string buf line ; - Buffer.add_string buf "\n" in - let write_data = Buffer.add_string buf in - (match Header.content_encoding header with - | `Quoted_printable -> - Quoted_printable.to_end_of_input ~write_data ~write_line - | `Base64 -> - B64.to_end_of_input ~write_data - | `Bit7 | `Bit8 | `Binary -> - to_end_of_input ~write_data - | `Ietf_token _x | `X_token _x -> assert false) - >>| fun () -> Buffer.contents buf - | Some boundary -> - let end_of_body = Rfc2046.make_delimiter boundary in - match Header.content_encoding header with - | `Quoted_printable -> - Quoted_printable.with_buffer end_of_body - | `Base64 -> - B64.with_buffer end_of_body - | `Bit7 | `Bit8 | `Binary -> - with_buffer end_of_body - | `Ietf_token _x | `X_token _x -> assert false + let buf = Buffer.create 0x800 in + let write_line line = + Buffer.add_string buf line; + Buffer.add_string buf "\n" + in + let write_data = Buffer.add_string buf in + (match Header.content_encoding header with + | `Quoted_printable -> + Quoted_printable.to_end_of_input ~write_data ~write_line + | `Base64 -> B64.to_end_of_input ~write_data + | `Bit7 | `Bit8 | `Binary -> to_end_of_input ~write_data + | `Ietf_token _x | `X_token _x -> assert false) + >>| fun () -> Buffer.contents buf + | Some boundary -> ( + let end_of_body = Rfc2046.make_delimiter boundary in + match Header.content_encoding header with + | `Quoted_printable -> Quoted_printable.with_buffer end_of_body + | `Base64 -> B64.with_buffer end_of_body + | `Bit7 | `Bit8 | `Binary -> with_buffer end_of_body + | `Ietf_token _x | `X_token _x -> assert false) let light_octet ~emitter boundary header = let open Angstrom in match boundary with | None -> - let write_line line = emitter (Some (line ^ "\n")) in - let write_data data = emitter (Some data) in - (match Header.content_encoding header with - | `Quoted_printable -> Quoted_printable.to_end_of_input ~write_line ~write_data - | `Base64 -> B64.to_end_of_input ~write_data - | `Bit7 | `Bit8 | `Binary -> to_end_of_input ~write_data - | `Ietf_token _ | `X_token _ -> assert false) >>= fun () -> - emitter None ; return () - | Some boundary -> - let end_of_body = Rfc2046.make_delimiter boundary in - match Header.content_encoding header with - | `Quoted_printable -> - Quoted_printable.with_emitter ~emitter end_of_body - >>= fun () -> emitter None ; return () - | `Base64 -> - B64.with_emitter ~emitter end_of_body - >>= fun () -> emitter None ; return () - | `Bit7 | `Bit8 | `Binary -> - with_emitter ~emitter end_of_body - >>= fun () -> emitter None ; return () - | `Ietf_token _ | `X_token _ -> assert false + let write_line line = emitter (Some (line ^ "\n")) in + let write_data data = emitter (Some data) in + (match Header.content_encoding header with + | `Quoted_printable -> + Quoted_printable.to_end_of_input ~write_line ~write_data + | `Base64 -> B64.to_end_of_input ~write_data + | `Bit7 | `Bit8 | `Binary -> to_end_of_input ~write_data + | `Ietf_token _ | `X_token _ -> assert false) + >>= fun () -> + emitter None; + return () + | Some boundary -> ( + let end_of_body = Rfc2046.make_delimiter boundary in + match Header.content_encoding header with + | `Quoted_printable -> + Quoted_printable.with_emitter ~emitter end_of_body >>= fun () -> + emitter None; + return () + | `Base64 -> + B64.with_emitter ~emitter end_of_body >>= fun () -> + emitter None; + return () + | `Bit7 | `Bit8 | `Binary -> + with_emitter ~emitter end_of_body >>= fun () -> + emitter None; + return () + | `Ietf_token _ | `X_token _ -> assert false) let boundary header = let content_type = Header.content_type header in @@ -125,76 +129,82 @@ let mail = let rec body parent header = match Content_type.ty (Header.content_type header) with | `Ietf_token _x | `X_token _x -> assert false - | #Content_type.Type.discrete -> heavy_octet parent header >>| fun body -> Leaf { header; body; } + | #Content_type.Type.discrete -> + heavy_octet parent header >>| fun body -> Leaf { header; body } | `Message -> - mail parent >>| fun (header', body') -> Message { header= header'; body= body' } - | `Multipart -> - match boundary header with - | Some boundary -> - Rfc2046.multipart_body ?parent boundary (body (Option.some boundary)) - >>| List.map snd - >>| fun parts -> Multipart { header; body= parts; } - | None -> fail "expected boundary" - + mail parent >>| fun (header', body') -> + Message { header = header'; body = body' } + | `Multipart -> ( + match boundary header with + | Some boundary -> + Rfc2046.multipart_body ?parent boundary + (body (Option.some boundary)) + >>| List.map snd + >>| fun parts -> Multipart { header; body = parts } + | None -> fail "expected boundary") and mail parent = - Header.Decoder.header <* char '\r' <* char '\n' - >>= fun header -> match Content_type.ty (Header.content_type header) with + Header.Decoder.header <* char '\r' <* char '\n' >>= fun header -> + match Content_type.ty (Header.content_type header) with | `Ietf_token _x | `X_token _x -> assert false | #Content_type.Type.discrete -> - heavy_octet parent header >>| fun body -> - header, Leaf { header; body; } + heavy_octet parent header >>| fun body -> (header, Leaf { header; body }) | `Message -> - mail parent >>| fun (header', message') -> - header, Message { header= header'; body= message' } - | `Multipart -> - match boundary header with - | Some boundary -> - Rfc2046.multipart_body ?parent boundary (body (Option.some boundary)) - >>| List.map snd - >>| fun parts -> header, Multipart { header; body= parts; } - | None -> fail "expected boundary" in + mail parent >>| fun (header', message') -> + (header, Message { header = header'; body = message' }) + | `Multipart -> ( + match boundary header with + | Some boundary -> + Rfc2046.multipart_body ?parent boundary + (body (Option.some boundary)) + >>| List.map snd + >>| fun parts -> (header, Multipart { header; body = parts }) + | None -> fail "expected boundary") + in mail None type 'id emitters = Header.t -> (string option -> unit) * 'id -let stream - : emitters:'id emitters -> (Header.t * 'id t) Angstrom.t - = fun ~emitters -> +let stream : emitters:'id emitters -> (Header.t * 'id t) Angstrom.t = + fun ~emitters -> let open Angstrom in let rec body parent header = match Content_type.ty (Header.content_type header) with | `Ietf_token _x | `X_token _x -> assert false | #Content_type.Type.discrete -> - let emitter, id = emitters header in - light_octet ~emitter parent header >>| fun () -> Leaf { header; body= id; } + let emitter, id = emitters header in + light_octet ~emitter parent header >>| fun () -> + Leaf { header; body = id } | `Message -> - mail parent >>| fun (header', body') -> Message { header= header'; body= body'; } - | `Multipart -> - match boundary header with - | Some boundary -> - Rfc2046.multipart_body ?parent boundary (body (Option.some boundary)) - >>| List.map (fun (_header, body) -> body) - >>| fun parts -> Multipart { header; body= parts; } - | None -> fail "expected boundary" - + mail parent >>| fun (header', body') -> + Message { header = header'; body = body' } + | `Multipart -> ( + match boundary header with + | Some boundary -> + Rfc2046.multipart_body ?parent boundary + (body (Option.some boundary)) + >>| List.map (fun (_header, body) -> body) + >>| fun parts -> Multipart { header; body = parts } + | None -> fail "expected boundary") and mail parent = Header.Decoder.header <* char '\r' <* char '\n' >>= fun header -> match Content_type.ty (Header.content_type header) with | `Ietf_token _x | `X_token _x -> assert false | #Content_type.Type.discrete -> - let emitter, id = emitters header in - light_octet ~emitter parent header - >>| fun () -> header, Leaf { header; body= id; } + let emitter, id = emitters header in + light_octet ~emitter parent header >>| fun () -> + (header, Leaf { header; body = id }) | `Message -> - mail parent >>| fun (header', body') -> - header, Message { header= header'; body= body'; } - | `Multipart -> - match boundary header with - | Some boundary -> - Rfc2046.multipart_body ?parent boundary (body (Option.some boundary)) - >>| List.map (fun (_header, body) -> body) - >>| fun parts -> header, Multipart { header; body= parts; } - | None -> fail "expected boundary" in + mail parent >>| fun (header', body') -> + (header, Message { header = header'; body = body' }) + | `Multipart -> ( + match boundary header with + | Some boundary -> + Rfc2046.multipart_body ?parent boundary + (body (Option.some boundary)) + >>| List.map (fun (_header, body) -> body) + >>| fun parts -> (header, Multipart { header; body = parts }) + | None -> fail "expected boundary") + in mail None diff --git a/lib/mail.mli b/lib/mail.mli index a0b07e5..2c906b9 100644 --- a/lib/mail.mli +++ b/lib/mail.mli @@ -14,7 +14,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *) -type 'a elt = { header : Header.t; body : 'a; } +type 'a elt = { header : Header.t; body : 'a } type 'a t = | Leaf of 'a elt @@ -24,7 +24,11 @@ type 'a t = val heavy_octet : string option -> Header.t -> string Angstrom.t (** {i Heavy} parser of a body - it will stores bodies into [string]. *) -val light_octet : emitter:(string option -> unit) -> string option -> Header.t -> unit Angstrom.t +val light_octet : + emitter:(string option -> unit) -> + string option -> + Header.t -> + unit Angstrom.t (** {i Light} parser of body - it sends contents to given [emitter]. *) val mail : (Header.t * string t) Angstrom.t @@ -32,6 +36,8 @@ val mail : (Header.t * string t) Angstrom.t type 'id emitters = Header.t -> (string option -> unit) * 'id -val stream : emitters:(Header.t -> (string option -> unit) * 'id) -> (Header.t * 'id t) Angstrom.t +val stream : + emitters:(Header.t -> (string option -> unit) * 'id) -> + (Header.t * 'id t) Angstrom.t (** [stream ~emitters] is an Angstrom parser of an entire RFC 5322 mail which will use given emitters by [emitters] to store bodies. *) diff --git a/lib/mailbox.ml b/lib/mailbox.ml index cb87061..d788629 100644 --- a/lib/mailbox.ml +++ b/lib/mailbox.ml @@ -8,11 +8,12 @@ let is_utf8_valid_string_with is x = let exception Invalid_char in try Uutf.String.fold_utf_8 - (fun () _pos -> function `Malformed _ -> raise Invalid_utf8 + (fun () _pos -> function + | `Malformed _ -> raise Invalid_utf8 | `Uchar uchar -> if Uchar.is_char uchar && not (is (Uchar.to_char uchar)) then - raise Invalid_char ) - () x ; + raise Invalid_char) + () x; true with | Invalid_utf8 -> false @@ -23,7 +24,7 @@ let is_utf8_valid_string x = try Uutf.String.fold_utf_8 (fun () _pos -> function `Malformed _ -> raise Invalid_utf8 | _ -> ()) - () x ; + () x; true with Invalid_utf8 -> false @@ -33,18 +34,13 @@ let is_qtext_valid_string = is_utf8_valid_string_with Emile.Parser.is_qtext let need_to_escape, escape_char = (* See [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 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_string x = let len = String.length x in @@ -52,20 +48,17 @@ let escape_string x = 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] ; + Buffer.add_char res '\\'; + Buffer.add_char res (escape_char x.[!pos])) + else Buffer.add_char res x.[!pos]; incr pos - done ; + done; Buffer.contents res let make_word raw = - if is_atext_valid_string raw - then Ok (`Atom raw) - else if is_qtext_valid_string raw - then Ok (`String raw) - else if is_utf8_valid_string raw - then Ok (`String (escape_string raw)) + if is_atext_valid_string raw then Ok (`Atom raw) + else if is_qtext_valid_string raw then Ok (`String raw) + else if is_utf8_valid_string raw then Ok (`String (escape_string raw)) else Rresult.R.error_msgf "word %S does not respect standards" raw module Decoder = struct @@ -76,73 +69,97 @@ end module Encoder = struct open Prettym - let atom = [ !!string; ] - let str = [ char $ '"'; !!string; char $ '"'; ] + let atom = [ !!string ] + let str = [ char $ '"'; !!string; char $ '"' ] let word ppf = function | `Atom x -> eval ppf atom x | `String x -> eval ppf str (escape_string x) - let dot = - (fun ppf () -> eval ppf [ cut; char $ '.'; cut ]), () - let comma = - (fun ppf () -> eval ppf [ cut; char $ ','; cut ]), () - - let local ppf lst = - eval ppf [ box; !!(list ~sep:dot word); close ] lst - + let dot = ((fun ppf () -> eval ppf [ cut; char $ '.'; cut ]), ()) + let comma = ((fun ppf () -> eval ppf [ cut; char $ ','; cut ]), ()) + let local ppf lst = eval ppf [ box; !!(list ~sep:dot word); close ] lst let ipaddr_v4 = using Ipaddr.V4.to_string string let ipaddr_v6 = using Ipaddr.V6.to_string string let domain ppf = function | `Domain domain -> - let boxed_string ppf x = eval ppf [ box; !!string; close ] x in - eval ppf [ box; !!(list ~sep:dot boxed_string); close ] domain + let boxed_string ppf x = eval ppf [ box; !!string; close ] x in + eval ppf [ box; !!(list ~sep:dot boxed_string); close ] domain | `Literal literal -> - eval ppf [ box; char $ '['; cut; !!string; cut; char $ ']'; close ] literal + eval ppf + [ box; char $ '['; cut; !!string; cut; char $ ']'; close ] + literal | `Addr (Emile.IPv4 ip) -> - eval ppf [ box; char $ '['; cut; !!ipaddr_v4; cut; char $ ']'; close ] ip + eval ppf + [ box; char $ '['; cut; !!ipaddr_v4; cut; char $ ']'; close ] + ip | `Addr (Emile.IPv6 ip) -> - eval ppf [ box; char $ '['; cut; string $ "IPv6:"; cut; !!ipaddr_v6; cut; char $ ']'; close ] ip + eval ppf + [ + box; char $ '['; cut; string $ "IPv6:"; cut; !!ipaddr_v6; cut; + char $ ']'; close; + ] + ip | `Addr (Emile.Ext (ldh, v)) -> - eval ppf [ box; char $ '['; cut; !!string; cut; char $ ':'; cut; !!string; cut; char $ ']'; close ] ldh v + eval ppf + [ + box; char $ '['; cut; !!string; cut; char $ ':'; cut; !!string; cut; + char $ ']'; close; + ] + ldh v let phrase ppf lst = let elt ppf = function | `Dot -> char ppf '.' | `Word w -> word ppf w | `Encoded (charset, Emile.Quoted_printable data) -> - Encoded_word.Encoder.encoded_word ppf - { Encoded_word.charset= `Charset charset - ; encoding= Encoded_word.Quoted_printable - ; data } + Encoded_word.Encoder.encoded_word ppf + { + Encoded_word.charset = `Charset charset; + encoding = Encoded_word.Quoted_printable; + data; + } | `Encoded (charset, Emile.Base64 data) -> - Encoded_word.Encoder.encoded_word ppf - { Encoded_word.charset= `Charset charset - ; encoding= Encoded_word.Base64 - ; data } in + Encoded_word.Encoder.encoded_word ppf + { + Encoded_word.charset = `Charset charset; + encoding = Encoded_word.Base64; + data; + } + in let space ppf () = eval ppf [ fws ] in eval ppf [ box; !!(list ~sep:(space, ()) elt); close ] lst - let mailbox ppf (t:Emile.mailbox) = - match t.Emile.name, t.Emile.domain with + let mailbox ppf (t : Emile.mailbox) = + match (t.Emile.name, t.Emile.domain) with | Some name, (x, []) -> - eval ppf [ box; !!phrase ; spaces 1; char $ '<'; cut; !!local; cut; char $ '@'; cut; !!domain; cut; char $ '>'; close ] - name t.Emile.local x + eval ppf + [ + box; !!phrase; spaces 1; char $ '<'; cut; !!local; cut; char $ '@'; + cut; !!domain; cut; char $ '>'; close; + ] + name t.Emile.local x | None, (x, []) -> - eval ppf [ box; !!local ; cut; char $ '@'; cut; !!domain; close ] - t.Emile.local x + eval ppf + [ box; !!local; cut; char $ '@'; cut; !!domain; close ] + t.Emile.local x | name, (x, r) -> - let domains ppf lst = - let domain ppf x = eval ppf [ box; char $ '@'; !!domain; close ] x in - (* XXX(dinosaure): according RFC, comma is surrounded by CFWS. *) - let comma = (fun ppf () -> eval ppf [ fws; char $ ','; fws ]), () in - eval ppf [ box; !!(list ~sep:comma domain); close ] lst in - let phrase ppf x = eval ppf [ box; !!phrase; spaces 1; close ] x in - - eval ppf - [ box; !!(option phrase); cut; char $ '<'; cut; !!domains; cut; char $ ':'; cut; !!local; cut; char $ '@'; cut; !!domain; cut; char $ '>'; close ] - name r t.Emile.local x + let domains ppf lst = + let domain ppf x = eval ppf [ box; char $ '@'; !!domain; close ] x in + (* XXX(dinosaure): according RFC, comma is surrounded by CFWS. *) + let comma = ((fun ppf () -> eval ppf [ fws; char $ ','; fws ]), ()) in + eval ppf [ box; !!(list ~sep:comma domain); close ] lst + in + let phrase ppf x = eval ppf [ box; !!phrase; spaces 1; close ] x in + + eval ppf + [ + box; !!(option phrase); cut; char $ '<'; cut; !!domains; cut; + char $ ':'; cut; !!local; cut; char $ '@'; cut; !!domain; cut; + char $ '>'; close; + ] + name r t.Emile.local x let mailboxes = list ~sep:comma mailbox end @@ -154,34 +171,32 @@ module Phrase = struct let o : elt = `Dot let q = Encoded_word.q let b = Encoded_word.b - let word x : (elt, [> Rresult.R.msg ]) result = Rresult.R.(make_word x >>| fun x -> `Word x) + + let word x : (elt, [> Rresult.R.msg ]) result = + Rresult.R.(make_word x >>| fun x -> `Word x) let word_exn x : elt = - match word x with - | Ok v -> v - | Error (`Msg err) -> invalid_arg err + match word x with Ok v -> v | Error (`Msg err) -> invalid_arg err let w : string -> elt = word_exn + let e ~encoding v : elt = let x = Encoded_word.make_exn ~encoding v in let charset = Encoded_word.charset_to_string x.Encoded_word.charset in match x.Encoded_word.encoding with | Base64 -> `Encoded (charset, Emile.Base64 x.Encoded_word.data) - | Quoted_printable -> `Encoded (charset, Emile.Quoted_printable x.Encoded_word.data) + | Quoted_printable -> + `Encoded (charset, Emile.Quoted_printable x.Encoded_word.data) let rec coerce : type a. a Peano.s t -> Emile.phrase = function - | [ x ] -> [(x :> elt)] + | [ x ] -> [ (x :> elt) ] | x :: y :: r -> List.cons (x :> elt) (coerce (y :: r)) let make : type a. a t -> (Emile.phrase, [> Rresult.R.msg ]) result = function | [] -> Rresult.R.error_msgf "A phrase must contain at least one element" | x :: r -> Ok (coerce (x :: r)) - let v l = - match make l with - | Ok v -> v - | Error (`Msg err) -> invalid_arg err - + let v l = match make l with Ok v -> v | Error (`Msg err) -> invalid_arg err let to_string x = Prettym.to_string Encoder.phrase x end @@ -196,10 +211,11 @@ module Literal_domain = struct try let len = String.length x in String.iteri - (fun pos -> function 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' -> () + (fun pos -> function + | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' -> () | '-' -> if pos = len - 1 then raise Invalid_char (* else () *) - | _ -> raise Invalid_char ) - x ; + | _ -> raise Invalid_char) + x; true with Invalid_char -> false @@ -212,7 +228,7 @@ module Literal_domain = struct try String.iter (fun chr -> if not (is_dcontent chr) then raise Invalid_char) - x ; + x; true with Invalid_char -> false @@ -229,31 +245,31 @@ module Literal_domain = struct let ldh, value = v in if is_ldh_valid_string ldh && is_dcontent_valid_string value then Ok (Emile.Ext (ldh, value)) - else Rresult.R.error_msgf "literal-domain %S-%S does not respect standards" ldh value + else + Rresult.R.error_msgf "literal-domain %S-%S does not respect standards" + ldh value let v : type a. a t -> a -> Emile.addr = fun witness v -> - match make witness v with - | Ok v -> v - | Error (`Msg err) -> invalid_arg err + match make witness v with Ok v -> v | Error (`Msg err) -> invalid_arg err end module Domain = struct - let atom x = if is_atext_valid_string x then Ok (`Atom x) else Rresult.R.error_msgf "atom %S does not respect standards" x + let atom x = + if is_atext_valid_string x then Ok (`Atom x) + else Rresult.R.error_msgf "atom %S does not respect standards" x let atom_exn x = - match atom x with - | Ok v -> v - | Error (`Msg err) -> invalid_arg err + match atom x with Ok v -> v | Error (`Msg err) -> invalid_arg err let a = atom_exn let literal x = let need_to_escape, escape_char = (* TODO *) - let bindings = [('\000', '\000')] in - ( (fun chr -> List.mem_assoc chr bindings) - , fun chr -> List.assoc chr bindings ) + let bindings = [ ('\000', '\000') ] in + ( (fun chr -> List.mem_assoc chr bindings), + fun chr -> List.assoc chr bindings ) in let escape_string x = let len = String.length x in @@ -261,11 +277,11 @@ module Domain = struct 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] ; + Buffer.add_char res '\\'; + Buffer.add_char res (escape_char x.[!pos])) + else Buffer.add_char res x.[!pos]; incr pos - done ; + done; Buffer.contents res in if is_dtext_valid_string x then Ok (`Literal x) @@ -273,22 +289,22 @@ module Domain = struct else Rresult.R.error_msgf "literal domain %S does not respect standards" x let literal_exn x = - match literal x with - | Ok v -> v - | Error (`Msg err) -> invalid_arg err + match literal x with Ok v -> v | Error (`Msg err) -> invalid_arg err - type atom = [`Atom of string] - type literal = [`Literal of string] + type atom = [ `Atom of string ] + type literal = [ `Literal of string ] let of_list l = let l = List.map atom l in let l = List.fold_left - (fun a x -> match a, x with - | (Error _ as err), _ -> err - | _, (Error _ as err) -> err - | Ok a, Ok (`Atom x) -> Ok (x :: a)) - (Ok []) l in + (fun a x -> + match (a, x) with + | (Error _ as err), _ -> err + | _, (Error _ as err) -> err + | Ok a, Ok (`Atom x) -> Ok (x :: a)) + (Ok []) l + in let open Rresult.R in l >>| List.rev >>= function | [] -> error_msgf "A domain must contain at least one element" @@ -299,10 +315,11 @@ module Domain = struct | [] : Peano.z domain let rec coerce : type a. a Peano.s domain -> string list = function - | [`Atom x] -> [x] + | [ `Atom x ] -> [ x ] | `Atom x :: y :: r -> List.cons x (coerce (y :: r)) - let make_domain : type a. a domain -> (string list, [> Rresult.R.msg ]) result = function + let make_domain : type a. a domain -> (string list, [> Rresult.R.msg ]) result + = function | [] -> Rresult.R.error_msg "A domain must contain at least one element" | x :: r -> Ok (coerce (x :: r)) @@ -327,9 +344,7 @@ module Domain = struct let v : type a. a t -> a -> Emile.domain = fun witness v -> - match make witness v with - | Ok v -> v - | Error (`Msg err) -> invalid_arg err + match make witness v with Ok v -> v | Error (`Msg err) -> invalid_arg err let to_string x = Prettym.to_string Encoder.domain x end @@ -339,20 +354,21 @@ module Local = struct | [] : Peano.z local | ( :: ) : Emile.word * 'a local -> 'a Peano.s local - let word x = if String.length x > 0 then make_word x else Rresult.R.error_msgf "A word can not be empty" + let word x = + if String.length x > 0 then make_word x + else Rresult.R.error_msgf "A word can not be empty" let word_exn x = - match word x with - | Ok v -> v - | Error (`Msg err) -> invalid_arg err + match word x with Ok v -> v | Error (`Msg err) -> invalid_arg err let w = word_exn let rec coerce : type a. a Peano.s local -> Emile.local = function - | [x] -> List.cons x [] + | [ x ] -> List.cons x [] | x :: y :: r -> List.cons x (coerce (y :: r)) - let make : type a. a local -> (Emile.local, [> Rresult.R.msg ]) result = function + let make : type a. a local -> (Emile.local, [> Rresult.R.msg ]) result = + function | [] -> Rresult.R.error_msg "A local-part must contain at least one element" | x :: r -> Ok (coerce (x :: r)) @@ -360,41 +376,39 @@ module Local = struct let l = List.map word l in let l = List.fold_left - (fun a x -> match a, x with - | (Error _ as err), _ -> err - | _, (Error _ as err) -> err - | Ok a, Ok x -> Ok (List.cons x a)) - (Ok []) l in + (fun a x -> + match (a, x) with + | (Error _ as err), _ -> err + | _, (Error _ as err) -> err + | Ok a, Ok x -> Ok (List.cons x a)) + (Ok []) l + in let open Rresult.R in l >>| List.rev >>= function | [] -> error_msgf "A local-part must contain at least one element" | v -> Ok v let v : type a. a local -> Emile.local = - fun l -> - match make l with - | Ok v -> v - | Error (`Msg err) -> invalid_arg err + fun l -> match make l with Ok v -> v | Error (`Msg err) -> invalid_arg err let to_string x = Prettym.to_string Encoder.local x end -let make ?name local ?(domains= []) domain = - { Emile.name; local; domain= (domain, domains); } +let make ?name local ?(domains = []) domain = + { Emile.name; local; domain = (domain, domains) } let ( @ ) : 'a Local.local -> 'b Domain.t * 'b -> Emile.mailbox = fun local (witness, domain) -> match (Local.make local, Domain.make witness domain) with - | Ok local, Ok domain -> - { Emile.name= None; local; domain= (domain, []) } + | Ok local, Ok domain -> { Emile.name = None; local; domain = (domain, []) } | Error (`Msg err), Ok _ -> invalid_arg err | Ok _, Error (`Msg err) -> invalid_arg err | Error _, Error _ -> Fmt.invalid_arg "Invalid local-part and domain" -let with_name = - fun name mailbox -> { mailbox with Emile.name= Some name } +let with_name name mailbox = { mailbox with Emile.name = Some name } -let of_string x = match Emile.of_string x with +let of_string x = + match Emile.of_string x with | Ok v -> Ok v | Error (`Invalid _) -> Rresult.R.error_msgf "Invalid email address: %S" x diff --git a/lib/mailbox.mli b/lib/mailbox.mli index db7dac3..30878e8 100644 --- a/lib/mailbox.mli +++ b/lib/mailbox.mli @@ -61,10 +61,12 @@ module Phrase : sig are NOT allowed - we can compute them but we choose to never produce them. *) - type elt = [ `Dot | `Word of Emile.word | `Encoded of string * Emile.raw ] - type 'a t = [] : Peano.z t | ( :: ) : elt * 'a t -> 'a Peano.s t - (** Phrase, according RFC 5322, is a non-empty list of three + + type 'a t = + | [] : Peano.z t + | ( :: ) : elt * 'a t -> 'a Peano.s t + (** Phrase, according RFC 5322, is a non-empty list of three elements ({!elt}): {ul @@ -180,8 +182,8 @@ module Domain : sig However, this last kind conforms only RFC 5322 - RFC 5321 (SMTP protocol) does not recognize this kind of domain. *) - type atom = [`Atom of string] - type literal = [`Literal of string] + type atom = [ `Atom of string ] + type literal = [ `Literal of string ] type 'a domain = | ( :: ) : atom * 'a domain -> 'a Peano.s domain @@ -312,7 +314,12 @@ module Local : sig (** [to_string x] returns a string which represents [x] as it is in an e-mail. *) end -val make : ?name:Emile.phrase -> Emile.local -> ?domains:Emile.domain list -> Emile.domain -> t +val make : + ?name:Emile.phrase -> + Emile.local -> + ?domains:Emile.domain list -> + Emile.domain -> + t (** [make ?name local ?domains domain] returns a {!mailbox} with local-part [local], first domain [domain], others domains [domains] (default is an empty list) and an optional name. diff --git a/lib/messageID.ml b/lib/messageID.ml index f215c90..fc96ffb 100644 --- a/lib/messageID.ml +++ b/lib/messageID.ml @@ -1,52 +1,61 @@ type domain = [ `Literal of string | `Domain of string list ] type t = Emile.local * domain -let pp_domain : domain Fmt.t = fun ppf -> function +let pp_domain : domain Fmt.t = + fun ppf -> function | `Domain _ as x -> Emile.pp_domain ppf x | `Literal _ as x -> Emile.pp_domain ppf x let pp ppf (local, domain) = - Fmt.pf ppf "<%a@%a>" - Emile.pp_local local - pp_domain domain + Fmt.pf ppf "<%a@%a>" Emile.pp_local local pp_domain domain -let equal_domain a b = match a, b with +let equal_domain a b = + match (a, b) with | a, b -> Emile.equal_domain (a :> Emile.domain) (b :> Emile.domain) let equal a b = - Emile.equal_local ~case_sensitive:true (fst a) (fst b) + Emile.equal_local ~case_sensitive:true (fst a) (fst b) && equal_domain (snd a) (snd b) module Decoder = struct open Angstrom - let message_id = Emile.Parser.msg_id >>= fun (local, domain) -> + let message_id = + Emile.Parser.msg_id >>= fun (local, domain) -> match domain with | `Addr _ -> fail "Invalid message-id" | #domain as domain -> return (local, domain) end -let of_string x = match Angstrom.parse_string ~consume:Angstrom.Consume.Prefix Decoder.message_id x with +let of_string x = + match + Angstrom.parse_string ~consume:Angstrom.Consume.Prefix Decoder.message_id x + with | Ok v -> Ok v | Error _ -> Rresult.R.error_msgf "Invalid message ID: %S" x module Encoder = struct open Prettym - let dot = (fun ppf () -> eval ppf [ cut; char $ '.'; cut ]), () + let dot = ((fun ppf () -> eval ppf [ cut; char $ '.'; cut ]), ()) - let domain : domain Prettym.t = fun ppf -> function + let domain : domain Prettym.t = + fun ppf -> function | `Domain domain -> - let x ppf x = eval ppf [ box; !!string; close ] x in - eval ppf [ tbox 1; !!(list ~sep:dot x); close ] domain + let x ppf x = eval ppf [ box; !!string; close ] x in + eval ppf [ tbox 1; !!(list ~sep:dot x); close ] domain | `Literal literal -> - eval ppf [ tbox 1; char $ '['; !!string; char $ ']'; close ] literal + eval ppf [ tbox 1; char $ '['; !!string; char $ ']'; close ] literal - let message_id = fun ppf t -> + let message_id ppf t = match t with - | (local_part, domain_part) -> - eval ppf [ tbox 1; char $ '<'; !!Mailbox.Encoder.local; char $ '@'; !!domain; char $ '>'; close ] - local_part domain_part + | local_part, domain_part -> + eval ppf + [ + tbox 1; char $ '<'; !!Mailbox.Encoder.local; char $ '@'; !!domain; + char $ '>'; close; + ] + local_part domain_part end let is_utf8_valid_string_with is x = @@ -54,11 +63,12 @@ let is_utf8_valid_string_with is x = let exception Invalid_char in try Uutf.String.fold_utf_8 - (fun () _pos -> function `Malformed _ -> raise Invalid_utf8 + (fun () _pos -> function + | `Malformed _ -> raise Invalid_utf8 | `Uchar uchar -> if Uchar.is_char uchar && not (is (Uchar.to_char uchar)) then - raise Invalid_char ) - () x ; + raise Invalid_char) + () x; true with | Invalid_utf8 -> false @@ -69,7 +79,7 @@ let is_utf8_valid_string x = try Uutf.String.fold_utf_8 (fun () _pos -> function `Malformed _ -> raise Invalid_utf8 | _ -> ()) - () x ; + () x; true with Invalid_utf8 -> false @@ -77,8 +87,9 @@ let is_atext = function | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' - | '!' | '#' | '$' | '%' | '&' | '\'' | '*' | '+' | '-' | '/' | '=' | '?' - | '^' | '_' | '`' | '{' | '}' | '|' | '~' -> true + | '!' | '#' | '$' | '%' | '&' | '\'' | '*' | '+' | '-' | '/' | '=' | '?' | '^' + | '_' | '`' | '{' | '}' | '|' | '~' -> + true | _ -> false let is_obs_no_ws_ctl = function @@ -105,21 +116,23 @@ module Domain = struct let literal x = let need_to_escape, escape_char = (* TODO *) - let bindings = [('\000', '\000')] in - ( (fun chr -> List.mem_assoc chr bindings) - , fun chr -> List.assoc chr bindings ) in + let bindings = [ ('\000', '\000') ] in + ( (fun chr -> List.mem_assoc chr bindings), + fun chr -> List.assoc chr bindings ) + in let escape_string 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] ; + 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 in + done; + Buffer.contents res + in if is_dtext_valid_string x then Some (`Literal x) else if is_utf8_valid_string x then Some (`Literal (escape_string x)) else None @@ -129,29 +142,29 @@ module Domain = struct | Some v -> v | None -> Fmt.invalid_arg "literal_exn: invalid domain literal value %S" x - type atom = [`Atom of string] - type literal = [`Literal of string] + type atom = [ `Atom of string ] + type literal = [ `Literal of string ] type 'a domain = | ( :: ) : atom * 'a domain -> 'a Peano.s domain | [] : Peano.z domain let rec coerce : type a. a Peano.s domain -> string list = function - | [`Atom x] -> [x] + | [ `Atom x ] -> [ x ] | `Atom x :: y :: r -> List.cons x (coerce (y :: r)) let make_domain : type a. a domain -> string list option = function | [] -> None | x :: r -> Some (coerce (x :: r)) - type 'a t = - | Domain : 'a domain t - | Literal : string t + type 'a t = Domain : 'a domain t | Literal : string t let domain = Domain let default = Literal - let make : type a. a t -> a -> [ `Literal of string | `Domain of string list ] option = + let make : + type a. a t -> a -> [ `Literal of string | `Domain of string list ] option + = fun witness v -> match witness with | Domain -> Option.(make_domain v >>| fun v -> `Domain v) diff --git a/lib/messageID.mli b/lib/messageID.mli index ef7b940..e49a709 100644 --- a/lib/messageID.mli +++ b/lib/messageID.mli @@ -38,8 +38,8 @@ module Domain : sig - : string = "[x25519]" ]} *) - type atom = [`Atom of string] - type literal = [`Literal of string] + type atom = [ `Atom of string ] + type literal = [ `Literal of string ] type 'a domain = | ( :: ) : atom * 'a domain -> 'a Peano.s domain @@ -77,7 +77,8 @@ module Domain : sig val default : string t (** Kind of {!literal}. *) - val make : 'a t -> 'a -> [ `Literal of string | `Domain of string list ] option + val make : + 'a t -> 'a -> [ `Literal of string | `Domain of string list ] option (** [make kind v] returns a safe domain. It can fail if an user-defined literal-domain ({!Literal_domain.extension}), a {!literal} domain or a {!domain} don't follow standards: diff --git a/lib/mrmime.ml b/lib/mrmime.ml index ec09427..54a9151 100644 --- a/lib/mrmime.ml +++ b/lib/mrmime.ml @@ -29,6 +29,5 @@ module Address = Address module Group = Group module Unstructured = Unstructured module Mail = Mail - module Hd = Hd module Mt = Mt diff --git a/lib/mt.ml b/lib/mt.ml index 3d71d33..09f2a3d 100644 --- a/lib/mt.ml +++ b/lib/mt.ml @@ -1,202 +1,245 @@ -type 'x stream = (unit -> 'x option) +type 'x stream = unit -> 'x option type buffer = string * int * int - type field = Field_name.t * Unstructured.t -let iter ~f buf ~off ~len = for i = off to len - 1 do f buf.[i] done +let iter ~f buf ~off ~len = + for i = off to len - 1 do + f buf.[i] + done -let to_quoted_printable : ?length:int -> buffer stream -> buffer stream = fun ?length:(chunk_length= 4096) stream -> +let to_quoted_printable : ?length:int -> buffer stream -> buffer stream = + fun ?length:(chunk_length = 4096) stream -> let chunk = Bytes.create chunk_length in let encoder = Pecu.encoder `Manual in let queue = Ke.Rke.create ~capacity:128 Bigarray.Int in let rec emit () = - Ke.Rke.cons queue 256 ; + Ke.Rke.cons queue 256; let len = chunk_length - Pecu.dst_rem encoder in Some (Bytes.unsafe_to_string chunk, 0, len) - and pending = function | `Ok -> go () | `Partial -> - let len = chunk_length - Pecu.dst_rem encoder in - Some (Bytes.unsafe_to_string chunk, 0, len) - - and go () = match Ke.Rke.pop_exn queue with - | 256 (* Await *) -> - Pecu.dst encoder chunk 0 chunk_length ; - ( match Pecu.encode encoder `Await with - | `Ok -> (go[@tailcall]) () - | `Partial -> (emit[@tailcall]) () ) - | 257 (* Line_break *) -> - (* XXX(dinosaure): we encode, in any case, a last CRLF to ensure that any - line emitted by [to_quoted_printable] finish with a [CRLF]. TODO: may - be this behavior is strictly under [Pecu] impl. *) - Ke.Rke.cons queue 258 ; - ( match Pecu.encode encoder `Line_break with + let len = chunk_length - Pecu.dst_rem encoder in + Some (Bytes.unsafe_to_string chunk, 0, len) + and go () = + match Ke.Rke.pop_exn queue with + | 256 (* Await *) -> ( + Pecu.dst encoder chunk 0 chunk_length; + match Pecu.encode encoder `Await with + | `Ok -> (go [@tailcall]) () + | `Partial -> (emit [@tailcall]) ()) + | 257 (* Line_break *) -> ( + (* XXX(dinosaure): we encode, in any case, a last CRLF to ensure that any + line emitted by [to_quoted_printable] finish with a [CRLF]. TODO: may + be this behavior is strictly under [Pecu] impl. *) + Ke.Rke.cons queue 258; + match Pecu.encode encoder `Line_break with | `Ok -> go () - | `Partial -> (emit[@tailcall]) () ) + | `Partial -> (emit [@tailcall]) ()) | 258 (* End *) -> - Ke.Rke.cons queue 259 ; - (pending[@tailcall]) (Pecu.encode encoder `End) - | 259 -> assert (Pecu.encode encoder `Await = `Ok) ; Ke.Rke.cons queue 259 ; None - | chr -> - ( match Pecu.encode encoder (`Char (Char.chr chr)) with - | `Ok -> (go[@tailcall]) () - | `Partial -> (emit[@tailcall]) () ) - | exception Ke.Rke.Empty -> - match stream () with - | Some (buf, off, len) -> iter ~f:(fun chr -> Ke.Rke.push queue (Char.code chr)) buf ~off ~len ; (go[@tailcall]) () - | None -> Ke.Rke.push queue 257 ; (go[@tailcall]) () in - - Pecu.dst encoder chunk 0 chunk_length ; go - -let to_base64 : ?length:int -> buffer stream -> buffer stream = fun ?length:(chunk_length= 4096) stream -> + Ke.Rke.cons queue 259; + (pending [@tailcall]) (Pecu.encode encoder `End) + | 259 -> + assert (Pecu.encode encoder `Await = `Ok); + Ke.Rke.cons queue 259; + None + | chr -> ( + match Pecu.encode encoder (`Char (Char.chr chr)) with + | `Ok -> (go [@tailcall]) () + | `Partial -> (emit [@tailcall]) ()) + | exception Ke.Rke.Empty -> ( + match stream () with + | Some (buf, off, len) -> + iter ~f:(fun chr -> Ke.Rke.push queue (Char.code chr)) buf ~off ~len; + (go [@tailcall]) () + | None -> + Ke.Rke.push queue 257; + (go [@tailcall]) ()) + in + + Pecu.dst encoder chunk 0 chunk_length; + go + +let to_base64 : ?length:int -> buffer stream -> buffer stream = + fun ?length:(chunk_length = 4096) stream -> let chunk = Bytes.create chunk_length in let encoder = Base64_rfc2045.encoder `Manual in let queue = Ke.Rke.create ~capacity:128 Bigarray.Int in let rec emit () = - Ke.Rke.cons queue 256 ; + Ke.Rke.cons queue 256; let len = chunk_length - Base64_rfc2045.dst_rem encoder in Some (Bytes.unsafe_to_string chunk, 0, len) - and pending = function - | `Ok -> (go[@tailcall]) () + | `Ok -> (go [@tailcall]) () | `Partial -> - let len = chunk_length - Base64_rfc2045.dst_rem encoder in - Some (Bytes.unsafe_to_string chunk, 0, len) - - and go () = match Ke.Rke.pop_exn queue with - | 256 (* Await *) -> - Base64_rfc2045.dst encoder chunk 0 chunk_length ; - ( match Base64_rfc2045.encode encoder `Await with - | `Ok -> (go[@tailcall]) () - | `Partial -> (emit[@tailcall]) () ) + let len = chunk_length - Base64_rfc2045.dst_rem encoder in + Some (Bytes.unsafe_to_string chunk, 0, len) + and go () = + match Ke.Rke.pop_exn queue with + | 256 (* Await *) -> ( + Base64_rfc2045.dst encoder chunk 0 chunk_length; + match Base64_rfc2045.encode encoder `Await with + | `Ok -> (go [@tailcall]) () + | `Partial -> (emit [@tailcall]) ()) | 257 (* End *) -> - Ke.Rke.cons queue 258 ; - (pending[@tailcall]) (Base64_rfc2045.encode encoder `End) - | 258 -> assert (Base64_rfc2045.encode encoder `Await = `Ok) ; Ke.Rke.cons queue 258 ; None - | chr -> - ( match Base64_rfc2045.encode encoder (`Char (Char.chr chr)) with - | `Ok -> (go[@tailcall]) () - | `Partial -> (emit[@tailcall]) () ) - | exception Ke.Rke.Empty -> - match stream () with - | Some (buf, off, len) -> iter ~f:(fun chr -> Ke.Rke.push queue (Char.code chr)) buf ~off ~len ; (go[@tailcall]) () - | None -> Ke.Rke.push queue 257 ; (go[@tailcall]) () in - - Base64_rfc2045.dst encoder chunk 0 chunk_length ; go - -type part = - { header : Header.t - ; body : buffer stream } - -type multipart = - { header : Header.t - ; parts : part list } - -let part ?(header= Header.empty) stream = + Ke.Rke.cons queue 258; + (pending [@tailcall]) (Base64_rfc2045.encode encoder `End) + | 258 -> + assert (Base64_rfc2045.encode encoder `Await = `Ok); + Ke.Rke.cons queue 258; + None + | chr -> ( + match Base64_rfc2045.encode encoder (`Char (Char.chr chr)) with + | `Ok -> (go [@tailcall]) () + | `Partial -> (emit [@tailcall]) ()) + | exception Ke.Rke.Empty -> ( + match stream () with + | Some (buf, off, len) -> + iter ~f:(fun chr -> Ke.Rke.push queue (Char.code chr)) buf ~off ~len; + (go [@tailcall]) () + | None -> + Ke.Rke.push queue 257; + (go [@tailcall]) ()) + in + + Base64_rfc2045.dst encoder chunk 0 chunk_length; + go + +type part = { header : Header.t; body : buffer stream } +type multipart = { header : Header.t; parts : part list } + +let part ?(header = Header.empty) stream = let content_type = Header.content_type header in let content_encoding = Header.content_encoding header in - if not (Content_type.is_discrete content_type) - then Fmt.invalid_arg "Content-type MUST be discrete type to make a part" ; - let stream = match content_encoding with + if not (Content_type.is_discrete content_type) then + Fmt.invalid_arg "Content-type MUST be discrete type to make a part"; + let stream = + match content_encoding with | `Quoted_printable -> to_quoted_printable stream | `Base64 -> to_base64 stream | `Bit8 | `Binary | `Bit7 -> stream - | `Ietf_token _ | `X_token _ -> assert false in (* XXX(dinosaure): TODO [`Bit7], IETF and extension encoding. *) - { header; body= stream; } + | `Ietf_token _ | `X_token _ -> assert false + in + (* XXX(dinosaure): TODO [`Bit7], IETF and extension encoding. *) + { header; body = stream } let multipart_content_default = let open Content_type in Content_type.make `Multipart (Subtype.v `Multipart "mixed") Parameters.empty type 'g rng = ?g:'g -> int -> string + external random_seed : unit -> int array = "caml_sys_random_seed" -let rng ?(g= random_seed ()) n = - Random.full_init g ; +let rng ?(g = random_seed ()) n = + Random.full_init g; let res = Bytes.create n in - for i = 0 to n - 1 do Bytes.set res i (Random.int 256 |> Char.chr) done ; + for i = 0 to n - 1 do + Bytes.set res i (Random.int 256 |> Char.chr) + done; Bytes.unsafe_to_string res |> Base64.encode_exn -let multipart ~rng ?(header= Header.empty) ?boundary parts = - let boundary = match boundary with Some boundary -> boundary | None -> rng ?g:None 8 in +let multipart ~rng ?(header = Header.empty) ?boundary parts = + let boundary = + match boundary with Some boundary -> boundary | None -> rng ?g:None 8 + in let boundary = Content_type.Parameters.v boundary in let content_type = - if Header.exists Field_name.content_type header - then Header.content_type header - else multipart_content_default in - let content_type = Content_type.with_parameter content_type ("boundary", boundary) in - let header = Header.replace Field_name.content_type (Field.Content, content_type) header in - { header; parts; } + if Header.exists Field_name.content_type header then + Header.content_type header + else multipart_content_default + in + let content_type = + Content_type.with_parameter content_type ("boundary", boundary) + in + let header = + Header.replace Field_name.content_type (Field.Content, content_type) header + in + { header; parts } -let none = (fun () -> None) +let none () = None let concat s0 s1 = let c = ref s0 in - let rec go () = match !c () with + let rec go () = + match !c () with | Some x -> Some x - | None -> if !c == s0 then ( c := s1 ; go ()) else None in + | None -> + if !c == s0 then ( + c := s1; + go ()) + else None + in go let stream_of_string x = let once = ref false in - let go () = if !once then None else ( once := true ; Some (x, 0, String.length x)) in go + let go () = + if !once then None + else ( + once := true; + Some (x, 0, String.length x)) + in + go let crlf () = stream_of_string "\r\n" - let ( @ ) a b = concat a b let map f stream = - let go () = match stream () with - | Some v -> Some (f v) - | None -> None in + let go () = match stream () with Some v -> Some (f v) | None -> None in go -let stream_of_part { header; body; } = - let content_stream = map (fun s -> s, 0, String.length s) (Prettym.to_stream Header.Encoder.header header) in +let stream_of_part { header; body } = + let content_stream = + map + (fun s -> (s, 0, String.length s)) + (Prettym.to_stream Header.Encoder.header header) + in content_stream @ crlf () @ body (* XXX(dinosaure): hard part to compile multiple parts under one. *) -let multipart_as_part : multipart -> part = fun { header; parts; } -> - let boundary = match Content_type.boundary (Header.content_type header) with +let multipart_as_part : multipart -> part = + fun { header; parts } -> + let boundary = + match Content_type.boundary (Header.content_type header) with | Some v -> v | None -> Fmt.failwith "Multipart MUST have a boundary" - (* XXX(dinosaure): should never occur! *) in + (* XXX(dinosaure): should never occur! *) + in let beginner = Rfc2046.make_dash_boundary boundary ^ "\r\n" in let inner = Rfc2046.make_delimiter boundary ^ "\r\n" in let closer = Rfc2046.make_close_delimiter boundary ^ "\r\n" in let rec go stream = function | [] -> none - | [ x ] -> stream @ (stream_of_part x) @ (stream_of_string closer) + | [ x ] -> stream @ stream_of_part x @ stream_of_string closer | x :: r -> - let stream = stream @ (stream_of_part x) @ (stream_of_string inner) in - go stream r in + let stream = stream @ stream_of_part x @ stream_of_string inner in + go stream r + in - { header; body= go (stream_of_string beginner) parts } + { header; body = go (stream_of_string beginner) parts } type 'x body = Simple : part body | Multi : multipart body let simple = Simple let multi = Multi -type t = - { header : Header.t - ; body : buffer stream } - -let rec make - : type a. Header.t -> a body -> a -> t - = fun header kind v -> match kind with - | Simple -> - let { header= header'; body; } : part = v in - { header= Header.concat header header'; body; } - | Multi -> +type t = { header : Header.t; body : buffer stream } + +let rec make : type a. Header.t -> a body -> a -> t = + fun header kind v -> + match kind with + | Simple -> + let ({ header = header'; body } : part) = v in + { header = Header.concat header header'; body } + | Multi -> let part = multipart_as_part v in make header Simple part let to_stream t : buffer stream = let header_stream = Header.to_stream t.header in let body_stream = t.body in - (map (fun s -> s, 0, String.length s) header_stream) @ crlf () @ body_stream + map (fun s -> (s, 0, String.length s)) header_stream @ crlf () @ body_stream diff --git a/lib/mt.mli b/lib/mt.mli index 62809a4..e02837f 100644 --- a/lib/mt.mli +++ b/lib/mt.mli @@ -14,11 +14,9 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. *) -type 'x stream = (unit -> 'x option) +type 'x stream = unit -> 'x option type buffer = string * int * int - type field = Field_name.t * Unstructured.t - type part type multipart type 'g rng = ?g:'g -> int -> string @@ -32,7 +30,8 @@ val part : ?header:Header.t -> buffer stream -> part use {!Content.default}. [stream] while be mapped according [Content-Transfer-Encoding] of [content]. *) -val multipart : rng:'g rng -> ?header:Header.t -> ?boundary:string -> part list -> multipart +val multipart : + rng:'g rng -> ?header:Header.t -> ?boundary:string -> part list -> multipart (** [multipart ~rng ~content ~boundary ~fields parts] makes a new multipart from a bunch of parts, specified [Content-*] fields, others [fields] and a specified [boundary]. If [boundary] is not specifed, we use [rng] to make a diff --git a/lib/option.ml b/lib/option.ml index b2db4a6..02c2422 100644 --- a/lib/option.ml +++ b/lib/option.ml @@ -2,11 +2,7 @@ let bind f = function Some x -> f x | None -> None let map f = function Some x -> Some (f x) | None -> None let ( >>= ) x f = bind f x let ( >>| ) x f = map f x - -let value ~default = function - | Some x -> x - | None -> default - +let value ~default = function Some x -> x | None -> default let some x = Some x let is_some = function Some _ -> true | None -> false let get_exn = function Some x -> x | None -> Fmt.invalid_arg "Option.get_exn" diff --git a/lib/quoted_printable.ml b/lib/quoted_printable.ml index 1caf5be..9b12bf7 100644 --- a/lib/quoted_printable.ml +++ b/lib/quoted_printable.ml @@ -10,108 +10,129 @@ let parser ~write_data ~write_line end_of_body = let check_end_of_body = let expected_len = String.length end_of_body in - Unsafe.peek expected_len - (fun ba ~off ~len -> - let raw = Bigstringaf.substring ba ~off ~len in - String.equal raw end_of_body) in + Unsafe.peek expected_len (fun ba ~off ~len -> + let raw = Bigstringaf.substring ba ~off ~len in + String.equal raw end_of_body) + in let trailer () = - let rec finish () = match Pecu.decode dec with + let rec finish () = + match Pecu.decode dec with | `Await -> assert false (* on [pecu], because [finish] was called just before [Pecu.src dec Bytes.empty 0 0] (so, when [len = 0]), semantically, it's impossible to retrieve this case. If [pecu] expects more inputs and we noticed end of input, it will return [`Malformed]. *) - | `Data data -> write_data data ; finish () - | `Line line -> write_line line ; finish () + | `Data data -> + write_data data; + finish () + | `Line line -> + write_line line; + finish () | `End -> commit | `Malformed err -> fail err - - and go () = match Pecu.decode dec with + and go () = + match Pecu.decode dec with | `Await -> - (* definitely [end_of_body]. *) - Pecu.src dec Bytes.empty 0 0 ; finish () + (* definitely [end_of_body]. *) + Pecu.src dec Bytes.empty 0 0; + finish () | `Data data -> - write_data data ; go () + write_data data; + go () | `Line line -> - write_line line ; go () + write_line line; + go () | `End -> commit - | `Malformed err -> fail err in + | `Malformed err -> fail err + in - go () in + go () + in fix @@ fun m -> let choose chunk = function | true -> - (* at this stage, we are at the end of body. We came from [`Await] case, - so it's safe to notice to [pecu] the last [chunk]. [trailer] will - unroll all outputs availables on [pecu]. *) - let chunk = Bytes.sub chunk 0 (Bytes.length chunk - 1) in - Pecu.src dec chunk 0 (Bytes.length chunk) ; trailer () + (* at this stage, we are at the end of body. We came from [`Await] case, + so it's safe to notice to [pecu] the last [chunk]. [trailer] will + unroll all outputs availables on [pecu]. *) + let chunk = Bytes.sub chunk 0 (Bytes.length chunk - 1) in + Pecu.src dec chunk 0 (Bytes.length chunk); + trailer () | false -> - (* at this stage, byte after [chunk] is NOT a part of [end_of_body]. We - can notice to [pecu] [chunk + end_of_body.[0]], advance on the - Angstrom's input to one byte, and recall fixpoint until [`Await] case - (see below). *) - Bytes.set chunk (Bytes.length chunk - 1) end_of_body.[0] ; - Pecu.src dec chunk 0 (Bytes.length chunk) ; - advance 1 *> m in + (* at this stage, byte after [chunk] is NOT a part of [end_of_body]. We + can notice to [pecu] [chunk + end_of_body.[0]], advance on the + Angstrom's input to one byte, and recall fixpoint until [`Await] case + (see below). *) + Bytes.set chunk (Bytes.length chunk - 1) end_of_body.[0]; + Pecu.src dec chunk 0 (Bytes.length chunk); + advance 1 *> m + in (* take while we did not discover the first byte of [end_of_body]. *) - - Unsafe.take_while ((<>) end_of_body.[0]) Bigstringaf.substring + Unsafe.take_while (( <> ) end_of_body.[0]) Bigstringaf.substring >>= fun chunk -> - (* start to know what we need to do with [pecu]. *) - - let rec go () = match Pecu.decode dec with + let rec go () = + match Pecu.decode dec with | `End -> commit | `Await -> - (* [pecu] expects inputs. At this stage, we know that after [chunk], we - have the first byte of [end_of_body] - but we don't know if we have - [end_of_body] or a part of it. - - [check_end_of_body] will advance to see if we really have - [end_of_body]. The result will be sended to [choose]. *) - let chunk' = Bytes.create (String.length chunk + 1) in - Bytes.blit_string chunk 0 chunk' 0 (String.length chunk) ; - check_end_of_body >>= choose chunk' + (* [pecu] expects inputs. At this stage, we know that after [chunk], we + have the first byte of [end_of_body] - but we don't know if we have + [end_of_body] or a part of it. + + [check_end_of_body] will advance to see if we really have + [end_of_body]. The result will be sended to [choose]. *) + let chunk' = Bytes.create (String.length chunk + 1) in + Bytes.blit_string chunk 0 chunk' 0 (String.length chunk); + check_end_of_body >>= choose chunk' | `Data data -> - write_data data ; go () + write_data data; + go () | `Line line -> - write_line line ; go () - | `Malformed err -> fail err in + write_line line; + go () + | `Malformed err -> fail err + in go () let with_buffer ?(end_of_line = "\n") end_of_body = let buf = Buffer.create 0x100 in let write_data x = Buffer.add_string buf x in let write_line x = - Buffer.add_string buf x ; - Buffer.add_string buf end_of_line in + Buffer.add_string buf x; + Buffer.add_string buf end_of_line + in parser ~write_data ~write_line end_of_body >>| fun () -> Buffer.contents buf let with_emitter ?(end_of_line = "\n") ~emitter end_of_body = let write_data x = emitter (Some x) in - let write_line x = - emitter (Some (x ^ end_of_line)) in + let write_line x = emitter (Some (x ^ end_of_line)) in parser ~write_data ~write_line end_of_body let to_end_of_input ~write_data ~write_line = let dec = Pecu.decoder `Manual in - fix @@ fun m -> match Pecu.decode dec with + fix @@ fun m -> + match Pecu.decode dec with | `End -> commit - | `Await -> - (peek_char >>= function - | None -> Pecu.src dec Bytes.empty 0 0 ; return () - | Some _ -> available >>= fun n -> Unsafe.take n - (fun ba ~off ~len -> - let chunk = Bytes.create len in - Bigstringaf.blit_to_bytes ba ~src_off:off chunk ~dst_off:0 ~len ; - Pecu.src dec chunk 0 len) - >>= fun () -> m) - | `Data data -> write_data data ; m - | `Line line -> write_line line ; m + | `Await -> ( + peek_char >>= function + | None -> + Pecu.src dec Bytes.empty 0 0; + return () + | Some _ -> + available >>= fun n -> + Unsafe.take n (fun ba ~off ~len -> + let chunk = Bytes.create len in + Bigstringaf.blit_to_bytes ba ~src_off:off chunk ~dst_off:0 ~len; + Pecu.src dec chunk 0 len) + >>= fun () -> m) + | `Data data -> + write_data data; + m + | `Line line -> + write_line line; + m | `Malformed err -> fail err diff --git a/lib/rfc2046.ml b/lib/rfc2046.ml index 21a7a6b..9ef71b7 100644 --- a/lib/rfc2046.ml +++ b/lib/rfc2046.ml @@ -7,8 +7,8 @@ open Angstrom "/" / ":" / "=" / "?" *) let is_bcharsnospace = function - | '\'' | '(' | ')' | '+' | '_' | ',' - | '-' | '.' | '/' | ':' | '=' | '?' -> true + | '\'' | '(' | ')' | '+' | '_' | ',' | '-' | '.' | '/' | ':' | '=' | '?' -> + true | 'a' .. 'z' | 'A' .. 'Z' -> true | '0' .. '9' -> true | _ -> false @@ -17,9 +17,7 @@ let is_bcharsnospace = function bchars := bcharsnospace / " " *) -let is_bchars = function - | ' ' -> true - | c -> is_bcharsnospace c +let is_bchars = function ' ' -> true | c -> is_bcharsnospace c (* From RFC 2046 @@ -28,36 +26,25 @@ let is_bchars = function ; boundary parameter of the ; Content-Type field. *) -let make_dash_boundary boundary = - "--" ^ boundary - -let dash_boundary boundary = - string (make_dash_boundary boundary) - -let make_delimiter boundary = - "\r\n" ^ (make_dash_boundary boundary) - -let make_close_delimiter boundary = - (make_delimiter boundary) ^ "--" - -let close_delimiter boundary = - string (make_close_delimiter boundary) +let make_dash_boundary boundary = "--" ^ boundary +let dash_boundary boundary = string (make_dash_boundary boundary) +let make_delimiter boundary = "\r\n" ^ make_dash_boundary boundary +let make_close_delimiter boundary = make_delimiter boundary ^ "--" +let close_delimiter boundary = string (make_close_delimiter boundary) (* NOTE: this parser terminate at the boundary, however it does not consume it. *) let discard_all_to_dash_boundary boundary = let check_boundary = let dash_boundary = make_dash_boundary boundary in let expected_len = String.length dash_boundary in - Unsafe.peek expected_len - (fun ba ~off ~len -> - let raw = Bigstringaf.substring ba ~off ~len in - String.equal raw dash_boundary) in + Unsafe.peek expected_len (fun ba ~off ~len -> + let raw = Bigstringaf.substring ba ~off ~len in + String.equal raw dash_boundary) + in fix @@ fun m -> - skip_while ((<>) '-') *> peek_char >>= function - | Some '-' -> - (check_boundary >>= function - | true -> return () - | false -> advance 1 *> m) + skip_while (( <> ) '-') *> peek_char >>= function + | Some '-' -> ( + check_boundary >>= function true -> return () | false -> advance 1 *> m) | Some _ -> advance 1 *> m (* impossible case? *) | None -> return () @@ -70,35 +57,32 @@ let discard_all_to_dash_boundary boundary = ; be able to handle padding ; added by message transports. *) -let transport_padding = skip_while (function '\x09' | '\x20' -> true | _ -> false) +let transport_padding = + skip_while (function '\x09' | '\x20' -> true | _ -> false) let discard_all_to_delimiter boundary = let check_delimiter = let delimiter = make_delimiter boundary in let expected_len = String.length delimiter in - Unsafe.peek expected_len - (fun ba ~off ~len -> - let raw = Bigstringaf.substring ba ~off ~len in - String.equal raw delimiter) in + Unsafe.peek expected_len (fun ba ~off ~len -> + let raw = Bigstringaf.substring ba ~off ~len in + String.equal raw delimiter) + in fix @@ fun m -> - skip_while ((<>) '\r') *> peek_char >>= function - | Some '\r' -> - (check_delimiter >>= function - | true -> return () - | false -> advance 1 *> m) + skip_while (( <> ) '\r') *> peek_char >>= function + | Some '\r' -> ( + check_delimiter >>= function true -> return () | false -> advance 1 *> m) | Some _ -> advance 1 *> m (* impossible case? *) | None -> return () let nothing_to_do = Fmt.kstrf fail "nothing to do" - let crlf = string "\r\n" let body_part body = Header.Decoder.header >>= fun header -> - ((crlf *> return `CRLF) <|> (return `Nothing)) - >>= (function - | `CRLF -> body header >>| Option.some - | `Nothing -> return None) + (crlf *> return `CRLF <|> return `Nothing >>= function + | `CRLF -> body header >>| Option.some + | `Nothing -> return None) >>| fun body -> (header, body) let encapsulation boundary body = @@ -116,7 +100,8 @@ let encapsulation boundary body = XXX(dinosaure): this parser consume the last CRLF which is NOT included in the ABNF. *) let preambule boundary = discard_all_to_dash_boundary boundary -let epilogue parent = match parent with +let epilogue parent = + match parent with | Some boundary -> discard_all_to_delimiter boundary | None -> skip_while (fun _ -> true) @@ -126,9 +111,8 @@ let multipart_body ?parent boundary body = *> transport_padding *> crlf *> body_part body - >>= fun x -> many (encapsulation boundary body) - >>= fun r -> ((close_delimiter boundary - *> transport_padding - *> option () (epilogue parent)) - <|> return ()) - *> return (x :: r) + >>= fun x -> + many (encapsulation boundary body) >>= fun r -> + (close_delimiter boundary *> transport_padding *> option () (epilogue parent) + <|> return ()) + *> return (x :: r) diff --git a/lib/unstructured.ml b/lib/unstructured.ml index a05dd8c..db90a88 100644 --- a/lib/unstructured.ml +++ b/lib/unstructured.ml @@ -4,13 +4,15 @@ type t = elt list let pp ppf t = let t = - List.fold_left (fun a -> function - | #Unstrctrd.elt as x -> x :: a - | _ -> a) [] t |> List.rev in + List.fold_left + (fun a -> function #Unstrctrd.elt as x -> x :: a | _ -> a) + [] t + |> List.rev + in match Unstrctrd.of_list t with | Ok l -> - let s = Unstrctrd.to_utf_8_string l in - Fmt.pf ppf "" s + let s = Unstrctrd.to_utf_8_string l in + Fmt.pf ppf "" s | Error _ -> Fmt.pf ppf "" module Decoder = struct @@ -33,32 +35,35 @@ module Encoder = struct type uchar = [ `Uchar of Uchar.t ] type ok_or_partial = [ `Ok | `Partial ] - let element : elt t = fun ppf -> function + let element : elt t = + fun ppf -> function | `CR -> string ppf "\r" | `LF -> string ppf "\n" | `Open Box -> eval ppf [ box ] | `Open (TBox n) -> eval ppf [ tbox n ] | `Open BBox -> eval ppf [ bbox ] | `Close -> eval ppf [ close ] - | `FWS wsp -> let ppf = eval ppf [ cut; new_line ] in string ppf (wsp :> string) + | `FWS wsp -> + let ppf = eval ppf [ cut; new_line ] in + string ppf (wsp :> string) | `OBS_NO_WS_CTL chr -> char ppf (chr :> char) | `WSP wsp -> eval ppf [ spaces (String.length (wsp :> string)) ] | `d0 -> char ppf '\000' | `Invalid_char _ -> string ppf "\xEF\xBF\xBD" | #uchar as uchar -> - let output = Stdlib.Buffer.create 4 in - let encoder = Uutf.encoder `UTF_8 (`Buffer output) in - (* XXX(dinosaure): [Uutf.encoder_dst <> `Manual]. It's safe. *) - let[@warning "-8"] `Ok : ok_or_partial = Uutf.encode encoder uchar in - let[@warning "-8"] `Ok : ok_or_partial = Uutf.encode encoder `End in - string ppf (Stdlib.Buffer.contents output) + let output = Stdlib.Buffer.create 4 in + let encoder = Uutf.encoder `UTF_8 (`Buffer output) in + (* XXX(dinosaure): [Uutf.encoder_dst <> `Manual]. It's safe. *) + let[@warning "-8"] (`Ok : ok_or_partial) = Uutf.encode encoder uchar in + let[@warning "-8"] (`Ok : ok_or_partial) = Uutf.encode encoder `End in + string ppf (Stdlib.Buffer.contents output) - let noop = (fun ppf () -> ppf), () - let unstructured : elt list t = - fun ppf lst -> list ~sep:noop element ppf lst + let noop = ((fun ppf () -> ppf), ()) + let unstructured : elt list t = fun ppf lst -> list ~sep:noop element ppf lst end -let of_string x = match Unstrctrd.of_string x with +let of_string x = + match Unstrctrd.of_string x with | Ok (_consumed, v) -> Ok v | Error (`Msg err) -> Error (`Msg err) diff --git a/test/dune b/test/dune index 6522fba..a8f19ee 100644 --- a/test/dune +++ b/test/dune @@ -35,35 +35,49 @@ (rule (alias runtest) - (deps (:rfc2045 rfc2045.exe)) - (action (run %{rfc2045} --color=always))) + (deps + (:rfc2045 rfc2045.exe)) + (action + (run %{rfc2045} --color=always))) (rule (alias runtest) - (deps (:rfc2047 rfc2047.exe)) - (action (run %{rfc2047} --color=always))) + (deps + (:rfc2047 rfc2047.exe)) + (action + (run %{rfc2047} --color=always))) (rule (alias runtest) - (deps (:rfc5322 rfc5322.exe)) - (action (run %{rfc5322} --color=always))) + (deps + (:rfc5322 rfc5322.exe)) + (action + (run %{rfc5322} --color=always))) (rule (alias runtest) - (deps (:date test_date.exe)) - (action (run %{date} --color=always))) + (deps + (:date test_date.exe)) + (action + (run %{date} --color=always))) (rule (alias runtest) - (deps (:message_id test_message_id.exe)) - (action (run %{message_id} --color=always))) + (deps + (:message_id test_message_id.exe)) + (action + (run %{message_id} --color=always))) (rule (alias runtest) - (deps (:mail test_mail.exe)) - (action (run %{mail} --color=always))) + (deps + (:mail test_mail.exe)) + (action + (run %{mail} --color=always))) (rule (alias runtest) - (deps (:hd test_hd.exe)) - (action (run %{hd} --color=always))) + (deps + (:hd test_hd.exe)) + (action + (run %{hd} --color=always))) diff --git a/test/rfc2045.ml b/test/rfc2045.ml index 56c5d4a..5068e8a 100644 --- a/test/rfc2045.ml +++ b/test/rfc2045.ml @@ -1,4 +1,4 @@ -let ( <.> ) f g = fun x -> f (g x) +let ( <.> ) f g x = f (g x) let parse_content_type x = let parser = @@ -11,12 +11,14 @@ let parse_content_type x = Unstrctrd.without_comments v >>| Unstrctrd.fold_fws >>| Unstrctrd.to_utf_8_string - >>= ( R.reword_error R.msg <.> Angstrom.parse_string - ~consume:Angstrom.Consume.Prefix - Mrmime.Content_type.Decoder.content ) in + >>= (R.reword_error R.msg + <.> Angstrom.parse_string ~consume:Angstrom.Consume.Prefix + Mrmime.Content_type.Decoder.content) + in match res with | Ok v -> return v - | Error (`Msg err) -> failf "Invalid Content-Type (%s)" err in + | Error (`Msg err) -> failf "Invalid Content-Type (%s)" err + in Angstrom.parse_string ~consume:Angstrom.Consume.Prefix parser (x ^ "\r\n") let content_type = @@ -35,7 +37,8 @@ let content_type_0 = Parameters.key "charset" >>= fun charset -> Parameters.value "us-ascii" >>= fun us_ascii -> Subtype.iana Type.text "plain" >>| fun subty -> - make Type.text subty Parameters.(add charset us_ascii empty) in + make Type.text subty Parameters.(add charset us_ascii empty) + in Rresult.R.get_ok value let content_type_1 = @@ -45,7 +48,8 @@ let content_type_1 = Parameters.key "charset" >>= fun charset -> Parameters.value "us-ascii" >>= fun us_ascii -> Subtype.iana Type.text "plain" >>| fun subty -> - make Type.text subty Parameters.(add charset us_ascii empty) in + make Type.text subty Parameters.(add charset us_ascii empty) + in Rresult.R.get_ok value let content_type_2 = @@ -55,12 +59,17 @@ let content_type_2 = Parameters.key "charset" >>= fun charset -> Parameters.value (Rosetta.encoding_to_string `ISO_8859_1) >>= fun latin1 -> Subtype.iana Type.text "plain" >>| fun subty -> - make Type.text subty Parameters.(add charset latin1 empty) in + make Type.text subty Parameters.(add charset latin1 empty) + in Rresult.R.get_ok value let () = Alcotest.run "rfc2045" - [ ( "content-type" - , [ make "text/plain; charset=us-ascii (Plain text)" content_type_0 - ; make "text/plain; charset=\"us-ascii\"" content_type_1 - ; make "text/plain; charset=ISO-8859-1" content_type_2 ] ) ] + [ + ( "content-type", + [ + make "text/plain; charset=us-ascii (Plain text)" content_type_0; + make "text/plain; charset=\"us-ascii\"" content_type_1; + make "text/plain; charset=ISO-8859-1" content_type_2; + ] ); + ] diff --git a/test/rfc2047.ml b/test/rfc2047.ml index 396bfcc..dfb3941 100644 --- a/test/rfc2047.ml +++ b/test/rfc2047.ml @@ -1,6 +1,5 @@ let parse_encoded_word x = - Angstrom.parse_string - ~consume:Angstrom.Consume.All + Angstrom.parse_string ~consume:Angstrom.Consume.All Mrmime.Encoded_word.Decoder.encoded_word x let encoded_word = @@ -20,39 +19,42 @@ let data = (Rresult.R.equal ~ok:String.equal ~error:(fun (`Msg _) (`Msg _) -> true)) let make raw (expect_charset, expect_encoding, expect_data) = - Alcotest.test_case raw `Quick - @@ fun () -> + Alcotest.test_case raw `Quick @@ fun () -> match parse_encoded_word raw with | Ok value -> Alcotest.(check charset) "charset" (Mrmime.Encoded_word.charset value) - expect_charset ; + expect_charset; Alcotest.(check encoding) "encoding" (Mrmime.Encoded_word.encoding value) - expect_encoding ; + expect_encoding; Alcotest.(check data) "data" (Mrmime.Encoded_word.data value) expect_data | Error _ -> Fmt.invalid_arg "Invalid encoded-word: %s." raw let () = Alcotest.run "rfc2047" - [ ( "encoded-word" - , [ make "=?US-ASCII?Q?Keith_Moore?=" - (`US_ASCII, Mrmime.Encoded_word.q, Ok "Keith Moore") - ; make "=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?=" - (`ISO_8859_1, Mrmime.Encoded_word.q, Ok "Keld Jørn Simonsen") - ; make "=?ISO-8859-1?Q?Andr=E9_?=" - (`ISO_8859_1, Mrmime.Encoded_word.q, Ok "André ") - ; make "=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=" - (`ISO_8859_1, Mrmime.Encoded_word.b, Ok "If you can read this yo") - ; make "=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=" - (`ISO_8859_2, Mrmime.Encoded_word.b, Ok "u understand the example.") - ; make "=?ISO-8859-1?Q?Olle_J=E4rnefors?=" - (`ISO_8859_1, Mrmime.Encoded_word.q, Ok "Olle Järnefors") - ; make "=?ISO-8859-1?Q?Patrik_F=E4ltstr=F6m?=" - (`ISO_8859_1, Mrmime.Encoded_word.q, Ok "Patrik Fältström") - ; make "=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?=" - ( `ISO_8859_8 - , Mrmime.Encoded_word.b - , Ok "םולש ןב ילטפנ" (* Il est un gentleman *) ) ] ) ] + [ + ( "encoded-word", + [ + make "=?US-ASCII?Q?Keith_Moore?=" + (`US_ASCII, Mrmime.Encoded_word.q, Ok "Keith Moore"); + make "=?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?=" + (`ISO_8859_1, Mrmime.Encoded_word.q, Ok "Keld Jørn Simonsen"); + make "=?ISO-8859-1?Q?Andr=E9_?=" + (`ISO_8859_1, Mrmime.Encoded_word.q, Ok "André "); + make "=?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?=" + (`ISO_8859_1, Mrmime.Encoded_word.b, Ok "If you can read this yo"); + make "=?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?=" + (`ISO_8859_2, Mrmime.Encoded_word.b, Ok "u understand the example."); + make "=?ISO-8859-1?Q?Olle_J=E4rnefors?=" + (`ISO_8859_1, Mrmime.Encoded_word.q, Ok "Olle Järnefors"); + make "=?ISO-8859-1?Q?Patrik_F=E4ltstr=F6m?=" + (`ISO_8859_1, Mrmime.Encoded_word.q, Ok "Patrik Fältström"); + make "=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?=" + ( `ISO_8859_8, + Mrmime.Encoded_word.b, + Ok "םולש ןב ילטפנ" (* Il est un gentleman *) ); + ] ); + ] diff --git a/test/rfc5322.ml b/test/rfc5322.ml index 5225e63..27cf2ef 100644 --- a/test/rfc5322.ml +++ b/test/rfc5322.ml @@ -1,31 +1,28 @@ let header_tests = [ -(* See RFC 5322 § Appendix A.1.1 *) -{|From: John Doe + (* See RFC 5322 § Appendix A.1.1 *) + {|From: John Doe To: Mary Smith Subject: Saying Hello Date: Fri, 21 Nov 1997 09:55:06 -0600 Message-ID: <1234@local.machine.example> |}; - -(* See RFC 5322 § Appendix A.1.2 *) -{|From: "Joe Q. Public" + (* See RFC 5322 § Appendix A.1.2 *) + {|From: "Joe Q. Public" To: Mary Smith , jdoe@example.org, Who? Cc: , "Giant; \"Big\" Box" Date: Tue, 1 Jul 2003 10:52:37 +0200 Message-ID: <5678.21-Nov-1997@example.com> |}; - -(* See RFC 5322 § Appendix A.1.3 *) -{|From: Pete + (* See RFC 5322 § Appendix A.1.3 *) + {|From: Pete To: A Group:Ed Jones ,joe@where.test,John ; Cc: Undisclosed recipients:; Date: Thu, 13 Feb 1969 23:32:54 -0330 Message-ID: |}; - -(* See RFC 5322 § Appendix A.2 *) -{|From: Mary Smith + (* See RFC 5322 § Appendix A.2 *) + {|From: Mary Smith To: John Doe Reply-To: "Mary Smith: Personal Account" Subject: Re: Saying Hello @@ -34,9 +31,8 @@ Message-ID: <3456@example.net> In-Reply-To: <1234@local.machine.example> References: <1234@local.machine.example> |}; - -(* See RFC 5322 § Appendix A.3 *) -{|Resent-From: Mary Smith + (* See RFC 5322 § Appendix A.3 *) + {|Resent-From: Mary Smith Resent-To: Jane Brown Resent-Date: Mon, 24 Nov 1997 14:22:01 -0800 Resent-Message-ID: <78910@example.net> @@ -46,9 +42,8 @@ Subject: Saying Hello Date: Fri, 21 Nov 1997 09:55:06 -0600 Message-ID: <1234@local.machine.example> |}; - -(* See RFC 5322 § Appendix A.4 *) -{|Received: from x.y.test + (* See RFC 5322 § Appendix A.4 *) + {|Received: from x.y.test by example.net via TCP with ESMTP @@ -61,9 +56,8 @@ Subject: Saying Hello Date: Fri, 21 Nov 1997 09:55:06 -0600 Message-ID: <1234@local.node.example> |}; - -(* See RFC 5322 § Appendix A.5 *) -{|From: Pete(A nice \) chap) + (* See RFC 5322 § Appendix A.5 *) + {|From: Pete(A nice \) chap) To:A Group(Some people) :Chris Jones , joe@example.org, @@ -77,24 +71,21 @@ Date: Thu, -0330 (Newfoundland Time) Message-ID: |}; - -(* See RFC 5322 § Appendix A.6.1 *) -{|From: Joe Q. Public + (* See RFC 5322 § Appendix A.6.1 *) + {|From: Joe Q. Public To: Mary Smith <@node.test:mary@example.net>, , jdoe@test . example Date: Tue, 1 Jul 2003 10:52:37 +0200 Message-ID: <5678.21-Nov-1997@example.com> |}; - -(* See RFC 5322 § Appendix A.6.2 *) -{|From: John Doe + (* See RFC 5322 § Appendix A.6.2 *) + {|From: John Doe To: Mary Smith Subject: Saying Hello Date: 21 Nov 97 09:55:06 GMT Message-ID: <1234@local.machine.example> |}; - -(* See RFC 5322 § Appendix A.6.3 *) -{|From : John Doe + (* See RFC 5322 § Appendix A.6.3 *) + {|From : John Doe To : Mary Smith @@ -102,24 +93,21 @@ Subject : Saying Hello Date : Fri, 21 Nov 1997 09(comment): 55 : 06 -0600 Message-ID : <1234 @ local(blah) .machine .example> |}; - -(* See RFC 822 § A.3.1 *) -{|Date: 26 Aug 76 14:29 EDT + (* See RFC 822 § A.3.1 *) + {|Date: 26 Aug 76 14:29 EDT From: Jones@Registry.Org Bcc: |}; - -(* See RFC 822 § A.3.2 *) -{|Date: 26 Aug 76 14:30 EDT + (* See RFC 822 § A.3.2 *) + {|Date: 26 Aug 76 14:30 EDT From: George Jones Sender: Secy@SHOST To: "Al Neuman"@Mad-Host, Sam.Irving@Other-Host Message-ID: |}; - -(* See RFC 822 § A.3.3 *) -{|Date : 27 Aug 76 09:32 PDT + (* See RFC 822 § A.3.3 *) + {|Date : 27 Aug 76 09:32 PDT From : Ken Davis Subject : Re: The Syntax in the RFC Sender : KSecy@Other-Host @@ -143,31 +131,27 @@ X-Special-action: This is a sample of user-defined field- preempted Message-ID: <4231.629.XYzi-What@Other-Host> |}; - -(* See RFC 2047 § 8 *) -{|From: =?US-ASCII?Q?Keith_Moore?= + (* See RFC 2047 § 8 *) + {|From: =?US-ASCII?Q?Keith_Moore?= Date : 27 Aug 76 09:32 PDT To: =?ISO-8859-1?Q?Keld_J=F8rn_Simonsen?= CC: =?ISO-8859-1?Q?Andr=E9?= Pirard Subject: =?ISO-8859-1?B?SWYgeW91IGNhbiByZWFkIHRoaXMgeW8=?= =?ISO-8859-2?B?dSB1bmRlcnN0YW5kIHRoZSBleGFtcGxlLg==?= |}; - -(* See RFC 2047 § 8 *) -{|From: =?ISO-8859-1?Q?Olle_J=E4rnefors?= + (* See RFC 2047 § 8 *) + {|From: =?ISO-8859-1?Q?Olle_J=E4rnefors?= To: ietf-822@dimacs.rutgers.edu, ojarnef@admin.kth.se Subject: Time for ISO 10646? |}; - -(* See RFC 2047 § 8 *) -{|To: Dave Crocker + (* See RFC 2047 § 8 *) + {|To: Dave Crocker Cc: ietf-822@dimacs.rutgers.edu, paf@comsol.se From: =?ISO-8859-1?Q?Patrik_F=E4ltstr=F6m?= Subject: Re: RFC-HDR care and feeding |}; - -(* See RFC 2047 § 8 *) -{|From: Nathaniel Borenstein + (* See RFC 2047 § 8 *) + {|From: Nathaniel Borenstein (=?iso-8859-8?b?7eXs+SDv4SDp7Oj08A==?=) To: Greg Vaudreuil , Ned Freed , Keith Moore @@ -178,15 +162,18 @@ Content-type: text/plain; charset=ISO-8859-1 ] let parse_header x = - match Angstrom.parse_string ~consume:Angstrom.Consume.Prefix Mrmime.Header.Decoder.header (x ^ "\r\n") with + match + Angstrom.parse_string ~consume:Angstrom.Consume.Prefix + Mrmime.Header.Decoder.header (x ^ "\r\n") + with | Ok header -> Fmt.pr "header: @[%a@].\n%!" Mrmime.Header.pp header | Error _ -> Fmt.failwith "Invalid header" let header_tests = let make idx input = Alcotest.test_case (Fmt.strf "header %d" idx) `Quick @@ fun () -> - Alcotest.(check pass) input (parse_header input) () in + Alcotest.(check pass) input (parse_header input) () + in List.mapi make header_tests -let () = - Alcotest.run "rfc5322" [ ("header", header_tests) ] +let () = Alcotest.run "rfc5322" [ ("header", header_tests) ] diff --git a/test/test_date.ml b/test/test_date.ml index 7f2e3a5..1dd44fa 100644 --- a/test/test_date.ml +++ b/test/test_date.ml @@ -1,5 +1,4 @@ -let ( <.> ) f g = fun x -> f (g x) - +let ( <.> ) f g x = f (g x) let date = Alcotest.testable Mrmime.Date.pp Mrmime.Date.equal let parse_date x = @@ -12,12 +11,12 @@ let parse_date x = Unstrctrd.without_comments v >>| Unstrctrd.fold_fws >>| Unstrctrd.to_utf_8_string - >>= ( R.reword_error R.msg <.> Angstrom.parse_string - ~consume:Angstrom.Consume.All - Mrmime.Date.Decoder.date_time ) in - match res with - | Ok v -> return v - | Error _ -> fail "Invalid date" in + >>= (R.reword_error R.msg + <.> Angstrom.parse_string ~consume:Angstrom.Consume.All + Mrmime.Date.Decoder.date_time) + in + match res with Ok v -> return v | Error _ -> fail "Invalid date" + in Angstrom.parse_string ~consume:Angstrom.Consume.All parser (x ^ "\r\n") let make raw expect = @@ -27,46 +26,76 @@ let make raw expect = | Error err -> Fmt.invalid_arg "Invalid date value (%s): %s." err raw let tests = - [ "Fri, 21 Nov 1997 09:55:06 -0600", - Mrmime.Date.{ day = Some Day.Fri - ; date = (21, Month.Nov, 1997) - ; time = (9, 55, Some 06) - ; zone = Zone.TZ (-06, 00) } - ; "Tue, 1 Jul 2003 10:52:37 +0200", - Mrmime.Date.{ day = Some Day.Tue - ; date = (1, Month.Jul, 2003) - ; time = (10, 52, Some 37) - ; zone = Zone.TZ (02, 00) } - ; "Thu, 13 Feb 1969 23:32:54 -0330", - Mrmime.Date.{ day = Some Day.Thu - ; date = (13, Month.Feb, 1969) - ; time = (23, 32, Some 54) - ; zone = Zone.TZ (-03, 30) } - ; "Mon, 24 Nov 1997 14:22:01 -0800", - Mrmime.Date.{ day = Some Day.Mon - ; date = (24, Month.Nov, 1997) - ; time = (14, 22, Some 01) - ; zone = Zone.TZ (-08, 00) } - ; "Thu,\r\n 13\r\n Feb\r\n 1969\r\n 23:32\r\n -0330 (Newfoundland Time)", - Mrmime.Date.{ day = Some Day.Thu - ; date = (13, Month.Feb, 1969) - ; time = (23, 32, None) - ; zone = Zone.TZ (-03, 30) } - ; "21 Nov 97 09:55:06 GMT", - Mrmime.Date.{ day = None - ; date = (21, Month.Nov, 97) - ; time = (09, 55, Some 06) - ; zone = Zone.GMT } - ; "Fri, 21 Nov 1997 09(comment): 55 : 06 -0600", - Mrmime.Date.{ day = Some Day.Fri - ; date = (21, Month.Nov, 1997) - ; time = (09, 55, Some 06) - ; zone = Zone.TZ (-06, 00) } - ; "Fri, 21 Nov 1990 00:00:00.1234 -0000", - Mrmime.Date.{ day = Some Day.Fri - ; date = (21, Month.Nov, 1990) - ; time = (0, 0, Some 0) - ; zone = Zone.TZ (0, 0) } + [ + ( "Fri, 21 Nov 1997 09:55:06 -0600", + Mrmime.Date. + { + day = Some Day.Fri; + date = (21, Month.Nov, 1997); + time = (9, 55, Some 06); + zone = Zone.TZ (-06, 00); + } ); + ( "Tue, 1 Jul 2003 10:52:37 +0200", + Mrmime.Date. + { + day = Some Day.Tue; + date = (1, Month.Jul, 2003); + time = (10, 52, Some 37); + zone = Zone.TZ (02, 00); + } ); + ( "Thu, 13 Feb 1969 23:32:54 -0330", + Mrmime.Date. + { + day = Some Day.Thu; + date = (13, Month.Feb, 1969); + time = (23, 32, Some 54); + zone = Zone.TZ (-03, 30); + } ); + ( "Mon, 24 Nov 1997 14:22:01 -0800", + Mrmime.Date. + { + day = Some Day.Mon; + date = (24, Month.Nov, 1997); + time = (14, 22, Some 01); + zone = Zone.TZ (-08, 00); + } ); + ( "Thu,\r\n\ + \ 13\r\n\ + \ Feb\r\n\ + \ 1969\r\n\ + \ 23:32\r\n\ + \ -0330 (Newfoundland Time)", + Mrmime.Date. + { + day = Some Day.Thu; + date = (13, Month.Feb, 1969); + time = (23, 32, None); + zone = Zone.TZ (-03, 30); + } ); + ( "21 Nov 97 09:55:06 GMT", + Mrmime.Date. + { + day = None; + date = (21, Month.Nov, 97); + time = (09, 55, Some 06); + zone = Zone.GMT; + } ); + ( "Fri, 21 Nov 1997 09(comment): 55 : 06 -0600", + Mrmime.Date. + { + day = Some Day.Fri; + date = (21, Month.Nov, 1997); + time = (09, 55, Some 06); + zone = Zone.TZ (-06, 00); + } ); + ( "Fri, 21 Nov 1990 00:00:00.1234 -0000", + Mrmime.Date. + { + day = Some Day.Fri; + date = (21, Month.Nov, 1990); + time = (0, 0, Some 0); + zone = Zone.TZ (0, 0); + } ); ] let () = diff --git a/test/test_hd.ml b/test/test_hd.ml index 1146303..4f3e87d 100644 --- a/test/test_hd.ml +++ b/test/test_hd.ml @@ -18,59 +18,69 @@ let p = |> Map.add content_encoding unstructured let test_000 = -{|Date: 26 Aug 76 14:29 EDT + {|Date: 26 Aug 76 14:29 EDT From: Jones@Registry.Org Bcc: |} -module Map = Map.Make(Field_name) +module Map = Map.Make (Field_name) let to_unstrctrd (unstructured : Unstructured.t) = - let fold acc = function - | #Unstrctrd.elt as elt -> elt :: acc - | _ -> acc in + let fold acc = function #Unstrctrd.elt as elt -> elt :: acc | _ -> acc in List.fold_left fold [] unstructured - |> List.rev |> Unstrctrd.of_list |> Rresult.R.get_ok + |> List.rev + |> Unstrctrd.of_list + |> Rresult.R.get_ok let add k v m = - try let vs = Map.find k m in Map.add k (v :: vs) m + try + let vs = Map.find k m in + Map.add k (v :: vs) m with Not_found -> Map.add k [ v ] m let parse str = let tmp = Bigstringaf.create 0x1000 in let pos = ref 0 in let decoder = Hd.decoder ~p tmp in - let rec go acc = match Hd.decode decoder with + let rec go acc = + match Hd.decode decoder with | `End prelude -> - Alcotest.(check string) "prelude" prelude "" ; acc - | `Field field -> - let Field.Field (field_name, w, v) = Location.prj field in - ( match w with + Alcotest.(check string) "prelude" prelude ""; + acc + | `Field field -> ( + let (Field.Field (field_name, w, v)) = Location.prj field in + match w with | Field.Unstructured -> - let v = Unstrctrd.(to_utf_8_string (fold_fws (to_unstrctrd v))) in - go (add field_name v acc) - | _ -> assert false ) + let v = Unstrctrd.(to_utf_8_string (fold_fws (to_unstrctrd v))) in + go (add field_name v acc) + | _ -> assert false) | `Malformed err -> Alcotest.failf "Hd.decode: %s" err - | `Await -> - let len = min (String.length str - !pos) 0x100 in - match Hd.src decoder str !pos len with - | Ok () -> pos := !pos + len ; go acc - | Error (`Msg err) -> Alcotest.failf "Hd.src: %s" err in + | `Await -> ( + let len = min (String.length str - !pos) 0x100 in + match Hd.src decoder str !pos len with + | Ok () -> + pos := !pos + len; + go acc + | Error (`Msg err) -> Alcotest.failf "Hd.src: %s" err) + in go Map.empty let test_000 = Alcotest.test_case "header-000" `Quick @@ fun () -> let fields = parse test_000 in - Alcotest.(check (list string)) "Date" (Map.find Field_name.date fields) - [ " 26 Aug 76 14:29 EDT" ] ; - Alcotest.(check (list string)) "From" (Map.find Field_name.from fields) - [ " Jones@Registry.Org" ] ; - Alcotest.(check (list string)) "Bcc" (Map.find Field_name.bcc fields) - [ "" ] + Alcotest.(check (list string)) + "Date" + (Map.find Field_name.date fields) + [ " 26 Aug 76 14:29 EDT" ]; + Alcotest.(check (list string)) + "From" + (Map.find Field_name.from fields) + [ " Jones@Registry.Org" ]; + Alcotest.(check (list string)) "Bcc" (Map.find Field_name.bcc fields) [ "" ] let test_001 = -{|From : John Doe + {|From : John Doe To : Mary Smith @@ -83,16 +93,25 @@ Message-ID : <1234 @ local(blah) .machine .example> let test_001 = Alcotest.test_case "header-000" `Quick @@ fun () -> let fields = parse test_001 in - Alcotest.(check (list string)) "From" (Map.find Field_name.from fields) - [ " John Doe " ] ; - Alcotest.(check (list string)) "To" (Map.find (Field_name.v "To") fields) - [ " Mary Smith " ] ; - Alcotest.(check (list string)) "Subhect" (Map.find Field_name.subject fields) - [ " Saying Hello" ] ; - Alcotest.(check (list string)) "Date" (Map.find Field_name.date fields) - [ " Fri, 21 Nov 1997 09(comment): 55 : 06 -0600" ] ; - Alcotest.(check (list string)) "Message-ID" (Map.find Field_name.message_id fields) + Alcotest.(check (list string)) + "From" + (Map.find Field_name.from fields) + [ " John Doe " ]; + Alcotest.(check (list string)) + "To" + (Map.find (Field_name.v "To") fields) + [ " Mary Smith " ]; + Alcotest.(check (list string)) + "Subhect" + (Map.find Field_name.subject fields) + [ " Saying Hello" ]; + Alcotest.(check (list string)) + "Date" + (Map.find Field_name.date fields) + [ " Fri, 21 Nov 1997 09(comment): 55 : 06 -0600" ]; + Alcotest.(check (list string)) + "Message-ID" + (Map.find Field_name.message_id fields) [ " <1234 @ local(blah) .machine .example>" ] -let () = - Alcotest.run "hd" [ ("header", [ test_000; test_001 ]) ] +let () = Alcotest.run "hd" [ ("header", [ test_000; test_001 ]) ] diff --git a/test/test_mail.ml b/test/test_mail.ml index 87c26a5..9dc9458 100644 --- a/test/test_mail.ml +++ b/test/test_mail.ml @@ -2,52 +2,90 @@ let () = Printexc.record_backtrace true let stream_of_string s = let once = ref false in - (fun () -> if !once then None else ( once := true ; Some (s, 0, String.length s))) + fun () -> + if !once then None + else ( + once := true; + Some (s, 0, String.length s)) -let stream_of_random ?(chunk= 128) len = +let stream_of_random ?(chunk = 128) len = let ic = open_in "/dev/urandom" in let ln = ref 0 in let closed = ref false in let rs = Bytes.create chunk in let go () = let len = min (len - !ln) chunk in - if len == 0 then ( if not !closed then close_in ic ; closed := true ; None ) - else ( really_input ic rs 0 len - ; ln := !ln + len - ; Some (Bytes.unsafe_to_string rs, 0, len) ) in + if len == 0 then ( + if not !closed then close_in ic; + closed := true; + None) + else ( + really_input ic rs 0 len; + ln := !ln + len; + Some (Bytes.unsafe_to_string rs, 0, len)) + in go let stream_to_string s = let b = Buffer.create 4096 in - let rec go () = match s () with + let rec go () = + match s () with | Some (buf, off, len) -> - Buffer.add_substring b buf off len ; go () - | None -> Buffer.contents b in + Buffer.add_substring b buf off len; + go () + | None -> Buffer.contents b + in go () let example0 = let open Mrmime in - let john = let open Mailbox in Local.[ w "john" ] @ Domain.(domain, [ a "gmail"; a "com" ]) in - let thomas = let open Mailbox in Local.[ w "thomas" ] @ Domain.(domain, [ a "gazagnaire"; a "com" ]) in - let anil = let open Mailbox in Local.[ w "anil" ] @ Domain.(domain, [ a "recoil"; a "org" ]) in + let john = + let open Mailbox in + Local.[ w "john" ] @ Domain.(domain, [ a "gmail"; a "com" ]) + in + let thomas = + let open Mailbox in + Local.[ w "thomas" ] @ Domain.(domain, [ a "gazagnaire"; a "com" ]) + in + let anil = + let open Mailbox in + Local.[ w "anil" ] @ Domain.(domain, [ a "recoil"; a "org" ]) + in let header0 = let content0 = let open Content_type in - make `Application (Subtype.v `Application "pdf") Parameters.(of_list [ k "filename", v "prg.exe" ]) in - Header.of_list Field.[ Field (Field_name.content_type, Content, content0) - ; Field (Field_name.content_encoding, Encoding, `Base64) ] in + make `Application + (Subtype.v `Application "pdf") + Parameters.(of_list [ (k "filename", v "prg.exe") ]) + in + Header.of_list + Field. + [ + Field (Field_name.content_type, Content, content0); + Field (Field_name.content_encoding, Encoding, `Base64); + ] + in let header1 = let content1 = let open Content_type in - make `Text (Subtype.v `Text "plain") Parameters.(of_list [ k "charset", v "utf-8" ]) in - Header.of_list Field.[ Field (Field_name.content_type, Content, content1) - ; Field (Field_name.content_encoding, Encoding, `Quoted_printable) ] in + make `Text + (Subtype.v `Text "plain") + Parameters.(of_list [ (k "charset", v "utf-8") ]) + in + Header.of_list + Field. + [ + Field (Field_name.content_type, Content, content1); + Field (Field_name.content_encoding, Encoding, `Quoted_printable); + ] + in let subject = let open Unstructured.Craft in - compile [ v "First"; sp 1; v "email" ] in + compile [ v "First"; sp 1; v "email" ] + in let now = Date.of_ptime ~zone:Date.Zone.GMT (Ptime_clock.now ()) in let part0 = Mt.part ~header:header0 (stream_of_random 4096) in @@ -55,109 +93,163 @@ let example0 = let multipart = Mt.multipart ~rng:Mt.rng [ part0; part1 ] in let header = - Field.(Field (Field_name.sender, Mailbox, john)) - :: Field.(Field (Field_name.v "To", Addresses, Address.[ mailbox thomas; mailbox anil ])) - :: Field.(Field (Field_name.subject, Unstructured, subject)) - :: Field.(Field (Field_name.date, Date, now)) - :: [] in + [ + Field.(Field (Field_name.sender, Mailbox, john)); + Field.( + Field + ( Field_name.v "To", + Addresses, + Address.[ mailbox thomas; mailbox anil ] )); + Field.(Field (Field_name.subject, Unstructured, subject)); + Field.(Field (Field_name.date, Date, now)); + ] + in Mt.make (Header.of_list header) Mt.multi multipart let example1 = let open Mrmime in - let john = let open Mailbox in Local.[ w "john" ] @ Domain.(domain, [ a "gmail"; a "com" ]) in - let thomas = let open Mailbox in Local.[ w "thomas" ] @ Domain.(domain, [ a "gazagnaire"; a "org" ]) in - let anil = let open Mailbox in Local.[ w "anil" ] @ Domain.(domain, [ a "recoil"; a "org" ]) in - let hannes = let open Mailbox in Local.[ w "hannes" ] @ Domain.(domain, [ a "mehnert"; a "org" ]) in - let gemma = let open Mailbox in Local.[ w "gemma"; w "t"; w "gordon" ] @ Domain.(domain, [ a "gmail"; a "com" ]) in + let john = + let open Mailbox in + Local.[ w "john" ] @ Domain.(domain, [ a "gmail"; a "com" ]) + in + let thomas = + let open Mailbox in + Local.[ w "thomas" ] @ Domain.(domain, [ a "gazagnaire"; a "org" ]) + in + let anil = + let open Mailbox in + Local.[ w "anil" ] @ Domain.(domain, [ a "recoil"; a "org" ]) + in + let hannes = + let open Mailbox in + Local.[ w "hannes" ] @ Domain.(domain, [ a "mehnert"; a "org" ]) + in + let gemma = + let open Mailbox in + Local.[ w "gemma"; w "t"; w "gordon" ] + @ Domain.(domain, [ a "gmail"; a "com" ]) + in let header0 = let content0 = let open Content_type in - make `Text (Subtype.v `Text "plain") Parameters.(of_list [ k "charset", v "utf-8" ]) in - Header.of_list Field.[ Field (Field_name.content_type, Content, content0) ] in + make `Text + (Subtype.v `Text "plain") + Parameters.(of_list [ (k "charset", v "utf-8") ]) + in + Header.of_list Field.[ Field (Field_name.content_type, Content, content0) ] + in let subject = let open Unstructured.Craft in - compile [ v "Second"; sp 1; v "email" ] in + compile [ v "Second"; sp 1; v "email" ] + in let now = Date.of_ptime ~zone:Date.Zone.GMT (Ptime_clock.now ()) in let part = Mt.part ~header:header0 (stream_of_string "Hello World!") in let header = - Field.(Field (Field_name.sender, Mailbox, john)) - :: Field.(Field (Field_name.v "To", Addresses, Address.[ mailbox thomas; mailbox anil; mailbox hannes; mailbox gemma ])) - :: Field.(Field (Field_name.subject, Unstructured, subject)) - :: Field.(Field (Field_name.date, Date, now)) - :: [] in + [ + Field.(Field (Field_name.sender, Mailbox, john)); + Field.( + Field + ( Field_name.v "To", + Addresses, + Address. + [ mailbox thomas; mailbox anil; mailbox hannes; mailbox gemma ] )); + Field.(Field (Field_name.subject, Unstructured, subject)); + Field.(Field (Field_name.date, Date, now)); + ] + in Mt.make (Header.of_list header) Mt.simple part let test0 () = Alcotest.test_case "example 0" `Quick @@ fun () -> let res0 = stream_to_string (Mrmime.Mt.to_stream example0) in - match Angstrom.parse_string ~consume:Angstrom.Consume.All Mrmime.Mail.mail res0 with + match + Angstrom.parse_string ~consume:Angstrom.Consume.All Mrmime.Mail.mail res0 + with | Ok _ -> Fmt.epr "%s%!" res0 | Error _ -> Fmt.invalid_arg "Generate unparsable email" let test1 () = Alcotest.test_case "example 1" `Quick @@ fun () -> let res0 = stream_to_string (Mrmime.Mt.to_stream example1) in - match Angstrom.parse_string ~consume:Angstrom.Consume.All Mrmime.Mail.mail res0 with + match + Angstrom.parse_string ~consume:Angstrom.Consume.All Mrmime.Mail.mail res0 + with | Ok mail -> - Fmt.epr "%s%!" res0 ; - let gemma_exists (header, _) = - let open Mrmime in - let gemma = let open Mailbox in Local.[ w "gemma"; w "t"; w "gordon" ] @ Domain.(domain, [ a "gmail"; a "com" ]) in - match Header.assoc (Field_name.v "To") header with - | Field.Field (_, Field.Addresses, v) :: _ -> - if List.exists Address.(equal (mailbox gemma)) v - then () else Fmt.invalid_arg "Gemma does not exist" - | _ -> Fmt.invalid_arg "Field \"To\" does not exist" in - gemma_exists mail + Fmt.epr "%s%!" res0; + let gemma_exists (header, _) = + let open Mrmime in + let gemma = + let open Mailbox in + Local.[ w "gemma"; w "t"; w "gordon" ] + @ Domain.(domain, [ a "gmail"; a "com" ]) + in + match Header.assoc (Field_name.v "To") header with + | Field.Field (_, Field.Addresses, v) :: _ -> + if List.exists Address.(equal (mailbox gemma)) v then () + else Fmt.invalid_arg "Gemma does not exist" + | _ -> Fmt.invalid_arg "Field \"To\" does not exist" + in + gemma_exists mail | Error _ -> Fmt.invalid_arg "Generate unparsable email" -let subject = "Something larger than 80 columns to see where prettym split contents.\ - A large Subject should be split!" +let subject = + "Something larger than 80 columns to see where prettym split contents.A \ + large Subject should be split!" let example2 = let open Mrmime in let _, subject = Unstrctrd.safely_decode subject in - let header = [ Field.(Field (Field_name.subject, Unstructured, (subject :> Unstructured.elt list))) ] in + let header = + [ + Field.( + Field + (Field_name.subject, Unstructured, (subject :> Unstructured.elt list))); + ] + in let part = Mt.part (stream_of_string "Hello World!") in Mt.make (Header.of_list header) Mt.simple part -let to_unstrctrd acc = function - | #Unstrctrd.elt as elt -> elt :: acc - | _ -> acc +let to_unstrctrd acc = function #Unstrctrd.elt as elt -> elt :: acc | _ -> acc let to_unstrctrd unstrctrd = - match List.fold_left to_unstrctrd [] unstrctrd |> List.rev |> Unstrctrd.of_list with - | Ok v -> v | Error (`Msg err) -> failwith err + match + List.fold_left to_unstrctrd [] unstrctrd |> List.rev |> Unstrctrd.of_list + with + | Ok v -> v + | Error (`Msg err) -> failwith err let remove_fws (unstrctrd : Unstrctrd.t) = - let fold acc = function - | `FWS _ -> acc - | x -> x :: acc in - match List.fold_left fold [] (unstrctrd :> Unstrctrd.elt list) |> List.rev |> Unstrctrd.of_list with - | Ok v -> v | Error (`Msg err) -> failwith err + let fold acc = function `FWS _ -> acc | x -> x :: acc in + match + List.fold_left fold [] (unstrctrd :> Unstrctrd.elt list) + |> List.rev + |> Unstrctrd.of_list + with + | Ok v -> v + | Error (`Msg err) -> failwith err let test2 () = Alcotest.test_case "large subject" `Quick @@ fun () -> let res0 = stream_to_string (Mrmime.Mt.to_stream example2) in - match Angstrom.parse_string ~consume:Angstrom.Consume.All Mrmime.Mail.mail res0 with - | Ok (header, _) -> - let open Mrmime in - ( match Header.assoc Field_name.subject header with + match + Angstrom.parse_string ~consume:Angstrom.Consume.All Mrmime.Mail.mail res0 + with + | Ok (header, _) -> ( + let open Mrmime in + match Header.assoc Field_name.subject header with | Field.Field (_, Field.Unstructured, v) :: _ -> - let unstrctrd = to_unstrctrd v in - let unstrctrd = remove_fws unstrctrd in - let unstrctrd = Unstrctrd.to_utf_8_string unstrctrd in - let unstrctrd = String.trim unstrctrd in - Alcotest.(check string) "Same subject" unstrctrd subject - | _ -> Fmt.invalid_arg "Field \"Subject\" does not exist" ) + let unstrctrd = to_unstrctrd v in + let unstrctrd = remove_fws unstrctrd in + let unstrctrd = Unstrctrd.to_utf_8_string unstrctrd in + let unstrctrd = String.trim unstrctrd in + Alcotest.(check string) "Same subject" unstrctrd subject + | _ -> Fmt.invalid_arg "Field \"Subject\" does not exist") | Error _ -> Fmt.invalid_arg "Generate unparsable email" -let () = - Alcotest.run "mail" - [ "example", [ test0 (); test1 (); test2 (); ] ] +let () = Alcotest.run "mail" [ ("example", [ test0 (); test1 (); test2 () ]) ] diff --git a/test/test_message_id.ml b/test/test_message_id.ml index 3f65e09..5f504a7 100644 --- a/test/test_message_id.ml +++ b/test/test_message_id.ml @@ -1,7 +1,7 @@ let parse_msg_id x = - Angstrom.parse_string - ~consume:Angstrom.Consume.All + Angstrom.parse_string ~consume:Angstrom.Consume.All Mrmime.MessageID.Decoder.message_id x + let msg_id = Alcotest.testable Mrmime.MessageID.pp Mrmime.MessageID.equal let make raw expect = @@ -11,30 +11,48 @@ let make raw expect = | Error _ -> Fmt.invalid_arg "Invalid msg-id value: %s." raw let tests : (string * Mrmime.MessageID.t) list = - [ "<1234@local.machine.example>", ([ `Atom "1234" ], `Domain [ "local"; "machine"; "example" ]) - ; "<5678.21-Nov-1997@example.com>", ([ `Atom "5678"; `Atom "21-Nov-1997" ], `Domain [ "example"; "com" ]) - ; "", ([ `Atom "testabcd"; `Atom "1234" ], `Domain [ "silly"; "example" ]) - ; "<3456@example.net>", ([ `Atom "3456" ], `Domain [ "example"; "net" ]) - ; "", ([ `Atom "abcd"; `Atom "1234" ], `Domain [ "local"; "machine"; "tld" ]) - ; "<78910@example.net>", ([ `Atom "78910" ], `Domain [ "example"; "net" ]) - ; " ", ([ `Atom "testabcd"; `Atom "1234" ], `Domain [ "silly"; "test" ]) - ; "<1234 @ local(blah) .machine .example>", ([ `Atom "1234" ], `Domain [ "local"; "machine"; "example" ]) - ; "<089e01493ca6f216ca04fafe7e67@google.com>", ([ `Atom "089e01493ca6f216ca04fafe7e67" ], `Domain [ "google"; "com" ]) - ; "", ([ `Atom "CAL4csrQ8JPJ+7MMrzn6wOTC8rPxOTdLoUnQz+MPDCHTuebDTOA" ], `Domain [ "mail"; "gmail"; "com" ]) - ; "", ([ `Atom "mirage/irmin/pull/378/c259513470" ], `Domain [ "github"; "com" ]) + [ + ( "<1234@local.machine.example>", + ([ `Atom "1234" ], `Domain [ "local"; "machine"; "example" ]) ); + ( "<5678.21-Nov-1997@example.com>", + ([ `Atom "5678"; `Atom "21-Nov-1997" ], `Domain [ "example"; "com" ]) ); + ( "", + ([ `Atom "testabcd"; `Atom "1234" ], `Domain [ "silly"; "example" ]) ); + ("<3456@example.net>", ([ `Atom "3456" ], `Domain [ "example"; "net" ])); + ( "", + ([ `Atom "abcd"; `Atom "1234" ], `Domain [ "local"; "machine"; "tld" ]) ); + ("<78910@example.net>", ([ `Atom "78910" ], `Domain [ "example"; "net" ])); + ( " ", + ([ `Atom "testabcd"; `Atom "1234" ], `Domain [ "silly"; "test" ]) ); + ( "<1234 @ local(blah) .machine .example>", + ([ `Atom "1234" ], `Domain [ "local"; "machine"; "example" ]) ); + ( "<089e01493ca6f216ca04fafe7e67@google.com>", + ([ `Atom "089e01493ca6f216ca04fafe7e67" ], `Domain [ "google"; "com" ]) ); + ( "", + ( [ `Atom "CAL4csrQ8JPJ+7MMrzn6wOTC8rPxOTdLoUnQz+MPDCHTuebDTOA" ], + `Domain [ "mail"; "gmail"; "com" ] ) ); + ( "", + ([ `Atom "mirage/irmin/pull/378/c259513470" ], `Domain [ "github"; "com" ]) + ); ] let make_output v expect = - Alcotest.test_case (Fmt.to_to_string Mrmime.MessageID.pp v) `Quick @@ fun () -> + Alcotest.test_case (Fmt.to_to_string Mrmime.MessageID.pp v) `Quick + @@ fun () -> let res = Prettym.to_string Mrmime.MessageID.Encoder.message_id v in Alcotest.(check string) "result" res expect let tests_caml : (Mrmime.MessageID.t * string) list = let open Mrmime in - [ (Mailbox.Local.(v [ w "FE47A9B" ]), MessageID.Domain.(v domain [ a "gmail"; a "com" ])), - "" ] + [ + ( ( Mailbox.Local.(v [ w "FE47A9B" ]), + MessageID.Domain.(v domain [ a "gmail"; a "com" ]) ), + "" ); + ] let () = Alcotest.run "msg-id" - [ "valid msg-id", List.map (fun (raw, expect) -> make raw expect) tests - ; "output", List.map (fun (v, expect) -> make_output v expect) tests_caml ] + [ + ("valid msg-id", List.map (fun (raw, expect) -> make raw expect) tests); + ("output", List.map (fun (v, expect) -> make_output v expect) tests_caml); + ]