~ubuntu-branches/ubuntu/trusty/ocamlnet/trusty

« back to all changes in this revision

Viewing changes to src/netstring/nethttp.mlp

  • Committer: Bazaar Package Importer
  • Author(s): Stéphane Glondu
  • Date: 2011-09-02 14:12:33 UTC
  • mfrom: (18.2.3 sid)
  • Revision ID: james.westby@ubuntu.com-20110902141233-zbj0ygxb92u6gy4z
Tags: 3.4-1
* New upstream release
  - add a new NetcgiRequire directive to ease dependency management
    (Closes: #637147)
  - remove patches that were applied upstream:
    + Added-missing-shebang-lines-in-example-shell-scripts
    + Try-also-ocamlc-for-POSIX-threads

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
(* $Id: nethttp.mlp 1588 2011-04-28 13:59:54Z gerd $ 
 
2
 * ----------------------------------------------------------------------
 
3
 * Nethttp: Basic definitions for the HTTP protocol
 
4
 *)
 
5
 
 
6
type protocol_version = 
 
7
    int * int
 
8
type protocol_attribute =
 
9
  [ `Secure_https
 
10
  ]
 
11
type protocol =
 
12
  [ `Http of (protocol_version * protocol_attribute list)
 
13
  | `Other
 
14
  ]
 
15
 
 
16
let string_of_protocol =
 
17
  function
 
18
    | `Http((m,n),_) -> "HTTP/" ^ string_of_int m ^ "." ^ string_of_int n
 
19
    | `Other -> failwith "string_of_protocol"
 
20
 
 
21
let http_re = Netstring_str.regexp "HTTP/\\([0-9]+\\)\\.\\([0-9]+\\)$"
 
22
 
 
23
let protocol_of_string s =
 
24
  match Netstring_str.string_match http_re s 0 with
 
25
    | Some m ->
 
26
        ( try 
 
27
            `Http ((int_of_string (Netstring_str.matched_group m 1 s),
 
28
                    int_of_string (Netstring_str.matched_group m 2 s)), [])
 
29
          with
 
30
              Failure _ -> `Other  (* Probably denial-of-service attack! *)
 
31
        )
 
32
    | None ->
 
33
        `Other
 
34
 
 
35
type http_status = 
 
36
  (* 1xx: (informational) *)
 
37
  [ `Continue
 
38
  | `Switching_protocols 
 
39
  (* 2xx: (successful) *)
 
40
  | `Ok
 
41
  | `Created
 
42
  | `Accepted
 
43
  | `Non_authoritative
 
44
  | `No_content
 
45
  | `Reset_content
 
46
  | `Partial_content
 
47
  (* 3xx: (redirection) *)
 
48
  | `Multiple_choices
 
49
  | `Moved_permanently
 
50
  | `Found
 
51
  | `See_other
 
52
  | `Not_modified
 
53
  | `Use_proxy
 
54
  | `Temporary_redirect
 
55
  (* 4xx: (client error) *)
 
56
  | `Bad_request
 
57
  | `Unauthorized
 
58
  | `Payment_required
 
59
  | `Forbidden
 
60
  | `Not_found
 
61
  | `Method_not_allowed
 
62
  | `Not_acceptable
 
63
  | `Proxy_auth_required
 
64
  | `Request_timeout
 
65
  | `Conflict
 
66
  | `Gone
 
67
  | `Length_required
 
68
  | `Precondition_failed
 
69
  | `Request_entity_too_large
 
70
  | `Request_uri_too_long
 
71
  | `Unsupported_media_type
 
72
  | `Requested_range_not_satisfiable
 
73
  | `Expectation_failed
 
74
  (* 5xx: (server error) *)
 
75
  | `Internal_server_error
 
76
  | `Not_implemented
 
77
  | `Bad_gateway
 
78
  | `Service_unavailable
 
79
  | `Gateway_timeout
 
80
  | `Http_version_not_supported 
 
81
  ]
 
82
 
 
83
let int_of_http_status =
 
84
  function
 
85
      (* 1xx: (informational) *)
 
86
    | `Continue -> 100
 
87
    | `Switching_protocols -> 101
 
88
      (* 2xx: (successful) *)
 
89
    | `Ok -> 200
 
90
    | `Created -> 201
 
91
    | `Accepted -> 202
 
92
    | `Non_authoritative -> 203
 
93
    | `No_content -> 204
 
94
    | `Reset_content -> 205
 
95
    | `Partial_content -> 206
 
96
      (* 3xx: (redirection) *)
 
97
    | `Multiple_choices -> 300
 
98
    | `Moved_permanently -> 301
 
99
    | `Found -> 302
 
100
    | `See_other -> 303
 
101
    | `Not_modified -> 304
 
102
    | `Use_proxy -> 305
 
103
    | `Temporary_redirect -> 307
 
104
      (* 4xx: (client error) *)
 
105
    | `Bad_request -> 400
 
106
    | `Unauthorized -> 401
 
107
    | `Payment_required -> 402
 
108
    | `Forbidden -> 403
 
109
    | `Not_found -> 404
 
110
    | `Method_not_allowed -> 405
 
111
    | `Not_acceptable -> 406
 
112
    | `Proxy_auth_required -> 407
 
113
    | `Request_timeout -> 408
 
114
    | `Conflict -> 409
 
115
    | `Gone -> 410
 
116
    | `Length_required -> 411
 
117
    | `Precondition_failed -> 412
 
118
    | `Request_entity_too_large -> 413
 
119
    | `Request_uri_too_long -> 414
 
120
    | `Unsupported_media_type -> 415
 
121
    | `Requested_range_not_satisfiable -> 416
 
122
    | `Expectation_failed -> 417
 
123
      (* 5xx: (server error) *)
 
124
    | `Internal_server_error -> 500
 
125
    | `Not_implemented -> 501
 
126
    | `Bad_gateway -> 502
 
127
    | `Service_unavailable -> 503
 
128
    | `Gateway_timeout -> 504
 
129
    | `Http_version_not_supported -> 505
 
130
 
 
131
 
 
132
let string_of_http_status =
 
133
  function
 
134
      (* 1xx: (informational) *)
 
135
    | `Continue -> "Continue"
 
136
    | `Switching_protocols -> "Switching Protocols"
 
137
      (* 2xx: (successful) *)
 
138
    | `Ok -> "OK"
 
139
    | `Created -> "Created"
 
140
    | `Accepted -> "Accepted"
 
141
    | `Non_authoritative -> "Non-authoritative Information"
 
142
    | `No_content -> "No Content"
 
143
    | `Reset_content -> "Reset Content"
 
144
    | `Partial_content -> "Partial Content"
 
145
      (* 3xx: (redirection) *)
 
146
    | `Multiple_choices -> "Multiple Choices"
 
147
    | `Moved_permanently -> "Moved Permanently"
 
148
    | `Found -> "Found"
 
149
    | `See_other -> "See Other"
 
150
    | `Not_modified -> "Not Modified"
 
151
    | `Use_proxy -> "Use Proxy"
 
152
    | `Temporary_redirect -> "Temporary Redirect"
 
153
      (* 4xx: (client error) *)
 
154
    | `Bad_request -> "Bad Request"
 
155
    | `Unauthorized -> "Unauthorized"
 
156
    | `Payment_required -> "Payment Required"
 
157
    | `Forbidden -> "Forbidden"
 
158
    | `Not_found -> "Not Found"
 
159
    | `Method_not_allowed -> "Method Not Allowed"
 
160
    | `Not_acceptable -> "Not Acceptable"
 
161
    | `Proxy_auth_required -> "Proxy Authorization Required"
 
162
    | `Request_timeout -> "Request Timeout"
 
163
    | `Conflict -> "Conflict"
 
164
    | `Gone -> "Gone"
 
165
    | `Length_required -> "Length Required"
 
166
    | `Precondition_failed -> "Precondition Failed"
 
167
    | `Request_entity_too_large -> "Request Entity Too Large"
 
168
    | `Request_uri_too_long -> "Request URI Too Long"
 
169
    | `Unsupported_media_type -> "Unsupported Media Type"
 
170
    | `Requested_range_not_satisfiable -> "Request Range Not Satisfiable"
 
171
    | `Expectation_failed -> "Expectation Failed"
 
172
      (* 5xx: (server error) *)
 
173
    | `Internal_server_error -> "Internal Server Error"
 
174
    | `Not_implemented -> "Not Implemented"
 
175
    | `Bad_gateway -> "Bad Gateway"
 
176
    | `Service_unavailable -> "Service Unavailable"
 
177
    | `Gateway_timeout -> "Gateway Timeout"
 
178
    | `Http_version_not_supported -> "HTTP Version Not Supported"
 
179
 
 
180
 
 
181
let http_status_of_int =
 
182
  function
 
183
      (* 1xx: (informational) *)
 
184
    | 100 -> `Continue
 
185
    | 101 -> `Switching_protocols
 
186
      (* 2xx: (successful) *)
 
187
    | 200 -> `Ok
 
188
    | 201 -> `Created
 
189
    | 202 -> `Accepted
 
190
    | 203 -> `Non_authoritative
 
191
    | 204 -> `No_content
 
192
    | 205 -> `Reset_content
 
193
    | 206 -> `Partial_content
 
194
      (* 3xx: (redirection) *)
 
195
    | 300 -> `Multiple_choices
 
196
    | 301 -> `Moved_permanently
 
197
    | 302 -> `Found
 
198
    | 303 -> `See_other
 
199
    | 304 -> `Not_modified
 
200
    | 305 -> `Use_proxy
 
201
    | 307 -> `Temporary_redirect
 
202
      (* 4xx: (client error) *)
 
203
    | 400 -> `Bad_request
 
204
    | 401 -> `Unauthorized
 
205
    | 402 -> `Payment_required
 
206
    | 403 -> `Forbidden
 
207
    | 404 -> `Not_found
 
208
    | 405 -> `Method_not_allowed
 
209
    | 406 -> `Not_acceptable
 
210
    | 407 -> `Proxy_auth_required
 
211
    | 408 -> `Request_timeout
 
212
    | 409 -> `Conflict
 
213
    | 410 -> `Gone
 
214
    | 411 -> `Length_required
 
215
    | 412 -> `Precondition_failed
 
216
    | 413 -> `Request_entity_too_large
 
217
    | 414 -> `Request_uri_too_long
 
218
    | 415 -> `Unsupported_media_type
 
219
    | 416 -> `Requested_range_not_satisfiable
 
220
    | 417 -> `Expectation_failed
 
221
      (* 5xx: (server error) *)
 
222
    | 500 -> `Internal_server_error
 
223
    | 501 -> `Not_implemented
 
224
    | 502 -> `Bad_gateway
 
225
    | 503 -> `Service_unavailable
 
226
    | 504 -> `Gateway_timeout
 
227
    | 505 -> `Http_version_not_supported
 
228
    | _ -> raise Not_found
 
229
 
 
230
type http_method = string * string
 
231
  (** Method name, URI *)
 
232
 
 
233
type cache_control_token =
 
234
    [ `No_store
 
235
    | `Max_age of int
 
236
    | `Max_stale of int option
 
237
    | `Min_fresh of int
 
238
    | `No_transform
 
239
    | `Only_if_cached
 
240
    | `Public
 
241
    | `Private of string list
 
242
    | `No_cache of string list
 
243
    | `Must_revalidate
 
244
    | `Proxy_revalidate
 
245
    | `S_maxage of int
 
246
    | `Extension of string * string option
 
247
    ]
 
248
 
 
249
type etag =
 
250
    [ `Weak of string
 
251
    | `Strong of string
 
252
    ]
 
253
 
 
254
let weak_validator_match e1 e2 =
 
255
  match (e1,e2) with
 
256
    | (`Strong s1, `Strong s2) -> s1 = s2
 
257
    | (`Strong s1, `Weak w2) -> s1 = w2
 
258
    | (`Weak w1, `Strong s2) -> w1 = s2
 
259
    | (`Weak w1, `Weak w2) -> w1 = w2
 
260
 
 
261
let strong_validator_match e1 e2 =
 
262
  match (e1,e2) with
 
263
    | (`Strong s1, `Strong s2) -> s1 = s2
 
264
    | _ -> false
 
265
  
 
266
exception Bad_header_field of string
 
267
 
 
268
class type http_header = Netmime.mime_header
 
269
class type http_header_ro = Netmime.mime_header_ro
 
270
class type http_trailer = Netmime.mime_header
 
271
class type http_trailer_ro = Netmime.mime_header_ro
 
272
 
 
273
 
 
274
type netscape_cookie =
 
275
    { cookie_name : string;
 
276
      cookie_value : string;
 
277
      cookie_expires : float option;
 
278
      cookie_domain : string option;
 
279
      cookie_path : string option;
 
280
      cookie_secure : bool;
 
281
    }
 
282
 
 
283
type cookie = netscape_cookie
 
284
 
 
285
 
 
286
let status_re =
 
287
  Netstring_str.regexp "^\\([0-9]+\\)\\([ \t]+\\(.*\\)\\)?$"
 
288
 
 
289
let status_of_cgi_header hdr =
 
290
  let (code, phrase) =
 
291
    try
 
292
      let status = hdr # field "Status" in
 
293
      ( match Netstring_str.string_match status_re status 0 with
 
294
          | Some m ->
 
295
              (int_of_string (Netstring_str.matched_group m 1 status),
 
296
               (try Netstring_str.matched_group m 3 status with Not_found -> "")
 
297
              )
 
298
          | None ->
 
299
              failwith "Bad Status response header field"
 
300
                (* Don't know what to do *)
 
301
      )
 
302
    with
 
303
        Not_found ->
 
304
          (* Maybe there is a [Location] header: *)
 
305
          ( try
 
306
              let _location = hdr # field "Location" in
 
307
              (302, "Found")
 
308
            with
 
309
                Not_found ->
 
310
                  (* Default: 200 OK *)
 
311
                  (200, "OK")
 
312
          )
 
313
  in
 
314
  (* Repair [phrase] if empty: *)
 
315
  let phrase =
 
316
    if phrase = "" then 
 
317
      ( try string_of_http_status (http_status_of_int code)
 
318
        with Not_found -> "Unknown"
 
319
      )
 
320
    else
 
321
      phrase in
 
322
  (code, phrase)
 
323
;;
 
324
 
 
325
 
 
326
let query_re =
 
327
  Netstring_str.regexp "^\\([^?]*\\)\\?\\(.*\\)$"
 
328
 
 
329
let decode_query req_uri =
 
330
  match Netstring_str.string_match query_re req_uri 0 with
 
331
    | Some m ->
 
332
        (Netstring_str.matched_group m 1 req_uri,
 
333
         Netstring_str.matched_group m 2 req_uri)
 
334
    | None ->
 
335
        (req_uri, "")
 
336
 
 
337
let host4_re =
 
338
  Netstring_str.regexp "\\([^]: \t[]+\\)\\(:\\([0-9]+\\)\\)?$" (* CHECK *)
 
339
 
 
340
let host6_re =
 
341
  Netstring_str.regexp "\\[\\([^ \t]+\\)\\]\\(:\\([0-9]+\\)\\)?$"
 
342
 
 
343
let split_host_port s =
 
344
  match Netstring_str.string_match host4_re s 0 with
 
345
    | Some m ->
 
346
        let host_name = Netstring_str.matched_group m 1 s in
 
347
        let host_port =
 
348
          try Some(int_of_string(Netstring_str.matched_group m 3 s))
 
349
          with
 
350
            | Not_found -> None
 
351
        in
 
352
        (host_name, host_port)
 
353
    | None ->
 
354
        ( match Netstring_str.string_match host6_re s 0 with
 
355
            | Some m ->
 
356
                let host_name = Netstring_str.matched_group m 1 s in
 
357
                let host_port =
 
358
                  try Some(int_of_string(Netstring_str.matched_group m 3 s))
 
359
                  with
 
360
                    | Not_found -> None
 
361
                in
 
362
                (host_name, host_port)
 
363
            | None ->
 
364
                failwith "Invalid hostname"
 
365
        )
 
366
 
 
367
let uripath_encode s =
 
368
  let l = Neturl.split_path s in
 
369
  let l' = List.map (Netencoding.Url.encode ~plus:false) l in
 
370
  Neturl.join_path l'
 
371
 
 
372
let uripath_decode s =
 
373
  let l = Neturl.split_path s in
 
374
  let l' = 
 
375
    List.map
 
376
      (fun u -> 
 
377
         let u' = Netencoding.Url.decode ~plus:false u in
 
378
         if String.contains u' '/' then
 
379
           failwith "Nethttp.uripath_decode";
 
380
         u')
 
381
      l in
 
382
  Neturl.join_path l'
 
383
 
 
384
let rev_split is_cut s =
 
385
  (* exported *)
 
386
  let rec seek_cut acc i0 i1 =
 
387
    if i1 >= String.length s then
 
388
      (String.sub s i0 (i1 - i0)) :: acc
 
389
    else if is_cut(String.unsafe_get s i1) then
 
390
      skip ((String.sub s i0 (i1 - i0)) :: acc) (i1 + 1) (i1 + 1)
 
391
    else
 
392
      seek_cut acc i0 (i1 + 1)
 
393
  and skip acc i0 i1 =
 
394
    if i1 >= String.length s then acc
 
395
    else if is_cut(String.unsafe_get s i1) then skip acc i0 (i1 + 1)
 
396
    else seek_cut acc i1 i1 in
 
397
  skip [] 0 0
 
398
    
 
399
 
 
400
module Cookie = struct
 
401
  (* This module has been written by Christophe Troestler.
 
402
      For full copyright message see netcgi.ml
 
403
   *)
 
404
 
 
405
  (* Cookies are chosen to be mutable because they are stored on the
 
406
   client -- there is no rollback possible -- and mutability kind of
 
407
   reflects that... *)
 
408
 
 
409
  type t =  {
 
410
    mutable name : string;
 
411
    mutable value : string;
 
412
    mutable max_age : int option;
 
413
    mutable domain : string option;
 
414
    mutable path : string option;
 
415
    mutable secure : bool;
 
416
    mutable comment : string;
 
417
    mutable comment_url : string;
 
418
    mutable ports : int list option;
 
419
  }
 
420
 
 
421
  let make ?max_age ?domain ?path ?(secure=false)
 
422
      ?(comment="") ?(comment_url="") ?ports name value =
 
423
    { name = name;
 
424
      value = value;
 
425
      max_age = max_age;
 
426
      domain = domain;
 
427
      path = path;
 
428
      secure = secure;
 
429
      comment = comment;
 
430
      comment_url = comment_url;
 
431
      ports = ports;
 
432
    }
 
433
 
 
434
  (* Old version of cookies *)
 
435
  let of_netscape_cookie c =
 
436
    { name = c.cookie_name;
 
437
      value = c.cookie_value;
 
438
      max_age = (match c.cookie_expires with
 
439
                 | None -> None
 
440
                 | Some t -> Some(truncate(t -. Unix.time())));
 
441
      domain = c.cookie_domain;
 
442
      path = c.cookie_path;
 
443
      secure = c.cookie_secure;
 
444
      comment = "";
 
445
      comment_url = "";
 
446
      ports = None
 
447
    }
 
448
  let to_netscape_cookie cookie =
 
449
    { cookie_name = cookie.name;
 
450
      cookie_value = cookie.value;
 
451
      cookie_expires = (match cookie.max_age with
 
452
                          | None -> None
 
453
                          | Some t -> Some(float t +. Unix.time()));
 
454
      cookie_domain = cookie.domain;
 
455
      cookie_path = cookie.path;
 
456
      cookie_secure = cookie.secure;
 
457
    }
 
458
 
 
459
  let name cookie = cookie.name
 
460
  let value cookie = cookie.value
 
461
  let max_age cookie = cookie.max_age
 
462
  let domain cookie = cookie.domain
 
463
  let path cookie = cookie.path
 
464
  let secure cookie = cookie.secure
 
465
  let comment cookie = cookie.comment
 
466
  let comment_url cookie = cookie.comment_url
 
467
  let ports cookie = cookie.ports
 
468
 
 
469
  let set_value cookie v = cookie.value <- v
 
470
  let set_max_age cookie t = cookie.max_age <- t
 
471
  let set_domain cookie dom = cookie.domain <- dom
 
472
  let set_path cookie s = cookie.path <- s
 
473
  let set_secure cookie b = cookie.secure <- b
 
474
  let set_comment cookie s = cookie.comment <- s
 
475
  let set_comment_url cookie s = cookie.comment_url <- s
 
476
  let set_ports cookie p = cookie.ports <- p
 
477
 
 
478
  (* Set -------------------------------------------------- *)
 
479
 
 
480
  (* Escape '"', '\\',... and surround the string with quotes. *)
 
481
  let escape s0 =
 
482
    let len = String.length s0 in
 
483
    let encoded_length = ref len in
 
484
    for i = 0 to len - 1 do
 
485
      match String.unsafe_get s0 i with
 
486
      | '\"' | '\\' | '\n' | '\r' -> incr encoded_length
 
487
      | '\000' .. '\031' -> decr encoded_length (* ignore *)
 
488
      | _ -> ()
 
489
    done;
 
490
    let s = String.create (!encoded_length + 2) in
 
491
    String.unsafe_set s 0 '\"';
 
492
    let j = ref 1 in
 
493
    for i = 0 to len - 1 do
 
494
      (match String.unsafe_get s0 i with
 
495
      | '\"' | '\\' as c ->
 
496
          String.unsafe_set s !j '\\'; incr j;
 
497
          String.unsafe_set s !j c; incr j
 
498
      | '\n' ->
 
499
          String.unsafe_set s !j '\\'; incr j;
 
500
          String.unsafe_set s !j 'n'; incr j
 
501
      | '\r' ->
 
502
          String.unsafe_set s !j '\\'; incr j;
 
503
          String.unsafe_set s !j 'r'; incr j
 
504
      | '\000' .. '\031' ->
 
505
          () (* Ignore these control chars, useless for comments *)
 
506
      | c ->
 
507
          String.unsafe_set s !j c; incr j
 
508
      );
 
509
    done;
 
510
    String.unsafe_set s !j '\"';
 
511
    s
 
512
 
 
513
  (* [gen_cookie c] returns a buffer containing an attribute suitable
 
514
     for "Set-Cookie" (RFC 2109) and "Set-Cookie2" (RFC 2965).
 
515
     which is backward compatible with Netscape spec.  It is the
 
516
     minimal denominator. *)
 
517
  let gen_cookie c =
 
518
    let buf = Buffer.create 128 in
 
519
    (* Encode, do not quote, key-val for compatibility with old browsers. *)
 
520
    Buffer.add_string buf (Netencoding.Url.encode ~plus:false c.name);
 
521
    Buffer.add_string buf "=";
 
522
    Buffer.add_string buf (Netencoding.Url.encode ~plus:false c.value);
 
523
    Buffer.add_string buf ";Version=1";
 
524
    (* FIXME: Although values of Domain and Path can be quoted since
 
525
       RFC2109, it seems that browsers do not understand them -- they
 
526
       take the quotes as part of the value.  One way to get correct
 
527
       headers is to strip [d] and [p] of unsafe chars -- if they have any. *)
 
528
    (match c.domain with
 
529
     | None -> ()
 
530
     | Some d ->
 
531
         Buffer.add_string buf ";Domain=";
 
532
         Buffer.add_string buf d);
 
533
    (match c.path with
 
534
     | None -> ()
 
535
     | Some p ->
 
536
         Buffer.add_string buf ";Path=";
 
537
         Buffer.add_string buf p);
 
538
    if c.secure then Buffer.add_string buf ";secure";
 
539
    (match c.max_age with
 
540
     | None -> ()
 
541
     | Some s ->
 
542
         Buffer.add_string buf ";Max-Age=";
 
543
         Buffer.add_string buf (if s > 0 then string_of_int s else "0");
 
544
         (* For compatibility with old browsers: *)
 
545
         Buffer.add_string buf ";Expires=";
 
546
         Buffer.add_string buf
 
547
           (if s > 0 then Netdate.mk_mail_date (Unix.time() +. float s)
 
548
            else "Thu, 1 Jan 1970 00:00:00 GMT");
 
549
    );
 
550
    if c.comment <> "" then (
 
551
      Buffer.add_string buf ";Comment=";
 
552
      Buffer.add_string buf (escape c.comment);
 
553
    );
 
554
    buf
 
555
 
 
556
 
 
557
  let set_set_cookie_ct (http_header:#Netmime.mime_header) cookies =
 
558
    let add_cookie (c1, c2) c =
 
559
      let buf = gen_cookie c in
 
560
      (* In any case, we set a "Set-Cookie" header *)
 
561
      let c1 = (Buffer.contents buf) :: c1 in
 
562
      let c2 =
 
563
        if c.comment_url = "" && c.ports = None then c2 else (
 
564
          (* When this is relevant, also set a "Set-Cookie2" header *)
 
565
          if c.comment_url <> "" then (
 
566
            Buffer.add_string buf ";CommentURL=";
 
567
            Buffer.add_string buf (escape c.comment_url));
 
568
          (match c.ports with
 
569
           | None -> ()
 
570
           | Some p ->
 
571
               Buffer.add_string buf ";Port=\"";
 
572
               Buffer.add_string buf (String.concat ","
 
573
                                        (List.map string_of_int p));
 
574
               Buffer.add_string buf "\""
 
575
          );
 
576
          (Buffer.contents buf) :: c2
 
577
        ) in
 
578
      (c1, c2) in
 
579
    let cookie, cookie2 = List.fold_left add_cookie ([], []) cookies in
 
580
    http_header#update_multiple_field "Set-Cookie"  cookie;
 
581
    (* "Set-Cookie2" must come after in order, when they are
 
582
       understood, to override the "Set-Cookie". *)
 
583
    http_header#update_multiple_field "Set-Cookie2" cookie2
 
584
 
 
585
 
 
586
  (* Get -------------------------------------------------- *)
 
587
 
 
588
  (* According to RFC 2068:
 
589
        quoted-string  = ( <"> *(qdtext) <"> )
 
590
        qdtext         = <any TEXT except '\"'>
 
591
        quoted-pair    = "\\" CHAR
 
592
     As there a no details, we decode the usual escapes and treat
 
593
     other "\x" as simply "x". *)
 
594
  let unescape_range s low up =
 
595
    if low >= up then "" else
 
596
      let len = up - low in
 
597
      let s = String.sub s low len in
 
598
      let rec decode i j =
 
599
        if i < len then (
 
600
          match String.unsafe_get s i with
 
601
          | '\\' ->
 
602
              let i = i + 1 in
 
603
              if i < len then (
 
604
                (match String.unsafe_get s i with
 
605
                 | '\"' | '\\' as c -> String.unsafe_set s j c
 
606
                 | 'n' -> String.unsafe_set s j '\n'
 
607
                 | 'r' -> String.unsafe_set s j '\r'
 
608
                 | 't' -> String.unsafe_set s j '\t'
 
609
                 | c -> String.unsafe_set s j c
 
610
                );
 
611
                decode (i + 1) (j + 1)
 
612
              )
 
613
              else j
 
614
          | c ->
 
615
              String.unsafe_set s j c;
 
616
              decode (i + 1) (j + 1)
 
617
        )
 
618
        else j in
 
619
      let j = decode 0 0 in
 
620
      if j < len then String.sub s 0 j else s
 
621
 
 
622
 
 
623
  let ports_of_string s =
 
624
    let l = rev_split (fun c -> c = ',' || c = ' ') s in
 
625
    List.fold_left (fun pl p ->
 
626
                      try int_of_string p :: pl with _ -> pl) [] l
 
627
 
 
628
 (* Given a new key-val data, update the list of cookies accordingly
 
629
     (new cookie or update attributes of the current one). *)
 
630
  let add_key_val key value cl =
 
631
    if key <> "" && String.unsafe_get key 0 = '$' then
 
632
      (* Keys starting with '$' are for control; ignore the ones we do
 
633
         not know about. *)
 
634
      (match cl with
 
635
       | [] -> []
 
636
       | c :: _ ->
 
637
           (if key = "$Path" then c.path <- Some value
 
638
            else if key = "$Domain" then c.domain <- Some value
 
639
            else if key = "$Port" then
 
640
              c.ports <- Some (ports_of_string value));
 
641
           cl
 
642
      )
 
643
    else make key value :: cl
 
644
 
 
645
 
 
646
  let decode_range s start _end = 
 
647
    Netencoding.Url.decode ~pos:start ~len:(_end - start) s
 
648
 
 
649
  (* The difference between version 0 and version 1 cookies is that
 
650
     the latter start with $Version (present 1st or omitted).  Our
 
651
     decoding function can handle both versions transparently, so
 
652
     $Version is ignored.  In the absence of "=", the string is
 
653
     treated as the VALUE. *)
 
654
 
 
655
  (* [get_key cs i0 i len] scan the cookie string [cs] and get the
 
656
     key-val pairs. keys and values are stripped of heading and
 
657
     trailing spaces, except for quoted values. *)
 
658
  let rec get_key cs i0 i len cl =
 
659
    if i >= len then
 
660
      let value = decode_range cs i0 len in
 
661
      if value = "" then cl else make "" value :: cl
 
662
    else
 
663
      match String.unsafe_get cs i with
 
664
      | ',' | ';' ->
 
665
          (* No "=", interpret as a value as Mozilla does.  We choose
 
666
             this over MSIE which is reported to return just "n"
 
667
             instead of "n=" when the value is empty.  *)
 
668
          let cl = make "" (decode_range cs i0 i) :: cl in
 
669
          skip_space_before_key cs (i + 1) len cl
 
670
      | '=' ->
 
671
          let i1 = i + 1 in
 
672
          skip_value_space cs i1 len (decode_range cs i0 i) cl
 
673
      | c ->
 
674
          get_key cs i0 (i + 1) len cl
 
675
  and skip_space_before_key cs i len cl =
 
676
    if i >= len then cl
 
677
    else
 
678
      match String.unsafe_get cs i with
 
679
      | ' ' | '\t' | '\n' | '\r' -> skip_space_before_key cs (i + 1) len cl
 
680
      | _ -> get_key cs i i len cl
 
681
  and skip_value_space cs i len key cl =
 
682
    if i >= len then add_key_val key "" cl (* no value *)
 
683
    else
 
684
      match String.unsafe_get cs i with
 
685
      | ' ' | '\t' | '\n' | '\r' -> (* skip linear white space *)
 
686
          skip_value_space cs (i + 1) len key cl
 
687
      | '\"' ->
 
688
          get_quoted_value cs (i + 1) (i + 1) len key cl
 
689
      | _ ->
 
690
          get_value cs i i len key cl
 
691
  and get_value cs i0 i len key cl =
 
692
    if i >= len then add_key_val key (decode_range cs i0 len) cl
 
693
    else
 
694
      match String.unsafe_get cs i with
 
695
      | ',' | ';' ->
 
696
          let cl = add_key_val key (decode_range cs i0 i) cl in
 
697
          (* Usually there is a space after ';' to skip *)
 
698
          skip_space_before_key cs (i + 1) len cl
 
699
      | _ ->
 
700
          get_value cs i0 (i + 1) len key cl
 
701
  and get_quoted_value cs i0 i len key cl =
 
702
    if i >= len then (* quoted string not closed; try anyway *)
 
703
      add_key_val key (unescape_range cs i0 len) cl
 
704
    else
 
705
      match String.unsafe_get cs i with
 
706
      | '\\' -> get_quoted_value cs i0 (i + 2) len key cl
 
707
      | '\"' ->
 
708
          let cl = add_key_val key (unescape_range cs i0 i) cl in
 
709
          skip_to_next cs (i + 1) len cl
 
710
      | _ -> get_quoted_value cs i0 (i + 1) len key cl
 
711
  and skip_to_next cs i len cl =
 
712
    if i >= len then cl
 
713
    else
 
714
      match String.unsafe_get cs i with
 
715
      | ',' | ';' -> skip_space_before_key cs (i + 1) len cl
 
716
      | _ -> skip_to_next cs (i + 1) len cl
 
717
 
 
718
 
 
719
 
 
720
  let get_cookie_ct (http_header:#http_header_ro) =
 
721
    let cookies = http_header#multiple_field "Cookie" in
 
722
    let cl = List.fold_left
 
723
      (fun cl cs -> get_key cs 0 0 (String.length cs) cl) [] cookies in
 
724
    (* The order of cookies is important for the Netscape ones since
 
725
       "more specific path mapping should be sent before cookies with
 
726
       less specific path mappings" -- for those, there will be only a
 
727
       single "Cookie" line. *)
 
728
    List.rev cl
 
729
end
 
730
 
 
731
 
 
732
module Header = struct
 
733
  open Netmime
 
734
  open Mimestring
 
735
 
 
736
  (* As scanner we use the scanner for mail header fields from Mimestring. It
 
737
   * is very configurable.
 
738
   *)
 
739
 
 
740
  let std_special_chars =
 
741
        [ ','; ';'; '=' ]
 
742
          (* CHECK: Maybe we should add more characters, e.g. '@'. They are not
 
743
           * used in HTTP, and including them here would cause that field values
 
744
           * containing them are rejected. Maybe we want that.
 
745
           *)
 
746
 
 
747
  let scan_value ?(specials = std_special_chars) s = 
 
748
    let scanner = create_mime_scanner ~specials ~scan_options:[] s in
 
749
    Stream.from
 
750
      (fun _ ->
 
751
         Some (snd (scan_token scanner)))
 
752
        
 
753
  (* ---- Parser combinators for stream parsers: ---- *)
 
754
 
 
755
  let rec parse_comma_separated_list subparser stream =
 
756
    (* The [subparser] is required to return its value when it finds a
 
757
     * comma (i.e. [Special ','], or when it finds [End]. These tokens
 
758
     * must not be swallowed.
 
759
     *)
 
760
    match stream with parser
 
761
      | [< expr = subparser; rest = parse_comma_separated_rest subparser >] ->
 
762
          expr :: rest
 
763
      | [< >] ->
 
764
          []
 
765
 
 
766
  and parse_comma_separated_rest subparser stream =
 
767
    match stream with parser
 
768
      | [< '(Special ','); _ = parse_commas; list = parse_comma_separated_list subparser >] ->
 
769
          list
 
770
      | [< 'End >] ->
 
771
          []
 
772
 
 
773
  and parse_commas stream =
 
774
    match stream with parser
 
775
      | [< '(Special ','); _ = parse_commas >] ->
 
776
          ()
 
777
      | [< >] ->
 
778
          ()
 
779
 
 
780
  let merge_lists mh fieldparser fieldname =
 
781
    let fields = mh # multiple_field fieldname in
 
782
    if fields = [] then raise Not_found;
 
783
    List.flatten (List.map fieldparser fields)
 
784
 
 
785
  let parse_field mh fn_name f_parse fieldname =
 
786
    try
 
787
      let field = mh # field fieldname in
 
788
      f_parse (scan_value field)
 
789
    with
 
790
      | Stream.Failure
 
791
      | Stream.Error _ ->
 
792
          raise (Bad_header_field fn_name)
 
793
 
 
794
  let parse_comma_separated_field ?specials mh fn_name f_parse fieldname =
 
795
    let fieldparser field =
 
796
      try
 
797
        parse_comma_separated_list f_parse (scan_value ?specials field)
 
798
      with
 
799
        | Stream.Failure
 
800
        | Stream.Error _ ->
 
801
            raise (Bad_header_field fn_name) in
 
802
    merge_lists mh fieldparser fieldname
 
803
 
 
804
  (* ----- Common parsers/printer: ---- *)
 
805
              
 
806
  let parse_token_list mh fn_name fieldname =
 
807
    let parse_token stream =
 
808
      match stream with parser 
 
809
        | [< '(Atom tok) >] -> tok
 
810
    in
 
811
    parse_comma_separated_field mh fn_name parse_token fieldname
 
812
 
 
813
  let parse_token_or_qstring stream =
 
814
    match stream with parser
 
815
      | [< '(Atom tok) >] -> tok
 
816
      | [< '(QString v) >] -> v
 
817
 
 
818
  let rec parse_params stream =
 
819
    match stream with parser
 
820
      | [< '(Special ';'); 
 
821
           '(Atom name); '(Special '='); v = parse_token_or_qstring;
 
822
           rest = parse_params
 
823
        >]->
 
824
          (name,v) :: rest
 
825
      | [< >] ->
 
826
          []
 
827
 
 
828
  let parse_extended_token_list mh fn_name fieldname =
 
829
    (* token [ '=' (token|qstring) ( ';' token '=' (token|qstring) ) * ] *)
 
830
    let rec parse_extended_token stream =
 
831
      match stream with parser
 
832
        | [< '(Atom tok); extension = parse_equation >] ->
 
833
            ( match extension with
 
834
                  Some (eq_val, params) ->
 
835
                    (tok, Some eq_val, params)
 
836
                | None ->
 
837
                    (tok, None, [])
 
838
            )
 
839
    and parse_equation stream =
 
840
      match stream with parser
 
841
        | [< '(Special '='); v = parse_token_or_qstring; params = parse_params >] ->
 
842
            Some (v, params)
 
843
        | [< >] ->
 
844
            None
 
845
    in
 
846
    parse_comma_separated_field mh fn_name parse_extended_token fieldname
 
847
 
 
848
  let qstring_indicator_re =
 
849
    Netstring_str.regexp "[]\\\"()<>@,;:/[?={} \x00-\x1f\x7f]"
 
850
      (* Netstring_pcre.regexp "[\\\\\"()<>@,;:/[\\]?={} \\x00-\\x1f\\x7f]" *)
 
851
      
 
852
  let qstring_re = 
 
853
    Netstring_str.regexp "[\\\"]"
 
854
      (* Netstring_pcre.regexp "[\\\\\\\"]" *)
 
855
      
 
856
  let qstring_of_value s =
 
857
    (* Returns a qstring *)
 
858
      "\"" ^ Netstring_str.global_replace qstring_re "\\\\\\0" s ^ "\""
 
859
        (* Escape qstring_re with a backslash *)
 
860
 
 
861
  let string_of_value s =
 
862
    (* Returns a token or a qstring, depending on the value of [s] *)
 
863
    try 
 
864
      ignore(Netstring_str.search_forward qstring_indicator_re s 0);
 
865
      qstring_of_value s
 
866
    with
 
867
        Not_found -> s
 
868
 
 
869
  let string_of_params l =
 
870
    if l = [] then
 
871
      ""
 
872
    else
 
873
      ";" ^ 
 
874
      String.concat
 
875
        ";"
 
876
        (List.map
 
877
           (fun (n,s) -> 
 
878
              n ^ "=" ^ string_of_value s)
 
879
           l)
 
880
 
 
881
  let string_of_extended_token fn_name =
 
882
    function
 
883
      | (tok, None, []) ->
 
884
          tok
 
885
      | (tok, None, _) ->
 
886
          invalid_arg fn_name
 
887
      | (tok, Some eq_val, params) ->
 
888
          tok ^ "=" ^ eq_val ^ string_of_params params
 
889
 
 
890
  let parse_parameterized_token_list mh fn_name fieldname =
 
891
    (* token ( ';' token '=' (token|qstring) ) * *)
 
892
    let rec parse_parameterized_token stream =
 
893
      match stream with parser
 
894
        | [< '(Atom tok); params = parse_params >] ->
 
895
            (tok, params)
 
896
    in
 
897
    parse_comma_separated_field mh fn_name parse_parameterized_token fieldname
 
898
 
 
899
  let string_of_parameterized_token (tok, params) =
 
900
    tok ^ string_of_params params
 
901
 
 
902
  let q_split ( l : (string * (string * string) list) list )  
 
903
              : (string * (string * string) list * (string * string) list) list
 
904
              =
 
905
    (* Find the "q" param, and split [params] at that position *)
 
906
    let rec split params =
 
907
      match params with
 
908
        | [] -> ([], [])
 
909
        | ("q", q) :: rest -> ([], params)
 
910
        | other :: rest -> 
 
911
            let before, after = split rest in
 
912
            (other :: before), after
 
913
    in
 
914
    List.map
 
915
      (fun (tok, params) ->
 
916
         let before, after = split params in
 
917
         (tok, before, after))
 
918
      l
 
919
 
 
920
  let q_merge fn_name (tok, params, q_params) =
 
921
    if List.mem_assoc "q" params then invalid_arg fn_name;
 
922
    ( match q_params with
 
923
        | ( "q", _ ) :: _
 
924
        | [] ->
 
925
            (tok, (params @ q_params))
 
926
        | _ ->
 
927
            invalid_arg fn_name
 
928
    )
 
929
 
 
930
 
 
931
  let date_of_string fn_name s =
 
932
    try
 
933
      Netdate.parse_epoch s
 
934
    with
 
935
        Invalid_argument _ -> 
 
936
          raise(Bad_header_field fn_name)
 
937
 
 
938
  let string_of_date f =
 
939
    Netdate.format ~fmt:"%a, %d %b %Y %H:%M:%S GMT" (Netdate.create ~zone:0 f)
 
940
 
 
941
  let sort_by_q ?(default=1.0) toks_with_params =
 
942
    (* Sorts [toks_with_params] such that the highest [q] values come first.
 
943
     * Tokens with a [q] value of 0 are removed. Tokens without [q] value
 
944
     * are assumed to have the [default] value. This is also done with 
 
945
     * unparseable [q] values.
 
946
     *)
 
947
    List.map
 
948
      snd
 
949
      (List.stable_sort
 
950
         (fun (q1, tok_param1) (q2, tok_param2) ->
 
951
            Pervasives.compare q2 q1)
 
952
         (List.filter
 
953
            (fun (q, tok_param) ->
 
954
               q > 0.0)
 
955
            (List.map
 
956
               (fun (tok, params) ->
 
957
                  try 
 
958
                    let q_str = List.assoc "q" params in
 
959
                    (float_of_string q_str, (tok, params))
 
960
                  with
 
961
                    | Not_found -> (default, (tok, params))
 
962
                    | Failure _ -> (default, (tok, params))
 
963
               )
 
964
               toks_with_params)))
 
965
 
 
966
  let sort_by_q' ?default tok_with_params_and_qparams =
 
967
    List.map 
 
968
      (fun ((tok, tok_params), q_params) -> (tok, tok_params, q_params))
 
969
      (sort_by_q
 
970
         ?default
 
971
         (List.map
 
972
            (fun (tok, tok_params, q_params) -> ((tok, tok_params), q_params))
 
973
            tok_with_params_and_qparams))
 
974
 
 
975
  (* ---- The field accessors: ---- *)
 
976
 
 
977
  let get_accept mh =
 
978
    q_split
 
979
      (parse_parameterized_token_list mh "Nethttp.get_accept" "Accept")
 
980
 
 
981
  let set_accept mh av =
 
982
    let s =
 
983
      String.concat ","
 
984
      (List.map
 
985
         (fun triple -> 
 
986
            string_of_parameterized_token (q_merge "Nethttp.set_accept" triple))
 
987
         av) in
 
988
    mh # update_field "Accept" s
 
989
 
 
990
  let best_media_type mh supp =
 
991
    let supp' =
 
992
      (* All of [supp] not mentioned in the [Accept] field *)
 
993
      let toks = try get_accept mh with Not_found -> [] in
 
994
      List.filter (fun supp_type -> 
 
995
                     not (List.exists (fun (t,_,_) -> t=supp_type) toks)) supp
 
996
    in
 
997
    let rec find_best toks =
 
998
      match toks with
 
999
        | (tok, params, qparams) :: toks' ->
 
1000
            ( if List.mem tok supp then
 
1001
                (tok, params)
 
1002
              else
 
1003
                let (main_type, sub_type) = Mimestring.split_mime_type tok in
 
1004
                if sub_type = "*" then (
 
1005
                  try
 
1006
                    (List.find
 
1007
                       (fun supp_type ->
 
1008
                          (main_type = "*") || 
 
1009
                          (sub_type = "*" && 
 
1010
                              main_type = fst(Mimestring.split_mime_type supp_type))
 
1011
                       )
 
1012
                       supp',
 
1013
                     params)
 
1014
                  with
 
1015
                      Not_found -> find_best toks'
 
1016
                )
 
1017
                else find_best toks'
 
1018
            )
 
1019
        | [] ->
 
1020
            (* Nothing acceptable: *)
 
1021
            ("", [])
 
1022
    in
 
1023
    try
 
1024
      let mt_list = sort_by_q' (get_accept mh) in  (* or Not_found *)
 
1025
      find_best mt_list
 
1026
    with
 
1027
        Not_found -> ("*/*", [])
 
1028
 
 
1029
  let get_accept_charset mh =
 
1030
    parse_parameterized_token_list mh
 
1031
      "Nethttp.get_accept_charset" "Accept-Charset"
 
1032
 
 
1033
  let set_accept_charset mh l =
 
1034
    mh # update_field
 
1035
      "Accept-Charset" 
 
1036
      (String.concat "," (List.map string_of_parameterized_token l))
 
1037
 
 
1038
  let best_tok_of_list toks supp = 
 
1039
    let tok =
 
1040
      List.find
 
1041
        (fun tok -> tok = "*" || List.mem tok supp)
 
1042
        toks in
 
1043
    if tok = "*" then
 
1044
      List.find (fun tok -> not (List.mem tok toks)) supp
 
1045
    else
 
1046
      tok
 
1047
 
 
1048
  let best_charset mh supp =
 
1049
    try
 
1050
      let toks_with_params = get_accept_charset mh in  (* or Not_found *)
 
1051
      (* Special handling of ISO-8859-1: *)
 
1052
      let toks_with_params' =
 
1053
        if not(List.mem_assoc "*" toks_with_params) && 
 
1054
          not(List.exists
 
1055
                (fun (tok,_) -> String.lowercase tok = "iso-8859-1") 
 
1056
                toks_with_params) 
 
1057
        then
 
1058
          toks_with_params @ [ "ISO-8859-1", ["q", "1.0"] ]
 
1059
        else
 
1060
          toks_with_params in
 
1061
      let toks' = List.map fst (sort_by_q toks_with_params') in
 
1062
      best_tok_of_list toks' supp
 
1063
    with
 
1064
        Not_found -> "*"
 
1065
 
 
1066
  let get_accept_encoding mh =
 
1067
    parse_parameterized_token_list mh
 
1068
      "Nethttp.get_accept_encoding" "Accept-Encoding"
 
1069
 
 
1070
  let set_accept_encoding mh l =
 
1071
    mh # update_field
 
1072
      "Accept-Encoding" 
 
1073
      (String.concat "," (List.map string_of_parameterized_token l))
 
1074
 
 
1075
  let best_encoding mh supp =
 
1076
    try
 
1077
      let toks_with_params = sort_by_q (get_accept_encoding mh) in
 
1078
      best_tok_of_list (List.map fst toks_with_params) supp
 
1079
    with
 
1080
        Not_found -> "identity"
 
1081
 
 
1082
  let get_accept_language mh =
 
1083
    parse_parameterized_token_list mh
 
1084
      "Nethttp.get_accept_language" "Accept-Language"
 
1085
 
 
1086
  let set_accept_language mh l =
 
1087
    mh # update_field
 
1088
      "Accept-Language" 
 
1089
      (String.concat "," (List.map string_of_parameterized_token l))
 
1090
 
 
1091
  let get_accept_ranges mh =
 
1092
    parse_token_list mh "Nethttp.get_accept_ranges" "Accept-Ranges"
 
1093
 
 
1094
  let set_accept_ranges mh toks =
 
1095
    mh # update_field "Accept-Ranges" (String.concat "," toks)
 
1096
 
 
1097
  let get_age mh =
 
1098
    try
 
1099
      float_of_string (mh # field "Age")
 
1100
    with
 
1101
        Failure _ -> raise(Bad_header_field "Nethttp.get_age")
 
1102
 
 
1103
  let set_age mh v =
 
1104
    mh # update_field "Age" (Printf.sprintf "%0.f" v)
 
1105
 
 
1106
  let get_allow mh =
 
1107
    parse_token_list mh "Nethttp.get_allow" "Allow"
 
1108
 
 
1109
  let set_allow mh toks =
 
1110
    mh # update_field "Allow" (String.concat "," toks)
 
1111
 
 
1112
  let comma_split_re = Netstring_str.regexp "\\([ \t]*,\\)+[ \t]*"
 
1113
 
 
1114
  let comma_split =
 
1115
    Netstring_str.split comma_split_re
 
1116
      
 
1117
  let parse_opt_eq_token stream =
 
1118
    match stream with parser
 
1119
      | [< '(Special '='); 
 
1120
           v = (fun stream ->
 
1121
                  match stream with parser
 
1122
                    | [< '(Atom v) >] -> v
 
1123
                    | [< '(QString v) >] -> v);
 
1124
        >] -> Some v
 
1125
      | [< >] -> None
 
1126
 
 
1127
  let parse_cc_directive stream =
 
1128
    match stream with parser
 
1129
      | [< '(Atom "no-cache"); name_opt = parse_opt_eq_token >] ->
 
1130
          ( match name_opt with
 
1131
              | None -> `No_cache []
 
1132
              | Some names -> `No_cache(comma_split names)
 
1133
          )
 
1134
      | [< '(Atom "no-store") >] -> 
 
1135
          `No_store
 
1136
      | [< '(Atom "max-age"); '(Special '='); '(Atom seconds) >] ->
 
1137
          `Max_age(int_of_string seconds)
 
1138
      | [< '(Atom "max-stale"); delta_opt = parse_opt_eq_token >] ->
 
1139
          ( match delta_opt with
 
1140
              | None -> `Max_stale None
 
1141
              | Some seconds -> `Max_stale(Some(int_of_string seconds))
 
1142
          )
 
1143
      | [< '(Atom "min-fresh"); '(Special '='); '(Atom seconds) >] ->
 
1144
          `Min_fresh(int_of_string seconds)
 
1145
      | [< '(Atom "no-transform") >] -> 
 
1146
          `No_transform
 
1147
      | [< '(Atom "only-if-cached") >] -> 
 
1148
          `Only_if_cached
 
1149
      | [< '(Atom "public") >] -> 
 
1150
          `Public
 
1151
      | [< '(Atom "private"); name_opt = parse_opt_eq_token >] ->
 
1152
          ( match name_opt with
 
1153
              | None -> `Private []
 
1154
              | Some names -> `Private(comma_split names)
 
1155
          )
 
1156
      | [< '(Atom "must-revalidate") >] -> 
 
1157
          `Must_revalidate
 
1158
      | [< '(Atom "proxy-revalidate") >] -> 
 
1159
          `Proxy_revalidate
 
1160
      | [< '(Atom "s-maxage"); '(Special '='); '(Atom seconds)>] ->
 
1161
          `S_maxage(int_of_string seconds)
 
1162
      | [< '(Atom extension); val_opt = parse_opt_eq_token >] ->
 
1163
          `Extension(extension, val_opt)
 
1164
 
 
1165
  let get_cache_control mh =
 
1166
    parse_comma_separated_field
 
1167
      mh "Nethttp.get_cache_control" parse_cc_directive "Cache-Control"
 
1168
 
 
1169
  let set_cache_control mh l =
 
1170
    let s = 
 
1171
      String.concat ","
 
1172
        (List.map
 
1173
           (function
 
1174
              | `No_store -> "no-store"
 
1175
              | `Max_age n -> "max-age=" ^ string_of_int n
 
1176
              | `Max_stale None -> "max-stale"
 
1177
              | `Max_stale(Some n) -> "max-stale=" ^ string_of_int n
 
1178
              | `Min_fresh n -> "min-fresh=" ^ string_of_int n
 
1179
              | `No_transform -> "no-transform"
 
1180
              | `Only_if_cached -> "only-if-cached"
 
1181
              | `Public -> "public"
 
1182
              | `Private names -> "private=\"" ^ String.concat "," names ^ "\""
 
1183
              | `No_cache [] -> "no-cache"
 
1184
              | `No_cache names -> "no-cache=\"" ^ String.concat "," names ^ "\""
 
1185
              | `Must_revalidate -> "must-revalidate"
 
1186
              | `Proxy_revalidate -> "proxy-revalidate"
 
1187
              | `S_maxage n -> "s-maxage=" ^ string_of_int n
 
1188
              | `Extension(tok,None) -> tok
 
1189
              | `Extension(tok, Some param) -> tok ^ "=" ^ string_of_value param
 
1190
           )
 
1191
           l) in
 
1192
    mh # update_field "Cache-Control" s
 
1193
 
 
1194
  let get_connection mh =
 
1195
    parse_token_list mh "Nethttp.get_connection" "Connection"
 
1196
 
 
1197
  let set_connection mh toks =
 
1198
    mh # update_field "Connection" (String.concat "," toks)
 
1199
 
 
1200
  let get_content_encoding mh =
 
1201
    parse_token_list mh "Nethttp.get_content_encoding" "Content-Encoding"
 
1202
 
 
1203
  let set_content_encoding mh toks =
 
1204
    mh # update_field "Content-Encoding" (String.concat "," toks)
 
1205
 
 
1206
  let get_content_language mh =
 
1207
    parse_token_list mh "Nethttp.get_content_language" "Content-Language"
 
1208
 
 
1209
  let set_content_language mh toks =
 
1210
    mh # update_field "Content-Language" (String.concat "," toks)
 
1211
 
 
1212
  let get_content_length mh =
 
1213
    try
 
1214
      Int64.of_string (mh # field "Content-Length")
 
1215
    with
 
1216
        Failure _ -> raise (Bad_header_field "Nethttp.get_content_length")
 
1217
 
 
1218
  let set_content_length mh n =
 
1219
    mh # update_field "Content-Length" (Int64.to_string n)
 
1220
 
 
1221
  let get_content_location mh =
 
1222
    mh # field "Content-Location"
 
1223
 
 
1224
  let set_content_location mh s =
 
1225
    mh # update_field "Content-Location" s
 
1226
 
 
1227
  let get_content_md5 mh =
 
1228
    mh # field "Content-MD5"
 
1229
 
 
1230
  let set_content_md5 mh s =
 
1231
    mh # update_field "Content-MD5" s
 
1232
 
 
1233
  let parse_byte_range_resp_spec stream =
 
1234
    match stream with parser
 
1235
      | [< '(Special '*') >] -> 
 
1236
          None
 
1237
      | [< '(Atom first); '(Special '-'); '(Atom last) >] -> 
 
1238
          Some(Int64.of_string first, Int64.of_string last)
 
1239
 
 
1240
  let parse_byte_range_resp_length stream =
 
1241
    match stream with parser
 
1242
      | [< '(Special '*') >] -> 
 
1243
          None
 
1244
      | [< '(Atom length) >] ->
 
1245
          Some(Int64.of_string length)
 
1246
 
 
1247
  let parse_content_range_spec stream =
 
1248
    match stream with parser
 
1249
      | [< '(Atom "bytes"); 
 
1250
          br=parse_byte_range_resp_spec; 
 
1251
          '(Special '/');
 
1252
          l=parse_byte_range_resp_length;
 
1253
          'End
 
1254
         >] ->
 
1255
          `Bytes(br,l)
 
1256
 
 
1257
  let get_content_range mh =
 
1258
    let s = mh # field "Content-Range" in
 
1259
    let stream = scan_value ~specials:[ ','; ';'; '='; '*'; '-'; '/' ] s in
 
1260
    try
 
1261
      parse_content_range_spec stream 
 
1262
    with
 
1263
      | Stream.Failure
 
1264
      | Stream.Error _
 
1265
      | Failure _ ->
 
1266
          raise (Bad_header_field "Nethttp.get_content_range")
 
1267
 
 
1268
  let set_content_range mh (`Bytes(range_opt,length_opt)) =
 
1269
    let s = 
 
1270
      ( match range_opt with
 
1271
          | Some (first,last) -> Int64.to_string first ^ "-" ^ Int64.to_string last
 
1272
          | None -> "*"
 
1273
      ) ^ "/" ^ 
 
1274
      ( match length_opt with
 
1275
          | Some length -> Int64.to_string length
 
1276
          | None -> "*"
 
1277
      ) in
 
1278
    mh # update_field "Content-Range" s
 
1279
 
 
1280
  let get_content_type mh =
 
1281
    try
 
1282
      List.hd
 
1283
        (parse_parameterized_token_list mh
 
1284
           "Nethttp.get_content_type" "Content-Type")
 
1285
    with
 
1286
        Failure _ -> raise(Bad_header_field "Nethttp.get_content_type")
 
1287
 
 
1288
  let set_content_type mh (tok,params) =
 
1289
    mh # update_field
 
1290
      "Content-Type" 
 
1291
      (string_of_parameterized_token (tok,params))
 
1292
 
 
1293
  let get_date mh =
 
1294
    date_of_string "Nethttp.get_date" (mh # field "Date")
 
1295
 
 
1296
  let set_date mh d =
 
1297
    mh # update_field "Date" (string_of_date d)
 
1298
 
 
1299
  let parse_etag_token stream =
 
1300
    match stream with parser
 
1301
      | [< '(Atom "W"); '(Special '/'); '(QString e) >] ->
 
1302
          `Weak e
 
1303
      | [< '(QString e) >] ->
 
1304
          `Strong e
 
1305
 
 
1306
  let parse_etag stream =
 
1307
    match stream with parser
 
1308
      | [< etag=parse_etag_token; 'End >] -> etag
 
1309
 
 
1310
  let get_etag mh =
 
1311
    let s = mh # field "ETag" in
 
1312
    let stream = scan_value ~specials:[ ','; ';'; '='; '/' ] s in
 
1313
    try parse_etag stream
 
1314
    with
 
1315
      | Stream.Failure
 
1316
      | Stream.Error _
 
1317
      | Failure _ ->
 
1318
          raise (Bad_header_field "Nethttp.get_etag")
 
1319
 
 
1320
  let string_of_etag =
 
1321
    function
 
1322
      | `Weak s -> "W/" ^ qstring_of_value s
 
1323
      | `Strong s -> qstring_of_value s
 
1324
          
 
1325
  let set_etag mh etag =
 
1326
    mh # update_field "ETag" (string_of_etag etag)
 
1327
 
 
1328
  let get_expect mh =
 
1329
    parse_extended_token_list mh "Nethttp.get_expect" "Expect"
 
1330
 
 
1331
  let set_expect mh expectation =
 
1332
    mh # update_field "Expect" 
 
1333
      (String.concat "," 
 
1334
         (List.map (string_of_extended_token "Nethttp.set_expect") expectation))
 
1335
 
 
1336
  let get_expires mh =
 
1337
    date_of_string "Nethttp.get_expires" (mh # field "Expires")
 
1338
 
 
1339
  let set_expires mh d =
 
1340
    mh # update_field "Expires" (string_of_date d)
 
1341
 
 
1342
  let get_from mh =
 
1343
    mh # field "From"
 
1344
 
 
1345
  let set_from mh v =
 
1346
    mh # update_field "From" v
 
1347
 
 
1348
  let get_host mh =
 
1349
    let s = mh # field "Host" in
 
1350
    try
 
1351
      split_host_port s
 
1352
    with
 
1353
      | Failure _ -> raise(Bad_header_field "Nethttp.get_host")
 
1354
 
 
1355
  let set_host mh (host,port_opt) =
 
1356
    let s = 
 
1357
      host ^ 
 
1358
      ( match port_opt with Some p -> ":" ^ string_of_int p | None -> "") in
 
1359
    mh # update_field "Host" s
 
1360
 
 
1361
  let parse_etag_or_star_tok stream =
 
1362
    match stream with parser
 
1363
      | [< '(Special '*') >] -> None
 
1364
      | [< etag=parse_etag_token >] -> Some etag
 
1365
 
 
1366
  let get_etag_list mh fn_name fieldname =
 
1367
    let specials = [ ','; ';'; '='; '/'; '*' ] in
 
1368
    let l =
 
1369
      parse_comma_separated_field
 
1370
        ~specials mh fn_name parse_etag_or_star_tok fieldname in
 
1371
    if List.mem None l then
 
1372
      None
 
1373
    else
 
1374
      Some(List.map (function Some e -> e | None -> assert false) l)
 
1375
 
 
1376
  let set_etag_list mh fieldname l_opt =
 
1377
    let v =
 
1378
      match l_opt with
 
1379
        | None -> "*"
 
1380
        | Some l ->
 
1381
            String.concat "," (List.map string_of_etag l) in
 
1382
    mh # update_field fieldname v
 
1383
 
 
1384
  let get_if_match mh =
 
1385
    get_etag_list mh "Nethttp.get_if_match" "If-Match"
 
1386
 
 
1387
  let set_if_match mh =
 
1388
    set_etag_list mh "If-Match"
 
1389
 
 
1390
  let get_if_modified_since mh =
 
1391
    date_of_string "Nethttp.get_if_modified_since" (mh # field "If-Modified-Since")
 
1392
 
 
1393
  let set_if_modified_since mh d =
 
1394
    mh # update_field "If-Modified-Since" (string_of_date d)
 
1395
 
 
1396
  let get_if_none_match mh =
 
1397
    get_etag_list mh "Nethttp.get_if_none_match" "If-None-Match"
 
1398
 
 
1399
  let set_if_none_match mh =
 
1400
    set_etag_list mh "If-None-Match"
 
1401
 
 
1402
  let get_if_range mh =
 
1403
    let s = mh # field "If-Range" in
 
1404
    let stream = scan_value ~specials:[ ','; ';'; '='; '/' ] s in
 
1405
    try `Etag (parse_etag stream)
 
1406
    with
 
1407
      | Stream.Failure
 
1408
      | Stream.Error _
 
1409
      | Failure _ ->
 
1410
          `Date (date_of_string "Nethttp.get_if_range" s)
 
1411
  
 
1412
  let set_if_range mh v =
 
1413
    let s =
 
1414
      match v with
 
1415
        | `Etag e -> string_of_etag e
 
1416
        | `Date d -> string_of_date d in
 
1417
    mh # update_field "If-Range" s
 
1418
 
 
1419
  let get_if_unmodified_since mh =
 
1420
    date_of_string "Nethttp.get_if_unmodified_since" 
 
1421
      (mh # field "If-Unmodified-Since")
 
1422
 
 
1423
  let set_if_unmodified_since mh d =
 
1424
    mh # update_field "If-Unmodified-Since" (string_of_date d)
 
1425
 
 
1426
  let get_last_modified mh =
 
1427
    date_of_string "Nethttp.get_last_modified" (mh # field "Last-Modified")
 
1428
 
 
1429
  let set_last_modified mh d =
 
1430
    mh # update_field "Last-Modified" (string_of_date d)
 
1431
 
 
1432
  let get_location mh =
 
1433
    mh # field "Location"
 
1434
 
 
1435
  let set_location mh s =
 
1436
    mh # update_field "Location" s
 
1437
 
 
1438
  let get_max_forwards mh =
 
1439
    try
 
1440
      int_of_string (mh # field "Max-Forwards")
 
1441
    with
 
1442
        Failure _ -> raise(Bad_header_field "Nethttp.get_max_forwards")
 
1443
 
 
1444
  let set_max_forwards mh n =
 
1445
    mh # update_field "Max-Forwards" (string_of_int n)
 
1446
 
 
1447
  let parse_pragma_directive stream =
 
1448
    match stream with parser
 
1449
      | [< '(Atom tok); param_opt = parse_opt_eq_token >] -> (tok, param_opt)
 
1450
 
 
1451
  let get_pragma mh =
 
1452
    parse_comma_separated_field
 
1453
      mh "Nethttp.get_pragma" parse_pragma_directive "Pragma"
 
1454
 
 
1455
  let set_pragma mh l =
 
1456
    let s =
 
1457
      String.concat ","
 
1458
        (List.map 
 
1459
           (function
 
1460
              | (tok, None) -> tok
 
1461
              | (tok, Some param) -> tok ^ "=" ^ string_of_value param)
 
1462
           l) in
 
1463
    mh # update_field "Pragma" s
 
1464
 
 
1465
  let parse_opt_last_pos stream =
 
1466
    match stream with parser
 
1467
      | [< '(Atom last) >] -> Some(Int64.of_string last)
 
1468
      | [< >] -> None
 
1469
 
 
1470
  let rec parse_byte_range_spec stream =
 
1471
    match stream with parser
 
1472
      | [< '(Atom first); '(Special '-'); last=parse_opt_last_pos; 
 
1473
           r=parse_byte_range_spec_rest
 
1474
        >] ->
 
1475
          (Some (Int64.of_string first), last) :: r
 
1476
      | [< '(Special '-'); '(Atom suffix_length);
 
1477
           r=parse_byte_range_spec_rest
 
1478
        >] ->
 
1479
          (None, Some(Int64.of_string suffix_length)) :: r
 
1480
      | [< >] ->
 
1481
          []
 
1482
 
 
1483
  and parse_byte_range_spec_rest stream =
 
1484
    match stream with parser
 
1485
      | [< '(Special ','); _=parse_commas; r=parse_byte_range_spec >] -> r
 
1486
      | [< >] -> []
 
1487
 
 
1488
  let parse_ranges_specifier stream =
 
1489
    match stream with parser
 
1490
      | [< '(Atom "bytes"); 
 
1491
          '(Special '=');
 
1492
          r=parse_byte_range_spec; 
 
1493
          'End
 
1494
         >] ->
 
1495
          `Bytes r
 
1496
 
 
1497
  let get_range mh =
 
1498
    let s = mh # field "Range" in
 
1499
    let stream = scan_value ~specials:[ ','; ';'; '='; '*'; '-'; '/' ] s in
 
1500
    try
 
1501
      parse_ranges_specifier stream
 
1502
    with
 
1503
      | Stream.Failure
 
1504
      | Stream.Error _
 
1505
      | Failure _ ->
 
1506
          raise (Bad_header_field "Nethttp.get_range")
 
1507
 
 
1508
  let set_range mh (`Bytes l) =
 
1509
    let s =
 
1510
      "bytes=" ^ 
 
1511
      String.concat ","
 
1512
        (List.map
 
1513
           (function
 
1514
              | (Some first, Some last) ->
 
1515
                  Int64.to_string first ^ "-" ^ Int64.to_string last
 
1516
              | (Some first, None) ->
 
1517
                  Int64.to_string first ^ "-"
 
1518
              | (None, Some last) ->
 
1519
                  "-" ^ Int64.to_string last
 
1520
              | (None, None) ->
 
1521
                  invalid_arg "Nethttp.set_range")
 
1522
           l) in
 
1523
    mh # update_field "Range" s
 
1524
        
 
1525
  let get_referer mh =
 
1526
    mh # field "Referer"
 
1527
 
 
1528
  let get_referrer = get_referer
 
1529
 
 
1530
  let set_referer mh s =
 
1531
    mh # update_field "Referer" s
 
1532
 
 
1533
  let set_referrer = set_referer
 
1534
 
 
1535
  let get_retry_after mh =
 
1536
    let s = mh # field "Retry-After" in
 
1537
    try
 
1538
      `Seconds(int_of_string s)
 
1539
    with
 
1540
        Failure _ -> `Date(date_of_string "Nethttp.get_retry_after" s)
 
1541
 
 
1542
  let set_retry_after mh v =
 
1543
    let s =
 
1544
      match v with
 
1545
        | `Seconds n -> string_of_int n 
 
1546
        | `Date d -> string_of_date d in
 
1547
    mh # update_field "Retry-After" s
 
1548
 
 
1549
  let get_server mh =
 
1550
    mh # field "Server"
 
1551
 
 
1552
  let set_server mh name =
 
1553
    mh # update_field "Server" name
 
1554
 
 
1555
  let get_te mh =
 
1556
    q_split
 
1557
      (parse_parameterized_token_list mh "Nethttp.get_te" "TE")
 
1558
 
 
1559
  let set_te mh te =
 
1560
    let s =
 
1561
      String.concat ","
 
1562
      (List.map
 
1563
         (fun triple -> 
 
1564
            string_of_parameterized_token (q_merge "Nethttp.set_te" triple))
 
1565
         te) in
 
1566
    mh # update_field "TE" s
 
1567
 
 
1568
  let get_trailer mh =
 
1569
    parse_token_list mh "Nethttp.get_trailer" "Trailer"
 
1570
 
 
1571
  let set_trailer mh fields =
 
1572
    mh # update_field "Trailer" (String.concat "," fields)
 
1573
 
 
1574
  let get_transfer_encoding mh =
 
1575
    parse_parameterized_token_list mh "Nethttp.get_transfer_encoding" "Transfer-Encoding"
 
1576
 
 
1577
  let set_transfer_encoding mh te =
 
1578
    let s =
 
1579
      String.concat ","
 
1580
      (List.map string_of_parameterized_token te) in
 
1581
    mh # update_field "Transfer-Encoding" s
 
1582
 
 
1583
  let get_upgrade mh =
 
1584
    parse_token_list mh "Nethttp.get_upgrade" "Upgrade"
 
1585
 
 
1586
  let set_upgrade mh fields =
 
1587
    mh # update_field "Upgrade" (String.concat "," fields)
 
1588
 
 
1589
  let get_user_agent mh =
 
1590
    mh # field "User-Agent"
 
1591
 
 
1592
  let set_user_agent mh s =
 
1593
    mh # update_field "User-Agent" s
 
1594
 
 
1595
  let get_vary mh =
 
1596
    let l = parse_token_list mh "Nethttp.get_vary" "Vary" in
 
1597
    if List.mem "*" l then
 
1598
      `Star
 
1599
    else
 
1600
      `Fields l
 
1601
 
 
1602
  let set_vary mh v =
 
1603
    let s =
 
1604
      match v with
 
1605
        | `Star -> "*"
 
1606
        | `Fields l -> String.concat "," l in
 
1607
    mh # update_field "Vary" s
 
1608
 
 
1609
 
 
1610
  (* --- Authentication --- *)
 
1611
 
 
1612
  let parse_challenges mh fn_name fieldname =
 
1613
    let rec parse_auth_params stream =
 
1614
      match stream with parser
 
1615
        | [< '(Atom ap_name); '(Special '='); ap_val = parse_token_or_qstring;
 
1616
             rest = parse_auth_param_rest
 
1617
          >] ->
 
1618
            (ap_name, ap_val) :: rest
 
1619
 
 
1620
    and parse_auth_param_rest stream =
 
1621
      match Stream.npeek 3 stream with
 
1622
        | [ (Special ','); (Atom _); (Special '=') ] ->
 
1623
            ( match stream with parser
 
1624
                | [< '(Special ',');
 
1625
                     '(Atom ap_name); '(Special '='); 
 
1626
                     ap_val = parse_token_or_qstring;
 
1627
                     rest = parse_auth_param_rest
 
1628
                  >] ->
 
1629
                    (ap_name, ap_val) :: rest
 
1630
                | [< >] ->    (* should not happen... *)
 
1631
                    []
 
1632
            )
 
1633
        | _ ->
 
1634
            []
 
1635
 
 
1636
    and parse_challenge stream =
 
1637
      match stream with parser
 
1638
        | [< '(Atom auth_scheme); auth_params = parse_auth_params >] ->
 
1639
            (auth_scheme, auth_params)
 
1640
    in
 
1641
    parse_comma_separated_field mh fn_name parse_challenge fieldname
 
1642
      
 
1643
  let mk_challenges fields =
 
1644
    String.concat "," 
 
1645
      (List.map
 
1646
         (fun (auth_name, auth_params) ->
 
1647
            auth_name ^ " " ^ 
 
1648
              (String.concat ","
 
1649
                 (List.map
 
1650
                    (fun (p_name, p_val) ->
 
1651
                       p_name ^ "=" ^ string_of_value p_val)
 
1652
                    auth_params))
 
1653
         )
 
1654
         fields)
 
1655
 
 
1656
  let get_www_authenticate mh =
 
1657
    parse_challenges mh "Nethttp.get_www_authenticate" "WWW-Authenticate"
 
1658
 
 
1659
  let set_www_authenticate mh fields =
 
1660
    mh # update_field "WWW-Authenticate" (mk_challenges fields)
 
1661
 
 
1662
  let get_proxy_authenticate mh =
 
1663
    parse_challenges mh "Nethttp.get_proxy_authenticate" "Proxy-Authenticate"
 
1664
 
 
1665
  let set_proxy_authenticate mh fields =
 
1666
    mh # update_field "Proxy-Authenticate" (mk_challenges fields)
 
1667
 
 
1668
  let ws_re = Netstring_str.regexp "[ \t\r\n]+";;
 
1669
 
 
1670
  let parse_credentials mh fn_name fieldname =
 
1671
    let rec parse_creds stream =
 
1672
      match stream with parser
 
1673
        | [< '(Atom auth_name);
 
1674
             params = parse_auth_params
 
1675
          >] ->
 
1676
            (auth_name, params)
 
1677
             
 
1678
    and parse_auth_params stream =
 
1679
      match stream with parser
 
1680
        | [< '(Atom ap_name); '(Special '='); ap_val = parse_token_or_qstring;
 
1681
             rest = parse_auth_param_rest
 
1682
          >] ->
 
1683
            (ap_name, ap_val) :: rest
 
1684
 
 
1685
    and parse_auth_param_rest stream =
 
1686
      match stream with parser
 
1687
        | [< '(Special ',');
 
1688
             '(Atom ap_name); '(Special '='); 
 
1689
             ap_val = parse_token_or_qstring;
 
1690
             rest = parse_auth_param_rest
 
1691
          >] ->
 
1692
            (ap_name, ap_val) :: rest
 
1693
        | [< >] ->
 
1694
            []
 
1695
    in
 
1696
 
 
1697
    (* Basic authentication is a special case! *)
 
1698
    let v = mh # field fieldname in  (* or Not_found *)
 
1699
    match Netstring_str.split ws_re v with
 
1700
      | [ name; creds ] when String.lowercase name = "basic" ->
 
1701
          ("basic", ["credentials", creds])
 
1702
      | _ ->
 
1703
          parse_field mh fn_name parse_creds fieldname
 
1704
 
 
1705
  let mk_credentials (auth_name, auth_params) =
 
1706
    if String.lowercase auth_name = "basic" then (
 
1707
      let creds = 
 
1708
        try List.assoc "credentials" auth_params 
 
1709
        with Not_found -> 
 
1710
          failwith "Nethttp.mk_credentials: basic credentials not found" in
 
1711
      "Basic " ^ creds
 
1712
    )
 
1713
    else
 
1714
      auth_name ^ " " ^ 
 
1715
        (String.concat ","
 
1716
           (List.map
 
1717
              (fun (p_name, p_val) ->
 
1718
                 p_name ^ "=" ^ string_of_value p_val)
 
1719
              auth_params))
 
1720
 
 
1721
  let get_authorization mh =
 
1722
    parse_credentials mh "Nethttp.get_authorization" "authorization"
 
1723
 
 
1724
  let set_authorization mh v =
 
1725
    mh # update_field "Authorization" (mk_credentials v)
 
1726
 
 
1727
  let get_proxy_authorization mh = 
 
1728
    parse_credentials mh "Nethttp.get_proxy_authorization" "proxy-authorization"
 
1729
 
 
1730
  let set_proxy_authorization mh v = 
 
1731
    mh # update_field "Proxy-Authorization" (mk_credentials v)
 
1732
 
 
1733
 
 
1734
 
 
1735
 
 
1736
  (* --- Cookies --- *)
 
1737
 
 
1738
  exception No_equation of string
 
1739
 
 
1740
  let split_name_is_value s =
 
1741
    (* Recognizes a string "name=value" and returns the pair (name,value).
 
1742
     * If the string has the wrong format, the function will raise
 
1743
     * No_equation, and the argument of the exception is the unparseable
 
1744
     * string.
 
1745
     *)
 
1746
    try
 
1747
      let p = String.index s '=' in
 
1748
      (String.sub s 0 p, String.sub s (p+1) (String.length s - p - 1))
 
1749
    with
 
1750
        Not_found ->
 
1751
          raise(No_equation s)
 
1752
 
 
1753
  let spaces_at_beginning_re = Netstring_str.regexp "^[ \t\r\n]+"
 
1754
  let spaces_at_end_re = Netstring_str.regexp "[ \t\r\n]+$"
 
1755
 
 
1756
  let strip_spaces s = (* Remove leading and trailing spaces: *)
 
1757
    Netstring_str.global_replace 
 
1758
      spaces_at_end_re ""
 
1759
      (Netstring_str.global_replace 
 
1760
         spaces_at_beginning_re "" s)
 
1761
      
 
1762
  let split_cookies_re = Netstring_str.regexp "[ \t\r\n]*;[ \t\r\n]*" ;;
 
1763
 
 
1764
  let get_cookie mh =
 
1765
    let cstrings = mh # multiple_field "Cookie" in
 
1766
    (* Remove leading and trailing spaces: *)
 
1767
    let cstrings' = List.map strip_spaces cstrings in
 
1768
    let partss = List.map
 
1769
                   (fun cstring ->
 
1770
                      Netstring_str.split split_cookies_re cstring
 
1771
                   )
 
1772
                   cstrings' in
 
1773
    let parts = List.flatten partss in
 
1774
 
 
1775
    List.map
 
1776
      (fun part ->
 
1777
         let n,v =
 
1778
           try split_name_is_value part
 
1779
           with No_equation _ -> (part, "")
 
1780
                (* Because it is reported that MSIE returns just "n" instead
 
1781
                 * of "n=" when the value v is empty
 
1782
                 *)
 
1783
         in
 
1784
         let n_dec = Netencoding.Url.decode n in
 
1785
         let v_dec = Netencoding.Url.decode v in
 
1786
         (n_dec, v_dec)
 
1787
      )
 
1788
      parts
 
1789
 
 
1790
  let get_cookie_ct =
 
1791
    Cookie.get_cookie_ct
 
1792
 
 
1793
  let set_cookie mh l =
 
1794
    let s =
 
1795
      String.concat ";"
 
1796
        (List.map
 
1797
           (fun (n,v) -> 
 
1798
              Netencoding.Url.encode n ^ "=" ^ Netencoding.Url.encode v)
 
1799
           l) in
 
1800
    mh # update_field "Cookie" s
 
1801
 
 
1802
 
 
1803
    (* CHECK
 
1804
       let nv_re = Pcre.regexp "^([a-zA-Z0-9_.]+)(=(.*))?$"
 
1805
     *)
 
1806
  let nv_re = Netstring_str.regexp "^\\([^=;]+\\)\\(=\\(.*\\)\\)?$"
 
1807
 
 
1808
 
 
1809
  let get_set_cookie_1 s =
 
1810
    let nv_list =
 
1811
      List.map
 
1812
        (fun item ->
 
1813
           ( match Netstring_str.string_match nv_re item 0 with
 
1814
               | None ->
 
1815
                   raise(Bad_header_field "Nethttp.Header.get_set_cookie")
 
1816
               | Some m ->
 
1817
                   let name = Netstring_str.matched_group m 1 item in
 
1818
                   let value = 
 
1819
                     try Netstring_str.matched_group m 3 item
 
1820
                     with Not_found -> "" in
 
1821
                   (name, value)
 
1822
           )
 
1823
        )
 
1824
        (Netstring_str.split split_cookies_re s)
 
1825
    in
 
1826
    match nv_list with
 
1827
      | (n,v) :: params ->
 
1828
          let params = 
 
1829
            List.map (fun (n,v) -> (String.lowercase n, v)) params in
 
1830
          { cookie_name = Netencoding.Url.decode ~plus:false n;
 
1831
            cookie_value = Netencoding.Url.decode ~plus:false v;
 
1832
            cookie_expires = (try
 
1833
                                let exp_str = List.assoc "expires" params in
 
1834
                                Some(Netdate.since_epoch
 
1835
                                       (Netdate.parse exp_str))
 
1836
                              with
 
1837
                                | Not_found -> None);
 
1838
            cookie_domain = ( try
 
1839
                                Some(List.assoc "domain" params)
 
1840
                              with
 
1841
                                | Not_found -> None
 
1842
                            );
 
1843
            cookie_path = ( try
 
1844
                              Some(List.assoc "path" params)
 
1845
                            with
 
1846
                              | Not_found -> None
 
1847
                          );
 
1848
            cookie_secure = ( try
 
1849
                                List.mem_assoc "secure" params
 
1850
                              with
 
1851
                                | Not_found -> false
 
1852
                            )
 
1853
          }
 
1854
      | _ ->
 
1855
          raise(Bad_header_field "Nethttp.Header.get_set_cookie")
 
1856
 
 
1857
 
 
1858
  let get_set_cookie mh =
 
1859
    let fields = mh # multiple_field "Set-Cookie" in
 
1860
    List.map get_set_cookie_1 fields
 
1861
 
 
1862
 
 
1863
  let set_set_cookie mh l =
 
1864
    let cookie_fields =
 
1865
      List.map
 
1866
        (fun c ->
 
1867
           let enc_name  = Netencoding.Url.encode ~plus:false c.cookie_name in
 
1868
           let enc_value = Netencoding.Url.encode ~plus:false c.cookie_value in
 
1869
           enc_name ^ "=" ^ enc_value ^ 
 
1870
           ( match c.cookie_expires with
 
1871
                 None -> ""
 
1872
               | Some t -> 
 
1873
                   ";EXPIRES=" ^ Netdate.mk_usenet_date t
 
1874
           ) ^ 
 
1875
           (match c.cookie_domain with
 
1876
                None -> ""
 
1877
              | Some d ->
 
1878
                  ";DOMAIN=" ^ d
 
1879
           ) ^
 
1880
           (match c.cookie_path with
 
1881
                None -> ""
 
1882
              | Some p ->
 
1883
                  ";PATH=" ^ p 
 
1884
           ) ^
 
1885
           if c.cookie_secure then ";SECURE" else ""
 
1886
        )
 
1887
        l
 
1888
    in
 
1889
    mh # update_multiple_field "Set-cookie" cookie_fields
 
1890
 
 
1891
  let set_set_cookie_ct =
 
1892
    Cookie.set_set_cookie_ct
 
1893
 
 
1894
 
 
1895
end