1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
open Response_code
type 'body t = {
meth: Method.t;
host: string;
client: Async.client;
mutable headers: Headers.t;
origin: Headers.t;
mutable cookies: Cookies.t;
http_version: int;
path: string;
path_components: string list;
query: (string*string) list;
multipart_headers: ((string*Headers.header)*string) list;
body: 'body;
start_time: float;
}
let fail_raise = Headers.fail_raise
let log = Log.f
let self = self.headers
let cookies self = self.cookies
let host self = self.host
let meth self = self.meth
let path self = self.path
let path_components self = self.path_components
let body self = self.body
let client self = self.client
let start_time self = self.start_time
let query self = self.query
let self = self.multipart_headers
let ?f self h = Headers.get ?f h self.headers
let self h = match get_header self h with
| Some x -> (try Some (int_of_string x) with _ -> None)
| None -> None
let get_origin r = r.origin
let k v self = {self with headers=Headers.set k v self.headers}
let f self = {self with headers=f self.headers}
let set_body b self = {self with body=b}
let reset_timeout self = Async.reset_timeout self.client
let get_cookie self h =
try Some (Cookies.get h self.cookies)
with Not_found -> None
let set_cookie self c =
{ self with cookies = Cookies.add c self.cookies }
let get_cookie_string self h =
let c = Cookies.get h self.cookies in
c.value
let get_cookie_int self h =
int_of_string (get_cookie_string self h)
(** Should we close the connection after this request? *)
let close_after_req (self:_ t) : bool =
match self.http_version with
| 1 -> get_header self Headers.Connection = Some"close"
| 0 -> not (get_header self Headers.Connection = Some"keep-alive")
| _ -> false
let pp_comp_ out comp =
Format.fprintf out "[%s]"
(String.concat ";" @@ List.map (Printf.sprintf "%S") comp)
let pp_query out q =
Format.fprintf out "[%s]"
(String.concat ";" @@
List.map (fun (a,b) -> Printf.sprintf "%S,%S" a b) q)
let pp_ out self : unit =
Format.fprintf out "{@[meth=%s;@ host=%s;@ headers=[@[%a@]];@ \
path=%S;@ body=?;@ path_components=%a;@ query=%a@]}"
(Method.to_string self.meth) self.host Headers.pp self.headers self.path
pp_comp_ self.path_components pp_query self.query
let pp out self : unit =
Format.fprintf out "{@[meth=%s;@ host=%s;@ headers=[@[%a@]];@ path=%S;@ \
body=%S;@ path_components=%a;@ query=%a@]}"
(Method.to_string self.meth) self.host Headers.pp self.headers
self.path self.body pp_comp_ self.path_components pp_query self.query
let read_stream_chunked_ ~buf ~trailer (bs:Input.t) : Input.t =
Input.read_chunked ~buf ~trailer
~fail:(fun s -> fail_raise ~code:bad_request "%s" s)
bs
let limit_body_size_ ~max_size (bs:Input.t) : Input.t =
Input.limit_size_to ~max_size ~close_rec:false bs
~too_big:(fun size ->
fail_raise ~code:content_too_large
"body size was supposed to be %d, but at least %d bytes received"
max_size size
)
let limit_body_size ~max_size (req:Input.t t) : Input.t t =
{ req with body=limit_body_size_ ~max_size req.body }
let read_exactly ~size (bs:Input.t) : Input.t =
Input.read_exactly bs ~close_rec:false
~size ~too_short:(fun size ->
fail_raise ~code:bad_request "body is too short by %d bytes" size)
let parse_req_start ~client ~buf (bs:Input.t)
: Input.t t option =
try
let meth = Method.parse bs in
let start_time = Async.register_starttime client in
let _ = Input.exact_char ' ' () bs in
let (path, path_components, query) = Input.read_path ~buf bs in
let _ = Input.exact_string "HTTP/" () bs in
let major = Input.int bs in
let _ = Input.exact_char '.' () bs in
let minor = Input.int bs in
let _ = Input.exact_char '\r' () bs in
let _ = Input.exact_char '\n' () bs in
if major != 1 || (minor != 0 && minor != 1) then Input.fail_parse bs;
let (, cookies, origin) = Headers.parse_ ~buf bs in
let host =
match Headers.get Headers.Host headers with
| None -> fail_raise ~code:bad_request "No 'Host' header in request"
| Some h -> h
in
let = []
in
let req = {
meth; query; host; origin; client; path; path_components;
multipart_headers;
headers; cookies; http_version=minor; body=bs; start_time;
} in
Some req
with
| Headers.Bad_req _ | Unix.Unix_error _ | Ssl.Read_error _
| Ssl.Write_error _ as e ->
log (Exc 1) (fun k->k "exn in request %s" (Async.printexn e));
raise e
| Input.FailParse n ->
log (Exc 1) (fun k->k "Invalid request line at %d: %S" n (Input.current bs));
fail_raise ~code:bad_request "Invalid request line"
| e -> fail_raise ~code:internal_server_error "exception: %s" (Async.printexn e)
let parse_multipart_ ~bound req =
let target = "\r\n" ^ bound in
let body = req.body in
let buf = Buffer.create 1024 in
let buf2 = Buffer.create 1024 in
let query = ref [] in
let = ref [] in
Input.read_until ~buf ~target:bound body;
let line = Input.read_line ~buf:buf2 body in
let cont = ref (String.trim line <> "--") in
while !cont do
let (, _, _) = Headers.parse_ ~buf body in
let cd = match Headers.get Content_Disposition header
with Some cd -> Scanf.Scanning.from_string cd
| None -> raise Not_found
in
let key = Scanf.bscanf cd "form-data; name = %S" String.trim in
let _ =
try Scanf.bscanf cd " ; filename = %S"
(fun str ->
multipart_headers := ((key, Headers.Filename_Multipart), String.trim str)
:: !multipart_headers)
with _ -> ()
in
List.iter (fun (h,v) ->
if h <> Headers.Content_Disposition then
multipart_headers := ((key, h), v) :: !multipart_headers) header;
let value =
Input.read_until ~buf:buf2 ~target body;
let line = Input.read_line ~buf body in
if String.trim line = "--" then cont := false;
Buffer.contents buf2
in
query := (key, value) :: !query
done;
let body = Input.of_string "" in
{ req with query = List.rev_append !query req.query;
multipart_headers = List.rev_append !multipart_headers req.multipart_headers;
body }
let parse_urlencoded size req =
let r = Bytes.create size in
let too_short () = fail_raise ~code:bad_request "body too_short" in
Input.read_exactly_bytes ~too_short req.body r size;
let query = Bytes.unsafe_to_string r in
let query =
try Util.parse_query query
with Util.Invalid_query e ->
fail_raise ~code:bad_request "invalid body: %s" e
in
{ req with query = List.rev_append query req.query}
type enctype =
| Multipart of string
| UrlEncoded
| NoData
let parse_body_ ~tr_stream ~buf (req:Input.t t) : Input.t t =
try
Buffer.clear buf;
let size =
match Headers.get_exn Headers.Content_Length req.headers
|> int_of_string with
| n -> n
| exception Not_found -> 0
| exception _ -> fail_raise ~code:bad_request "invalid Content-Length"
in
let trailer bs =
let , cookies, _ = Headers.parse_ ~buf bs in
req.headers <- req.headers @ headers;
req.cookies <- req.cookies @ cookies;
in
let enctype =
try
match Headers.(get Content_Type req.headers)
with
| Some "application/x-www-form-urlencoded" ->
UrlEncoded
| Some ct ->
Scanf.sscanf ct "multipart/form-data ; boundary = %s "
(fun b -> Multipart("--" ^ b))
| None -> NoData
with _ -> NoData
in
let transfer_encoding =
get_header ~f:String.trim req Headers.Transfer_Encoding
in
let req =
match transfer_encoding, enctype
with
| None, NoData ->
let body = read_exactly ~size @@ tr_stream req.body in
{ req with body }
| None, Multipart bound ->
let req = parse_multipart_ ~bound req in
let body = Input.of_string "" in
{ req with body }
| None, UrlEncoded ->
let req = parse_urlencoded size req in
let body = Input.of_string "" in
{ req with body }
| Some "chunked", _ ->
let bs =
read_stream_chunked_ ~buf ~trailer @@ tr_stream req.body
in
let body = if size>0 then limit_body_size_ ~max_size:size bs else bs in
let req = { req with body } in
(match enctype with
| Multipart bound ->
parse_multipart_ ~bound req
| UrlEncoded ->
parse_urlencoded size req
| NoData -> req)
| Some s, _ -> fail_raise ~code:not_implemented "cannot handle transfer encoding: %s" s
in
req
with
| Headers.Bad_req _ as e -> raise e
| e -> fail_raise ~code:internal_server_error "exception: %s" (Async.printexn e)
let parse_body ~buf req : Input.t t =
parse_body_ ~tr_stream:(fun s->s) ~buf req
let read_body_full ~buf (self:Input.t t) : string t =
try
let body = Input.read_all ~buf self.body in
{ self with body }
with
| e -> fail_raise ~code:bad_request "failed to read body: %s" (Async.printexn e)