-
Notifications
You must be signed in to change notification settings - Fork 1
/
http_request.ml
169 lines (150 loc) · 6.12 KB
/
http_request.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
(*
OCaml HTTP - do it yourself (fully OCaml) HTTP daemon
Copyright (C) <2002-2007> Stefano Zacchiroli <[email protected]>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Library General Public License as
published by the Free Software Foundation, version 2.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Library General Public License for more details.
You should have received a copy of the GNU Library General Public
License along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
USA
*)
open Printf;;
open Http_common;;
open Http_types;;
let debug_dump_request path params =
debug_print ("request path = " ^ path);
debug_print (
sprintf"request params = %s"
(String.concat ";"
(List.map (fun (h,v) -> String.concat "=" [h;v]) params)))
let auth_sep_RE = Pcre.regexp ":"
let basic_auth_RE = Pcre.regexp "^Basic\\s+"
exception Fallback;; (* used internally by request class *)
class request ic =
let (meth, uri, version) = Http_parser.parse_request_fst_line ic in
let uri_str = Neturl.string_of_url uri in
let path = Http_parser.parse_path uri in
let query_get_params = Http_parser.parse_query_get_params uri in
let (headers, body) =
(match version with
| None -> [], "" (* No version given, use request's 1st line only *)
| Some version -> (* Version specified, parse also headers and body *)
let headers =
List.map (* lowercase header names to ease lookups before having a
request object *)
(fun (h,v) -> (String.lowercase_ascii h, v))
(Http_parser.parse_headers ic) (* trailing \r\n consumed! *)
in
let body =
(* TODO fallback on size defined in Transfer-Encoding if
Content-Length isn't defined *)
match meth with
| `POST | `PUT | `TRACE ->
Buffer.contents
(try (* read only Content-Length bytes *)
let limit_raw =
(try
List.assoc "content-length" headers
with Not_found -> raise Fallback)
in
let limit =
(try (* TODO supports only a maximum content-length of 1Gb *)
int_of_string limit_raw
with Failure "int_of_string" ->
raise (Invalid_header ("content-length: " ^ limit_raw)))
in
Http_misc.buf_of_inchan ~limit ic
with Fallback -> Http_misc.buf_of_inchan ic) (* read until EOF *)
| _ ->
""
in
(headers, body))
in
let cookies =
try
let _hdr, raw_cookies =
List.find
(fun (hdr, _cookie) -> String.lowercase_ascii hdr = "cookie")
headers
in
Some (Http_parser.parse_cookies raw_cookies)
with
| Not_found -> None
| Malformed_cookies _ -> None
in
let query_post_params =
match meth with
| `POST ->
let ct = try List.assoc "content-type" headers with Not_found -> "" in
if ct = "application/x-www-form-urlencoded" then
Http_parser.split_query_params body
else []
| _ -> []
in
let params = query_post_params @ query_get_params in (* prefers POST params *)
let _ = debug_dump_request path params in
let (clisockaddr, srvsockaddr) =
(Http_misc.peername_of_in_channel ic, Http_misc.sockname_of_in_channel ic)
in
object (self)
inherit
Http_message.message ~body ~headers ~version ~clisockaddr ~srvsockaddr
val params_tbl =
let tbl = Hashtbl.create (List.length params) in
List.iter (fun (n,v) -> Hashtbl.add tbl n v) params;
tbl
method meth = meth
method uri = uri_str
method path = path
method param ?(meth: meth option) ?(default: string option) name =
try
(match meth with
| None -> Hashtbl.find params_tbl name
| Some `GET -> List.assoc name query_get_params
| Some `HEAD -> List.assoc name query_get_params
| Some `PUT -> List.assoc name query_get_params
| Some `DELETE -> List.assoc name query_get_params
| Some `OPTIONS -> List.assoc name query_get_params
| Some `TRACE -> List.assoc name query_get_params
| Some `POST -> List.assoc name query_post_params)
with Not_found ->
(match default with
| None -> raise (Param_not_found name)
| Some value -> value)
method paramAll ?meth name =
(match (meth: meth option) with
| None -> List.rev (Hashtbl.find_all params_tbl name)
| Some `GET -> Http_misc.list_assoc_all name query_get_params
| Some `HEAD -> Http_misc.list_assoc_all name query_get_params
| Some `PUT -> Http_misc.list_assoc_all name query_get_params
| Some `DELETE -> Http_misc.list_assoc_all name query_get_params
| Some `OPTIONS -> Http_misc.list_assoc_all name query_get_params
| Some `TRACE -> Http_misc.list_assoc_all name query_get_params
| Some `POST -> Http_misc.list_assoc_all name query_post_params)
method params = params
method params_GET = query_get_params
method params_POST = query_post_params
method cookies = cookies
method private fstLineToString =
let method_string = string_of_method self#meth in
match self#version with
| Some version ->
sprintf "%s %s %s" method_string self#uri (string_of_version version)
| None -> sprintf "%s %s" method_string self#uri
method authorization: auth_info option =
try
let credentials =
Netencoding.Base64.decode
(Pcre.replace ~rex:basic_auth_RE (self#header "authorization"))
in
debug_print ("HTTP Basic auth credentials: " ^ credentials);
(match Pcre.split ~rex:auth_sep_RE credentials with
| [username; password] -> Some (`Basic (username, password))
| l -> raise Exit)
with Header_not_found _ | Invalid_argument _ | Exit -> None
end