33823
|
1 |
(* Title: Tools/WWW_Find/scgi_req.ML
|
33817
|
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"
|
41491
|
83 |
| pr (SOME i) = "SOME " ^ string_of_int i;
|
33817
|
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 =
|
40152
|
109 |
(case Int.fromString (field "CONTENT_LENGTH") of
|
|
110 |
SOME n => n
|
|
111 |
| NONE => raise InvalidReq "Bad CONTENT_LENGTH");
|
33817
|
112 |
|
|
113 |
val req = Req {
|
|
114 |
path_info = field "PATH_INFO",
|
|
115 |
path_translated = field "PATH_TRANSLATED",
|
|
116 |
script_name = field "SCRIPT_NAME",
|
|
117 |
request_method = (parse_req_method o field) "REQUEST_METHOD",
|
|
118 |
query_string = (HttpUtil.parse_query_string o field) "QUERY_STRING",
|
|
119 |
content_type = (Mime.parse_type o field) "CONTENT_TYPE",
|
|
120 |
environment = env
|
|
121 |
}
|
|
122 |
|
|
123 |
in (req, (fin, content_length)) end;
|
|
124 |
|
|
125 |
fun show (Req {path_info, path_translated, script_name,
|
|
126 |
request_method, query_string, content_type, environment}) =
|
|
127 |
let
|
|
128 |
fun show_symtab to_string table = let
|
|
129 |
fun show (n, v) r = ["\t", n, " = \"", to_string v, "\"\n"] @ r;
|
|
130 |
in Symtab.fold show table [] end;
|
|
131 |
in
|
43703
|
132 |
implode
|
33817
|
133 |
(["path_info: \"", path_info, "\"\n",
|
|
134 |
"path_translated: \"", path_translated, "\"\n",
|
|
135 |
"script_name: \"", script_name, "\"\n",
|
|
136 |
"request_method: \"", show_req_method request_method, "\"\n",
|
|
137 |
"query_string:\n"]
|
|
138 |
@
|
|
139 |
show_symtab I query_string
|
|
140 |
@
|
|
141 |
["content_type: ",
|
|
142 |
(the_default "" o Option.map Mime.show_type) content_type, "\n",
|
|
143 |
"environment:\n"]
|
|
144 |
@
|
|
145 |
show_symtab Byte.unpackStringVec environment)
|
|
146 |
end;
|
|
147 |
|
|
148 |
fun test path =
|
|
149 |
let
|
|
150 |
val fin = BinIO.openIn path;
|
|
151 |
val (req, cs) = parse fin;
|
|
152 |
val () = TextIO.print (show req);
|
|
153 |
val () =
|
|
154 |
BinIO.inputN cs
|
|
155 |
|> Word8VectorSlice.full
|
|
156 |
|> Byte.unpackStringVec
|
|
157 |
|> TextIO.print;
|
|
158 |
in BinIO.closeIn fin end;
|
|
159 |
|
|
160 |
end;
|
|
161 |
|