|
1 (* Title: parse_scgi_req.ML |
|
2 Author: Timothy Bourke, NICTA |
|
3 |
|
4 Parses an SCGI (Simple Common Gateway Interface) header. |
|
5 See: http://python.ca/scgi/protocol.txt |
|
6 *) |
|
7 |
|
8 signature SCGI_REQ = |
|
9 sig |
|
10 exception InvalidReq of string |
|
11 |
|
12 datatype req_method = Get | Head | Post |
|
13 |
|
14 datatype t = Req of { |
|
15 path_info : string, |
|
16 path_translated : string, |
|
17 script_name : string, |
|
18 request_method : req_method, |
|
19 query_string : string Symtab.table, |
|
20 content_type : Mime.t option, |
|
21 environment : Word8VectorSlice.slice Symtab.table |
|
22 } |
|
23 |
|
24 val parse : BinIO.instream -> t * (BinIO.instream * int) |
|
25 val test : string -> unit |
|
26 |
|
27 val show : t -> string |
|
28 end; |
|
29 |
|
30 structure ScgiReq : SCGI_REQ = |
|
31 struct |
|
32 |
|
33 exception InvalidReq of string; |
|
34 |
|
35 datatype req_method = Get | Head | Post; |
|
36 |
|
37 datatype t = Req of { |
|
38 path_info : string, |
|
39 path_translated : string, |
|
40 script_name : string, |
|
41 request_method : req_method, |
|
42 query_string : string Symtab.table, |
|
43 content_type : Mime.t option, |
|
44 environment : Word8VectorSlice.slice Symtab.table |
|
45 }; |
|
46 |
|
47 fun parse_req_method "POST" = Post |
|
48 | parse_req_method "HEAD" = Head |
|
49 | parse_req_method _ = Get; |
|
50 |
|
51 fun show_req_method Get = "Get" |
|
52 | show_req_method Post = "Post" |
|
53 | show_req_method Head = "Head"; |
|
54 |
|
55 fun find_nulls (idx, 0wx00, idxs) = idx::idxs |
|
56 | find_nulls (_, _, idxs) = idxs; |
|
57 |
|
58 fun read_net_string fin = |
|
59 let |
|
60 fun read_size (_, NONE) = raise InvalidReq "Bad netstring length." |
|
61 | read_size (t, SOME 0wx3a) = t |
|
62 | read_size (t, SOME d) = |
|
63 let |
|
64 val n = (Word8.toInt d) - 0x30; |
|
65 in |
|
66 if n >=0 andalso n <= 9 |
|
67 then read_size (t * 10 + n, BinIO.input1 fin) |
|
68 else read_size (t, NONE) |
|
69 end; |
|
70 val size = read_size (0, BinIO.input1 fin); |
|
71 val payload = BinIO.inputN (fin, size); |
|
72 in |
|
73 (case (Word8Vector.length payload = size, BinIO.input1 fin) of |
|
74 (true, SOME 0wx2c) => payload |
|
75 | _ => raise InvalidReq "Bad netstring.") |
|
76 end; |
|
77 |
|
78 fun split_fields vec = |
|
79 let |
|
80 val nulls = ~1 :: (Word8Vector.foldri find_nulls [] vec); |
|
81 |
|
82 fun pr NONE = "NONE" |
|
83 | pr (SOME i) = "SOME " ^ Int.toString i; |
|
84 |
|
85 fun hd_diff (i1::i2::_) = SOME (i2 - i1 - 1) |
|
86 | hd_diff _ = NONE; |
|
87 |
|
88 fun slice [] = [] |
|
89 | slice (idxs as idx::idxs') = |
|
90 Word8VectorSlice.slice (vec, idx + 1, hd_diff idxs) :: slice idxs'; |
|
91 |
|
92 fun make_pairs (x::y::xys) = (Byte.unpackStringVec x, y) :: make_pairs xys |
|
93 | make_pairs _ = []; |
|
94 |
|
95 in make_pairs (slice nulls) end; |
|
96 |
|
97 fun parse fin = |
|
98 let |
|
99 val raw_fields = read_net_string fin; |
|
100 val fields = split_fields raw_fields; |
|
101 val env = Symtab.make fields; |
|
102 |
|
103 fun field name = |
|
104 (case Symtab.lookup env name of |
|
105 NONE => "" |
|
106 | SOME wv => Byte.unpackStringVec wv); |
|
107 |
|
108 val content_length = |
|
109 (the o Int.fromString o field) "CONTENT_LENGTH" |
|
110 handle _ => raise InvalidReq "Bad CONTENT_LENGTH"; |
|
111 |
|
112 val req = Req { |
|
113 path_info = field "PATH_INFO", |
|
114 path_translated = field "PATH_TRANSLATED", |
|
115 script_name = field "SCRIPT_NAME", |
|
116 request_method = (parse_req_method o field) "REQUEST_METHOD", |
|
117 query_string = (HttpUtil.parse_query_string o field) "QUERY_STRING", |
|
118 content_type = (Mime.parse_type o field) "CONTENT_TYPE", |
|
119 environment = env |
|
120 } |
|
121 |
|
122 in (req, (fin, content_length)) end; |
|
123 |
|
124 fun show (Req {path_info, path_translated, script_name, |
|
125 request_method, query_string, content_type, environment}) = |
|
126 let |
|
127 fun show_symtab to_string table = let |
|
128 fun show (n, v) r = ["\t", n, " = \"", to_string v, "\"\n"] @ r; |
|
129 in Symtab.fold show table [] end; |
|
130 in |
|
131 concat |
|
132 (["path_info: \"", path_info, "\"\n", |
|
133 "path_translated: \"", path_translated, "\"\n", |
|
134 "script_name: \"", script_name, "\"\n", |
|
135 "request_method: \"", show_req_method request_method, "\"\n", |
|
136 "query_string:\n"] |
|
137 @ |
|
138 show_symtab I query_string |
|
139 @ |
|
140 ["content_type: ", |
|
141 (the_default "" o Option.map Mime.show_type) content_type, "\n", |
|
142 "environment:\n"] |
|
143 @ |
|
144 show_symtab Byte.unpackStringVec environment) |
|
145 end; |
|
146 |
|
147 fun test path = |
|
148 let |
|
149 val fin = BinIO.openIn path; |
|
150 val (req, cs) = parse fin; |
|
151 val () = TextIO.print (show req); |
|
152 val () = |
|
153 BinIO.inputN cs |
|
154 |> Word8VectorSlice.full |
|
155 |> Byte.unpackStringVec |
|
156 |> TextIO.print; |
|
157 in BinIO.closeIn fin end; |
|
158 |
|
159 end; |
|
160 |