3 OCaml HTTP - do it yourself (fully OCaml) HTTP daemon
5 Copyright (C) <2002-2004> Stefano Zacchiroli <zack@cs.unibo.it>
7 This program is free software; you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by
9 the Free Software Foundation; either version 2 of the License, or
10 (at your option) any later version.
12 This program is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License
18 along with this program; if not, write to the Free Software
19 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 let (bindings_sep, binding_sep, pieces_sep, header_sep) =
29 (Pcre.regexp "&", Pcre.regexp "=", Pcre.regexp " ", Pcre.regexp ":")
30 let header_RE = Pcre.regexp "([^:]*):(.*)"
32 let url_decode url = Netencoding.Url.decode ~plus:true url
34 let split_query_params query =
35 let bindings = Pcre.split ~rex:bindings_sep query in
37 | [] -> raise (Malformed_query query)
41 match Pcre.split ~rex:binding_sep binding with
42 | [ ""; b ] -> (* '=b' *)
43 raise (Malformed_query_part (binding, query))
44 | [ a; b ] -> (* 'a=b' *) (url_decode a, url_decode b)
45 | [ a ] -> (* 'a=' || 'a' *) (url_decode a, "")
46 | _ -> raise (Malformed_query_part (binding, query)))
49 (** internal, used by generic_input_line *)
50 exception Line_completed;;
52 (** given an input channel and a separator
53 @return a line read from it (like Pervasives.input_line)
54 line is returned only after reading a separator string; separator string isn't
55 included in the returned value
56 TODO what about efficiency?, input is performed char-by-char
58 let generic_input_line ~sep ~ic =
59 let sep_len = String.length sep in
61 failwith ("Separator '" ^ sep ^ "' is too short!")
62 else (* valid separator *)
64 let sep_pointer = ref 0 in
67 if !sep_pointer >= String.length sep then (* line completed *)
69 else begin (* incomplete line: need to read more *)
70 let ch = input_char ic in
71 if ch = String.get sep !sep_pointer then (* next piece of sep *)
73 else begin (* useful char *)
74 for i = 0 to !sep_pointer - 1 do
75 line := !line ^ (String.make 1 (String.get sep i))
78 line := !line ^ (String.make 1 ch)
82 assert false (* unreacheable statement *)
83 with Line_completed -> !line
85 let patch_empty_path = function "" -> "/" | s -> s
86 let debug_dump_request path params =
89 "recevied request; path: %s; params: %s"
91 (String.concat ", " (List.map (fun (n, v) -> n ^ "=" ^ v) params)))
93 let parse_request_fst_line ic =
94 let request_line = generic_input_line ~sep:crlf ~ic in
95 debug_print (sprintf "HTTP request line (not yet parsed): %s" request_line);
97 (match Pcre.split ~rex:pieces_sep request_line with
98 | [ meth_raw; uri_raw ] -> (* ancient HTTP request line *)
99 (method_of_string meth_raw, (* method *)
100 Http_parser_sanity.url_of_string uri_raw, (* uri *)
101 None) (* no version given *)
102 | [ meth_raw; uri_raw; http_version_raw ] -> (* HTTP 1.{0,1} *)
103 (method_of_string meth_raw, (* method *)
104 Http_parser_sanity.url_of_string uri_raw, (* uri *)
105 Some (version_of_string http_version_raw)) (* version *)
106 | _ -> raise (Malformed_request request_line))
107 with Malformed_URL url -> raise (Malformed_request_URI url)
109 let parse_response_fst_line ic =
110 let response_line = generic_input_line ~sep:crlf ~ic in
111 debug_print (sprintf "HTTP response line (not yet parsed): %s" response_line);
113 (match Pcre.split ~rex:pieces_sep response_line with
114 | [ version_raw; code_raw; _ ] ->
115 (version_of_string version_raw, (* method *)
116 status_of_code (int_of_string code_raw)) (* status *)
117 | _ -> raise (Malformed_response response_line))
119 | Malformed_URL _ | Invalid_code _ | Failure "int_of_string" ->
120 raise (Malformed_response response_line)
122 let parse_path uri = patch_empty_path (String.concat "/" (Neturl.url_path uri))
123 let parse_query_get_params uri =
124 try (* act on HTTP encoded URIs *)
125 split_query_params (Neturl.url_query ~encoded:true uri)
128 let parse_headers ic =
129 (* consume also trailing "^\r\n$" line *)
130 let rec parse_headers' headers =
131 match generic_input_line ~sep:crlf ~ic with
132 | "" -> List.rev headers
136 Pcre.extract ~rex:header_RE line
137 with Not_found -> raise (Invalid_header line)
142 with Invalid_argument "Array.get" -> raise (Invalid_header line)
146 Http_parser_sanity.normalize_header_value subs.(2)
147 with Invalid_argument "Array.get" -> ""
149 Http_parser_sanity.heal_header (header, value);
150 parse_headers' ((header, value) :: headers))
154 let parse_request ic =
155 let (meth, uri, version) = parse_request_fst_line ic in
156 let path = parse_path uri in
157 let query_get_params = parse_query_get_params uri in
158 debug_dump_request path query_get_params;
159 (path, query_get_params)