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
open Angstrom
let is_bcharsnospace = function
| '\'' | '(' | ')' | '+' | '_' | ',' | '-' | '.' | '/' | ':' | '=' | '?' ->
true
| 'a' .. 'z' | 'A' .. 'Z' -> true
| '0' .. '9' -> true
| _ -> false
let is_bchars = function ' ' -> true | c -> is_bcharsnospace c
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 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
fix @@ fun m ->
skip_while (( <> ) '-') *> peek_char >>= function
| Some '-' -> (
check_boundary >>= function true -> return () | false -> advance 1 *> m)
| Some _ -> advance 1 *> m
| None -> return ()
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
fix @@ fun m ->
skip_while (( <> ) '\r') *> peek_char >>= function
| Some '\r' -> (
check_delimiter >>= function true -> return () | false -> advance 1 *> m)
| Some _ -> advance 1 *> m
| None -> return ()
let nothing_to_do = Fmt.kstr fail "nothing to do"
let crlf = char '\r' *> char '\n'
let body_part body =
Header.Decoder.header >>= fun fields ->
( crlf *> return `CRLF <|> return `Nothing <* commit >>= function
| `CRLF -> body fields >>| Option.some
| `Nothing -> return None )
>>| fun body -> (fields, body)
let encapsulation boundary body =
string (make_delimiter boundary)
*> transport_padding
*> crlf
*> commit
*> body_part body
let preambule boundary = discard_all_to_dash_boundary boundary
let epilogue parent =
match parent with
| Some boundary -> discard_all_to_delimiter boundary
| None -> skip_while (fun _ -> true)
let multipart_body ?parent boundary body =
option () (preambule boundary)
*> dash_boundary boundary
*> transport_padding
*> crlf
*> commit
*> body_part body
>>= fun x ->
many (encapsulation boundary body) >>= fun r ->
(commit
*> close_delimiter boundary
*> transport_padding
*> option () (epilogue parent)
<|> return ())
*> return (x :: r)