| 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 | 
 |