package mrmime

  1. Overview
  2. Docs

Source file content_encoding.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
type t =
  [ `Bit7
  | `Bit8
  | `Binary
  | `Quoted_printable
  | `Base64
  | `Ietf_token of string
  | `X_token of string ]

let pp ppf = function
  | `Bit7 -> Format.pp_print_string ppf "7bit"
  | `Bit8 -> Format.pp_print_string ppf "8bit"
  | `Binary -> Format.pp_print_string ppf "binary"
  | `Quoted_printable -> Format.pp_print_string ppf "quoted-printable"
  | `Base64 -> Format.pp_print_string ppf "base64"
  | `Ietf_token token -> Format.fprintf ppf "ietf:%s" token
  | `X_token token -> Format.fprintf ppf "x:%s" token

let default = `Bit7
let bit8 = `Bit8
let bit7 = `Bit7
let binary = `Binary
let quoted_printable = `Quoted_printable
let base64 = `Base64
let error_msgf fmt = Format.kasprintf (fun msg -> Error (`Msg msg)) fmt

let of_string = function
  | "7bit" -> Ok `Bit7
  | "8bit" -> Ok `Bit8
  | "binary" -> Ok `Binary
  | "quoted-printable" -> Ok `Quoted_printable
  | "base64" -> Ok `Base64
  | x -> error_msgf "Invalid MIME encoding: %s" x
(* TODO:
   - let the user to craft an extension token.
   - check IETF database *)

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))
  | _, _ -> false

module Decoder = struct
  open Angstrom

  let invalid_token token = Format.kasprintf 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

  (* From RFC 2045

          tspecials :=  "(" / ")" / "<" / ">" / "@" /
                        "," / ";" / ":" / "\" / <">
                        "/" / "[" / "]" / "?" / "="
                        ; Must be in quoted-string,
                        ; to use within parameter values

        Note that the definition of "tspecials" is the same as the RFC 822
        definition of "specials" with the addition of the three characters
        "/", "?", and "=", and the removal of ".".
  *)
  let is_tspecials = function
    | '(' | ')' | '<' | '>' | '@' | ',' | ';' | ':' | '\\' | '"' | '/' | '['
    | ']' | '?' | '=' ->
        true
    | _ -> false

  let is_ctl = function '\000' .. '\031' | '\127' -> true | _ -> false
  let is_space = ( = ) ' '

  (* From RFC 2045

          token := 1*<any (US-ASCII) CHAR except SPACE, CTLs,
                      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 token = take_while1 is_token

  (* From RFC 2045

          ietf-token := <An extension token defined by a
                            standards-track RFC and registered
                            with IANA.>

     XXX(dinosaure): we don't check at this time if IETF token exists.
  *)
  let ietf_token = token

  (* From RFC 2045

          x-token := <The two characters "X-" or "x-" followed, with
                         no intervening white space, by any token>
  *)
  let x_token =
    satisfy (function 'x' | 'X' -> true | _ -> false) *> char '-' *> token

  (* From RFC 2045

          extension-token := ietf-token / x-token
  *)
  let extension_token =
    peek_char >>= function
    | Some 'X' | Some 'x' -> x_token >>| fun v -> `X_token v
    | _ -> ietf_token >>| fun v -> `Ietf_token v

  (* From RFC 2045

          mechanism := "7bit" / "8bit" / "binary" /
                       "quoted-printable" / "base64" /
                       ietf-token / x-token

        These values are not case sensitive -- Base64 and BASE64 and bAsE64
        are all equivalent.  An encoding type of 7BIT requires that the body
        is already in a 7bit mail-ready representation.  This is the default
        value -- that is, "Content-Transfer-Encoding: 7BIT" is assumed if the
        Content-Transfer-Encoding header field is not present.
  *)
  let mechanism =
    skip_while is_space *> token >>= fun s ->
    (* XXX(dinosaure): lowercase_*ascii* is fine, not utf8 in this part. *)
    match String.lowercase_ascii s with
    | "7bit" -> return `Bit7
    | "8bit" -> return `Bit8
    | "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)
end

module Encoder = struct
  open Prettym

  let mechanism ppf = function
    | `Bit7 -> string ppf "7bit"
    | `Bit8 -> string ppf "8bit"
    | `Binary -> string ppf "binary"
    | `Quoted_printable -> string ppf "quoted-printable"
    | `Base64 -> string ppf "base64"
    | `Ietf_token x -> string ppf x
    | `X_token x -> eval ppf [ string $ "X-"; !!string ] x
end
OCaml

Innovation. Community. Security.