package multipart_form

  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
type t =
  [ `Bit7
  | `Bit8
  | `Binary
  | `Quoted_printable
  | `Base64
  | `Ietf_token of string
  | `X_token of string ]

let pp ppf = function
  | `Bit7 -> Fmt.string ppf "7bit"
  | `Bit8 -> Fmt.string ppf "8bit"
  | `Binary -> Fmt.string ppf "binary"
  | `Quoted_printable -> Fmt.string ppf "quoted-printable"
  | `Base64 -> Fmt.string ppf "base64"
  | `Ietf_token token -> Fmt.pf ppf "ietf:%s" token
  | `X_token token -> Fmt.pf 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 of_string = function
  | "7bit" -> Ok `Bit7
  | "8bit" -> Ok `Bit8
  | "binary" -> Ok `Binary
  | "quoted-printable" -> Ok `Quoted_printable
  | "base64" -> Ok `Base64
  | x -> Rresult.R.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 = 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

  (* 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 =
    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
OCaml

Innovation. Community. Security.