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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
module Handler = Vif_handler
module Request0 = Vif_request0
module Response = Vif_response
module Type = Vif_type
module Device = Vif_device
module Server = Vif_server
module Middleware = Vif_middleware
module Queries = Vif_queries
module Method = Vif_method
module Status = Vif_status
module Cookie = Vif_cookie
module Route = Vif_route
module Tags = Vif_tags
let src = Logs.Src.create "vif.core"
module Log = (val Logs.src_log src : Logs.LOG)
module Uri = struct
include Vif_uri
let int =
let prj = int_of_string and inj = string_of_int in
Tyre.(conv prj inj (regex Vif_route.Ext.arbitrary_int))
let string c = Tyre.regex (Vif_route.Ext.string c)
let rest = Tyre.regex Re.(rep1 any)
let path = Tyre.regex Re.(rep1 (compl [ char '?' ]))
let bool =
let prj = function "true" -> true | _ -> false
and inj x = if x then "true" else "false" in
Tyre.(conv prj inj (regex Vif_route.Ext.bool))
let float =
let prj = float_of_string and inj = string_of_float in
Tyre.(conv prj inj (regex Vif_route.Ext.float))
let option = Tyre.opt
let conv = Tyre.conv
let execp uri s =
let re = Vif_route.get_re uri in
Re.execp (Re.compile (Re.whole_string re)) s
let uri s f =
let _i'dunno, re_url, re = Vif_route.re_url 1 uri in
let id, re = Re.mark re in
let subs = Re.exec_opt (Re.compile (Re.whole_string re)) s in
match subs with
| Some subs ->
if Re.Mark.test subs id then
try Ok (Vif_route.extract_url ~original:s re_url subs f)
with Vif_route.Tyre_exn exn -> Error (`Converter_failure exn)
else Error `No_match
| None -> Error `No_match
end
module Devices = struct
type 'value t =
| [] : 'value t
| ( :: ) : ('value, 'a) Vif_device.device * 'value t -> 'value t
let run : Vif_device.Hmap.t -> 'value t -> 'value -> Vif_device.Hmap.t =
fun t lst user's_value ->
let rec go t = function
| [] -> t
| x :: r -> go (Vif_device.run t user's_value x) r
in
go t lst
let finally : Vif_device.t -> unit =
fun t ->
let[@warning "-8"] (Vif_device.Devices m) = t in
let fn (Vif_device.Hmap.B (k, v)) =
let { Vif_device.Device.finally; _ } = Vif_device.Hmap.Key.info k in
finally v
in
Vif_device.Hmap.iter fn m
end
let is_application_json { Multipart_form.Content_type.ty; subty; _ } =
match (ty, subty) with `Application, `Iana_token "json" -> true | _ -> false
let is_multipart_form_data { Multipart_form.Content_type.ty; subty; _ } =
match (ty, subty) with
| `Multipart, `Iana_token "form-data" -> true
| _ -> false
let content_type req0 =
let = Vif_request0.headers req0 in
let c = Vif_headers.get headers "content-type" in
let c = Option.map (fun c -> c ^ "\r\n") c in
let c = Option.to_result ~none:`Not_found c in
Result.bind c Multipart_form.Content_type.of_string
let recognize_request ~env req0 =
let : type c a.
Vif_method.t option
-> (c, a) Vif_type.t
-> ('s, c, a) Vif_request.t option =
fun meth c ->
let none = true in
let some = ( = ) (Vif_request0.meth req0) in
let meth_match = Option.fold ~none ~some meth in
match c with
| Vif_type.Any as encoding ->
if meth_match then Some (Vif_request.of_req0 ~encoding ~env req0)
else None
| Null as encoding ->
if meth_match then Some (Vif_request.of_req0 ~encoding ~env req0)
else None
| Json_encoding _ as encoding ->
let c = content_type req0 in
let type_match = Result.map is_application_json c in
let type_match = Result.value ~default:false type_match in
if type_match && meth_match then
Some (Vif_request.of_req0 ~encoding ~env req0)
else None
| Multipart_form_encoding _ as encoding ->
let c = content_type req0 in
let type_match = Result.map is_multipart_form_data c in
let type_match = Result.value ~default:false type_match in
if type_match && meth_match then
Some (Vif_request.of_req0 ~encoding ~env req0)
else None
| Multipart_form as encoding ->
let c = content_type req0 in
let type_match = Result.map is_multipart_form_data c in
let type_match = Result.value ~default:false type_match in
if type_match && meth_match then
Some (Vif_request.of_req0 ~encoding ~env req0)
else None
in
{ Vif_route.extract }
module Multipart_form = struct
open Flux
type 'id multipart_form_context = {
queue: event Queue.t
; parse: int parse
; actives: string Flux.Bqueue.c list
}
and event = [ `Id of Multipart_form.Header.t * string Flux.Bqueue.c ]
and 'id parse =
[ `Eof | `String of string ]
-> [ `Continue
| `Done of string Flux.Bqueue.c Multipart_form.t
| `Fail of string ]
let rec until_await ~tags ({ queue; parse; actives } as ctx) push acc str =
match Queue.pop queue with
| `Id (, bqueue) ->
let src = Source.bqueue bqueue in
let acc = push acc (header, src) in
let ctx = { ctx with actives= bqueue :: actives } in
until_await ~tags ctx push acc str
| exception Queue.Empty -> begin
match parse (`String str) with
| `Continue -> `Continue (ctx, acc)
| `Done _tree -> `Stop acc
| `Fail msg ->
List.iter Bqueue.close actives;
Log.err (fun m -> m ~tags "Invalid multipart/form-data: %s" msg);
`Stop acc
end
let rec until_done ~tags ({ queue; parse; actives } as ctx) push acc =
match Queue.pop queue with
| `Id (, bqueue) ->
let src = Source.bqueue bqueue in
let acc = push acc (header, src) in
let ctx = { ctx with actives= bqueue :: actives } in
until_done ~tags ctx push acc
| exception Queue.Empty -> begin
match parse `Eof with
| `Continue -> until_done ~tags ctx push acc
| `Done _tree -> acc
| `Fail msg ->
List.iter Bqueue.close actives;
Log.err (fun m -> m ~tags "Invalid multipart/form-data: %s" msg);
acc
end
let multipart_form req :
(string, Multipart_form.Header.t * string source) flow =
let hdrs = Vif_request.headers req in
let tags = Vif_request.tags req in
let content_type =
match Vif_headers.get hdrs "content-type" with
| None -> Fmt.invalid_arg "Content-type field missing"
| Some str ->
Multipart_form.Content_type.of_string (str ^ "\r\n") |> Result.get_ok
in
let flow (Sink k) =
let queue = Queue.create () in
let emitters =
let bqueue = Flux.Bqueue.(create with_close 0x7ff) in
Queue.push (`Id (header, bqueue)) queue;
let emitter = function
| None -> Bqueue.close bqueue
| Some str -> Bqueue.put bqueue str
in
(emitter, bqueue)
in
let init () =
let parse = Multipart_form.parse ~emitters content_type in
let acc = k.init () in
`Continue ({ queue; parse; actives= [] }, acc)
in
let push state str =
match state with
| `Continue (ctx, acc) -> until_await ~tags ctx k.push acc str
| `Stop _ as state -> state
in
let full = function `Continue _ -> false | `Stop _ -> true in
let stop = function
| `Continue (ctx, acc) -> k.stop (until_done ~tags ctx k.push acc)
| `Stop acc -> k.stop acc
in
Sink { init; stop; full; push }
in
{ flow }
let flat_parts : ('a * string source, 'a * string Miou.t) flow =
let flow (Sink k) =
let init () = k.init () in
let push acc (hdrs, from) =
let prm =
Miou.async @@ fun () ->
let via = Flow.identity in
let into = Sink.string in
let str, src = Stream.run ~from ~via ~into in
Option.iter Source.dispose src;
str
in
k.push acc (hdrs, prm)
in
let full acc = k.full acc in
let stop acc = k.stop acc in
Sink { init; stop; full; push }
in
{ flow }
include Vif_multipart_form
type part = meta = {
name: string option
; filename: string option
; size: int option
; mime: string option
}
let mime { mime; _ } = mime
let filename { filename; _ } = filename
let name { name; _ } = name
let size { size; _ } = size
let aggregate hdrs =
let hdrs = Multipart_form.Header.to_list hdrs in
let name = ref None in
let filename = ref None in
let size = ref None in
let mime = ref None in
let fn = function
| Multipart_form.Field.Field (_, Content_type, { ty; subty; _ }) ->
let open Multipart_form.Content_type in
let value = Fmt.str "%a/%a" Type.pp ty Subtype.pp subty in
mime := Some value;
None
| Field (_, Content_encoding, _) -> None
| Field (_, Content_disposition, t) ->
let open Multipart_form in
name := Content_disposition.name t;
filename := Content_disposition.filename t;
size := Content_disposition.size t;
None
| Field (fn, Field, unstrctrd) ->
let k = (fn :> string) in
let v = Unstrctrd.fold_fws unstrctrd in
let v = Unstrctrd.to_utf_8_string v in
Some (k, v)
in
let hdrs = List.filter_map fn hdrs in
let meta = { name= !name; filename= !filename; size= !size; mime= !mime } in
(hdrs, meta)
let parse req =
let from = Vif_request.source req in
try
let lst, src =
Stream.run ~from
~via:Flow.(multipart_form req << flat_parts)
~into:Sink.list
in
Option.iter Source.dispose src;
let fn (hdrs, prm) =
let hdrs, meta = aggregate hdrs in
((meta, hdrs), Miou.await_exn prm)
in
Ok (List.map fn lst)
with _exn -> Error `Invalid_multipart_form
let stream req =
let fn (hdrs, src) =
let _hdrs, meta = aggregate hdrs in
(meta, src)
in
Stream.from (Vif_request.source req)
|> Stream.via (multipart_form req)
|> Stream.map fn
end
module Request = struct
include Vif_request
let of_multipart_form : type a.
('s, Vif_type.multipart_form, a) Vif_request.t
-> (a, [> `Invalid_multipart_form | `Not_found of string ]) result =
function
| { encoding= Multipart_form_encoding r; _ } as req ->
let ( let* ) = Result.bind in
let* raw = Multipart_form.parse req in
begin try Ok (Multipart_form.get_record r raw) with
| Multipart_form.Field_not_found field -> Error (`Not_found field)
| exn ->
let tags = Vif_request.tags req in
Log.err (fun m ->
m ~tags "Unexpected exception from multipart-form/data: %s"
(Printexc.to_string exn));
Error `Invalid_multipart_form
end
| { encoding= Multipart_form; _ } as req -> Ok (Multipart_form.stream req)
| { encoding= Any; _ } -> assert false
end