Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 891 lines (820 sloc) 37.406 kb
4842019 @mdempsky first post
mdempsky authored
1 %% @author Bob Ippolito <bob@mochimedia.com>
2 %% @copyright 2007 Mochi Media, Inc.
52248ca @tuncer As discussed with @etrepum, add missing license headers
tuncer authored
3 %%
4 %% Permission is hereby granted, free of charge, to any person obtaining a
5 %% copy of this software and associated documentation files (the "Software"),
6 %% to deal in the Software without restriction, including without limitation
7 %% the rights to use, copy, modify, merge, publish, distribute, sublicense,
8 %% and/or sell copies of the Software, and to permit persons to whom the
9 %% Software is furnished to do so, subject to the following conditions:
10 %%
11 %% The above copyright notice and this permission notice shall be included in
12 %% all copies or substantial portions of the Software.
13 %%
14 %% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 %% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 %% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
17 %% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 %% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19 %% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20 %% DEALINGS IN THE SOFTWARE.
4842019 @mdempsky first post
mdempsky authored
21
22 %% @doc Utilities for parsing multipart/form-data.
23
24 -module(mochiweb_multipart).
25 -author('bob@mochimedia.com').
26
ee16b1e @mdempsky add mochiweb_multipart:parse_form/1 with a default file handler
mdempsky authored
27 -export([parse_form/1, parse_form/2]).
4842019 @mdempsky first post
mdempsky authored
28 -export([parse_multipart_request/2]).
8de2b0d @etrepum more coverage for mochiweb_multipart
etrepum authored
29 -export([parts_to_body/3, parts_to_multipart_body/4]).
30 -export([default_file_handler/2]).
4842019 @mdempsky first post
mdempsky authored
31
32 -define(CHUNKSIZE, 4096).
33
34 -record(mp, {state, boundary, length, buffer, callback, req}).
35
25c0f1d @etrepum new mochiweb_multipart:parse_form/2 API, add more MIME guessing, unquote...
etrepum authored
36 %% TODO: DOCUMENT THIS MODULE.
8de2b0d @etrepum more coverage for mochiweb_multipart
etrepum authored
37 %% @type key() = atom() | string() | binary().
38 %% @type value() = atom() | iolist() | integer().
39 %% @type header() = {key(), value()}.
40 %% @type bodypart() = {Start::integer(), End::integer(), Body::iolist()}.
41 %% @type formfile() = {Name::string(), ContentType::string(), Content::binary()}.
42 %% @type request().
43 %% @type file_handler() = (Filename::string(), ContentType::string()) -> file_handler_callback().
44 %% @type file_handler_callback() = (binary() | eof) -> file_handler_callback() | term().
45
46 %% @spec parts_to_body([bodypart()], ContentType::string(),
47 %% Size::integer()) -> {[header()], iolist()}
48 %% @doc Return {[header()], iolist()} representing the body for the given
49 %% parts, may be a single part or multipart.
b4c3c37 @etrepum more refactoring, new mochitemp module for creating and removing tempora...
etrepum authored
50 parts_to_body([{Start, End, Body}], ContentType, Size) ->
51 HeaderList = [{"Content-Type", ContentType},
52 {"Content-Range",
53 ["bytes ",
54 mochiweb_util:make_io(Start), "-", mochiweb_util:make_io(End),
55 "/", mochiweb_util:make_io(Size)]}],
56 {HeaderList, Body};
57 parts_to_body(BodyList, ContentType, Size) when is_list(BodyList) ->
8de2b0d @etrepum more coverage for mochiweb_multipart
etrepum authored
58 parts_to_multipart_body(BodyList, ContentType, Size,
59 mochihex:to_hex(crypto:rand_bytes(8))).
60
61 %% @spec parts_to_multipart_body([bodypart()], ContentType::string(),
62 %% Size::integer(), Boundary::string()) ->
63 %% {[header()], iolist()}
64 %% @doc Return {[header()], iolist()} representing the body for the given
65 %% parts, always a multipart response.
66 parts_to_multipart_body(BodyList, ContentType, Size, Boundary) ->
b4c3c37 @etrepum more refactoring, new mochitemp module for creating and removing tempora...
etrepum authored
67 HeaderList = [{"Content-Type",
68 ["multipart/byteranges; ",
69 "boundary=", Boundary]}],
70 MultiPartBody = multipart_body(BodyList, ContentType, Boundary, Size),
71
72 {HeaderList, MultiPartBody}.
73
8de2b0d @etrepum more coverage for mochiweb_multipart
etrepum authored
74 %% @spec multipart_body([bodypart()], ContentType::string(),
75 %% Boundary::string(), Size::integer()) -> iolist()
76 %% @doc Return the representation of a multipart body for the given [bodypart()].
b4c3c37 @etrepum more refactoring, new mochitemp module for creating and removing tempora...
etrepum authored
77 multipart_body([], _ContentType, Boundary, _Size) ->
78 ["--", Boundary, "--\r\n"];
79 multipart_body([{Start, End, Body} | BodyList], ContentType, Boundary, Size) ->
80 ["--", Boundary, "\r\n",
81 "Content-Type: ", ContentType, "\r\n",
82 "Content-Range: ",
83 "bytes ", mochiweb_util:make_io(Start), "-", mochiweb_util:make_io(End),
84 "/", mochiweb_util:make_io(Size), "\r\n\r\n",
85 Body, "\r\n"
86 | multipart_body(BodyList, ContentType, Boundary, Size)].
87
8de2b0d @etrepum more coverage for mochiweb_multipart
etrepum authored
88 %% @spec parse_form(request()) -> [{string(), string() | formfile()}]
89 %% @doc Parse a multipart form from the given request using the in-memory
90 %% default_file_handler/2.
ee16b1e @mdempsky add mochiweb_multipart:parse_form/1 with a default file handler
mdempsky authored
91 parse_form(Req) ->
92 parse_form(Req, fun default_file_handler/2).
93
8de2b0d @etrepum more coverage for mochiweb_multipart
etrepum authored
94 %% @spec parse_form(request(), F::file_handler()) -> [{string(), string() | term()}]
95 %% @doc Parse a multipart form from the given request using the given file_handler().
25c0f1d @etrepum new mochiweb_multipart:parse_form/2 API, add more MIME guessing, unquote...
etrepum authored
96 parse_form(Req, FileHandler) ->
97 Callback = fun (Next) -> parse_form_outer(Next, FileHandler, []) end,
98 {_, _, Res} = parse_multipart_request(Req, Callback),
99 Res.
100
101 parse_form_outer(eof, _, Acc) ->
102 lists:reverse(Acc);
103 parse_form_outer({headers, H}, FileHandler, State) ->
104 {"form-data", H1} = proplists:get_value("content-disposition", H),
105 Name = proplists:get_value("name", H1),
106 Filename = proplists:get_value("filename", H1),
107 case Filename of
108 undefined ->
109 fun (Next) ->
110 parse_form_value(Next, {Name, []}, FileHandler, State)
111 end;
112 _ ->
113 ContentType = proplists:get_value("content-type", H),
114 Handler = FileHandler(Filename, ContentType),
115 fun (Next) ->
116 parse_form_file(Next, {Name, Handler}, FileHandler, State)
117 end
118 end.
119
120 parse_form_value(body_end, {Name, Acc}, FileHandler, State) ->
121 Value = binary_to_list(iolist_to_binary(lists:reverse(Acc))),
122 State1 = [{Name, Value} | State],
123 fun (Next) -> parse_form_outer(Next, FileHandler, State1) end;
124 parse_form_value({body, Data}, {Name, Acc}, FileHandler, State) ->
125 Acc1 = [Data | Acc],
126 fun (Next) -> parse_form_value(Next, {Name, Acc1}, FileHandler, State) end.
127
128 parse_form_file(body_end, {Name, Handler}, FileHandler, State) ->
129 Value = Handler(eof),
130 State1 = [{Name, Value} | State],
131 fun (Next) -> parse_form_outer(Next, FileHandler, State1) end;
132 parse_form_file({body, Data}, {Name, Handler}, FileHandler, State) ->
133 H1 = Handler(Data),
134 fun (Next) -> parse_form_file(Next, {Name, H1}, FileHandler, State) end.
135
ee16b1e @mdempsky add mochiweb_multipart:parse_form/1 with a default file handler
mdempsky authored
136 default_file_handler(Filename, ContentType) ->
137 default_file_handler_1(Filename, ContentType, []).
138
139 default_file_handler_1(Filename, ContentType, Acc) ->
140 fun(eof) ->
141 Value = iolist_to_binary(lists:reverse(Acc)),
142 {Filename, ContentType, Value};
143 (Next) ->
144 default_file_handler_1(Filename, ContentType, [Next | Acc])
145 end.
146
4842019 @mdempsky first post
mdempsky authored
147 parse_multipart_request(Req, Callback) ->
148 %% TODO: Support chunked?
888ceb5 @kmwang add ability to handle combined content-length header.
kmwang authored
149 Length = list_to_integer(Req:get_combined_header_value("content-length")),
4842019 @mdempsky first post
mdempsky authored
150 Boundary = iolist_to_binary(
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
151 get_boundary(Req:get_header_value("content-type"))),
4842019 @mdempsky first post
mdempsky authored
152 Prefix = <<"\r\n--", Boundary/binary>>,
6d300db @etrepum patch from Kostis Sagonas, obtained by using tidier, a tool that tidies ...
etrepum authored
153 BS = byte_size(Boundary),
4842019 @mdempsky first post
mdempsky authored
154 Chunk = read_chunk(Req, Length),
6d300db @etrepum patch from Kostis Sagonas, obtained by using tidier, a tool that tidies ...
etrepum authored
155 Length1 = Length - byte_size(Chunk),
4842019 @mdempsky first post
mdempsky authored
156 <<"--", Boundary:BS/binary, "\r\n", Rest/binary>> = Chunk,
20b4bb4 @etrepum finally fixing http://code.google.com/p/mochiweb/issues/detail?id=22
etrepum authored
157 feed_mp(headers, flash_multipart_hack(#mp{boundary=Prefix,
158 length=Length1,
159 buffer=Rest,
160 callback=Callback,
161 req=Req})).
4842019 @mdempsky first post
mdempsky authored
162
163 parse_headers(<<>>) ->
164 [];
165 parse_headers(Binary) ->
166 parse_headers(Binary, []).
167
168 parse_headers(Binary, Acc) ->
169 case find_in_binary(<<"\r\n">>, Binary) of
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
170 {exact, N} ->
171 <<Line:N/binary, "\r\n", Rest/binary>> = Binary,
172 parse_headers(Rest, [split_header(Line) | Acc]);
173 not_found ->
174 lists:reverse([split_header(Binary) | Acc])
4842019 @mdempsky first post
mdempsky authored
175 end.
176
177 split_header(Line) ->
178 {Name, [$: | Value]} = lists:splitwith(fun (C) -> C =/= $: end,
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
179 binary_to_list(Line)),
4842019 @mdempsky first post
mdempsky authored
180 {string:to_lower(string:strip(Name)),
181 mochiweb_util:parse_header(Value)}.
182
183 read_chunk(Req, Length) when Length > 0 ->
184 case Length of
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
185 Length when Length < ?CHUNKSIZE ->
186 Req:recv(Length);
187 _ ->
188 Req:recv(?CHUNKSIZE)
4842019 @mdempsky first post
mdempsky authored
189 end.
190
191 read_more(State=#mp{length=Length, buffer=Buffer, req=Req}) ->
192 Data = read_chunk(Req, Length),
193 Buffer1 = <<Buffer/binary, Data/binary>>,
6d300db @etrepum patch from Kostis Sagonas, obtained by using tidier, a tool that tidies ...
etrepum authored
194 flash_multipart_hack(State#mp{length=Length - byte_size(Data),
20b4bb4 @etrepum finally fixing http://code.google.com/p/mochiweb/issues/detail?id=22
etrepum authored
195 buffer=Buffer1}).
196
197 flash_multipart_hack(State=#mp{length=0, buffer=Buffer, boundary=Prefix}) ->
198 %% http://code.google.com/p/mochiweb/issues/detail?id=22
199 %% Flash doesn't terminate multipart with \r\n properly so we fix it up here
200 PrefixSize = size(Prefix),
201 case size(Buffer) - (2 + PrefixSize) of
202 Seek when Seek >= 0 ->
203 case Buffer of
204 <<_:Seek/binary, Prefix:PrefixSize/binary, "--">> ->
205 Buffer1 = <<Buffer/binary, "\r\n">>,
206 State#mp{buffer=Buffer1};
207 _ ->
208 State
209 end;
210 _ ->
211 State
212 end;
213 flash_multipart_hack(State) ->
214 State.
4842019 @mdempsky first post
mdempsky authored
215
216 feed_mp(headers, State=#mp{buffer=Buffer, callback=Callback}) ->
217 {State1, P} = case find_in_binary(<<"\r\n\r\n">>, Buffer) of
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
218 {exact, N} ->
219 {State, N};
220 _ ->
221 S1 = read_more(State),
222 %% Assume headers must be less than ?CHUNKSIZE
223 {exact, N} = find_in_binary(<<"\r\n\r\n">>,
224 S1#mp.buffer),
225 {S1, N}
226 end,
4842019 @mdempsky first post
mdempsky authored
227 <<Headers:P/binary, "\r\n\r\n", Rest/binary>> = State1#mp.buffer,
228 NextCallback = Callback({headers, parse_headers(Headers)}),
229 feed_mp(body, State1#mp{buffer=Rest,
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
230 callback=NextCallback});
4842019 @mdempsky first post
mdempsky authored
231 feed_mp(body, State=#mp{boundary=Prefix, buffer=Buffer, callback=Callback}) ->
20b4bb4 @etrepum finally fixing http://code.google.com/p/mochiweb/issues/detail?id=22
etrepum authored
232 Boundary = find_boundary(Prefix, Buffer),
233 case Boundary of
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
234 {end_boundary, Start, Skip} ->
235 <<Data:Start/binary, _:Skip/binary, Rest/binary>> = Buffer,
236 C1 = Callback({body, Data}),
237 C2 = C1(body_end),
238 {State#mp.length, Rest, C2(eof)};
239 {next_boundary, Start, Skip} ->
240 <<Data:Start/binary, _:Skip/binary, Rest/binary>> = Buffer,
241 C1 = Callback({body, Data}),
242 feed_mp(headers, State#mp{callback=C1(body_end),
243 buffer=Rest});
244 {maybe, Start} ->
245 <<Data:Start/binary, Rest/binary>> = Buffer,
246 feed_mp(body, read_more(State#mp{callback=Callback({body, Data}),
247 buffer=Rest}));
248 not_found ->
249 {Data, Rest} = {Buffer, <<>>},
250 feed_mp(body, read_more(State#mp{callback=Callback({body, Data}),
251 buffer=Rest}))
4842019 @mdempsky first post
mdempsky authored
252 end.
253
254 get_boundary(ContentType) ->
255 {"multipart/form-data", Opts} = mochiweb_util:parse_header(ContentType),
256 case proplists:get_value("boundary", Opts) of
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
257 S when is_list(S) ->
258 S
4842019 @mdempsky first post
mdempsky authored
259 end.
260
f32ed32 Speed up the multipart parser
Daniel Abrahamsson authored
261 %% @spec find_in_binary(Pattern::binary(), Data::binary()) ->
605c48c @etrepum fix typo in mochiweb_multipart edoc
etrepum authored
262 %% {exact, N} | {partial, N, K} | not_found
f32ed32 Speed up the multipart parser
Daniel Abrahamsson authored
263 %% @doc Searches for the given pattern in the given binary.
264 find_in_binary(P, Data) when size(P) > 0 ->
265 PS = size(P),
266 DS = size(Data),
267 case DS - PS of
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
268 Last when Last < 0 ->
f32ed32 Speed up the multipart parser
Daniel Abrahamsson authored
269 partial_find(P, Data, 0, DS);
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
270 Last ->
52687de @danabr Use the new binary module to make multipart parsing 3x faster than with ...
danabr authored
271 case binary:match(Data, P) of
272 {Pos, _} -> {exact, Pos};
273 nomatch -> partial_find(P, Data, Last+1, PS-1)
274 end
4842019 @mdempsky first post
mdempsky authored
275 end.
276
277 partial_find(_B, _D, _N, 0) ->
278 not_found;
279 partial_find(B, D, N, K) ->
280 <<B1:K/binary, _/binary>> = B,
281 case D of
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
282 <<_Skip:N/binary, B1:K/binary>> ->
283 {partial, N, K};
284 _ ->
285 partial_find(B, D, 1 + N, K - 1)
4842019 @mdempsky first post
mdempsky authored
286 end.
287
288 find_boundary(Prefix, Data) ->
289 case find_in_binary(Prefix, Data) of
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
290 {exact, Skip} ->
291 PrefixSkip = Skip + size(Prefix),
292 case Data of
293 <<_:PrefixSkip/binary, "\r\n", _/binary>> ->
294 {next_boundary, Skip, size(Prefix) + 2};
295 <<_:PrefixSkip/binary, "--\r\n", _/binary>> ->
296 {end_boundary, Skip, size(Prefix) + 4};
297 _ when size(Data) < PrefixSkip + 4 ->
298 %% Underflow
299 {maybe, Skip};
300 _ ->
301 %% False positive
302 not_found
303 end;
304 {partial, Skip, Length} when (Skip + Length) =:= size(Data) ->
34a7654 @etrepum boundary edge case
etrepum authored
305 %% Underflow
306 {maybe, Skip};
307 _ ->
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
308 not_found
4842019 @mdempsky first post
mdempsky authored
309 end.
310
ca962ce @etrepum refactor to use eunit, generate code coverage, update Makefiles and skel...
etrepum authored
311 %%
312 %% Tests
313 %%
314 -ifdef(TEST).
cb46038 @etrepum include eunit only if TEST is already defined
etrepum authored
315 -include_lib("eunit/include/eunit.hrl").
ca962ce @etrepum refactor to use eunit, generate code coverage, update Makefiles and skel...
etrepum authored
316
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
317 ssl_cert_opts() ->
318 EbinDir = filename:dirname(code:which(?MODULE)),
319 CertDir = filename:join([EbinDir, "..", "support", "test-materials"]),
320 CertFile = filename:join(CertDir, "test_ssl_cert.pem"),
321 KeyFile = filename:join(CertDir, "test_ssl_key.pem"),
322 [{certfile, CertFile}, {keyfile, KeyFile}].
323
324 with_socket_server(Transport, ServerFun, ClientFun) ->
325 ServerOpts0 = [{ip, "127.0.0.1"}, {port, 0}, {loop, ServerFun}],
326 ServerOpts = case Transport of
327 plain ->
328 ServerOpts0;
329 ssl ->
330 ServerOpts0 ++ [{ssl, true}, {ssl_opts, ssl_cert_opts()}]
331 end,
7972b2b @etrepum implement start_link/1 exports and add {link, false} option to start/1
etrepum authored
332 {ok, Server} = mochiweb_socket_server:start_link(ServerOpts),
4842019 @mdempsky first post
mdempsky authored
333 Port = mochiweb_socket_server:get(Server, port),
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
334 ClientOpts = [binary, {active, false}],
335 {ok, Client} = case Transport of
336 plain ->
337 gen_tcp:connect("127.0.0.1", Port, ClientOpts);
338 ssl ->
339 ClientOpts1 = [{ssl_imp, new} | ClientOpts],
340 {ok, SslSocket} = ssl:connect("127.0.0.1", Port, ClientOpts1),
341 {ok, {ssl, SslSocket}}
342 end,
4842019 @mdempsky first post
mdempsky authored
343 Res = (catch ClientFun(Client)),
344 mochiweb_socket_server:stop(Server),
345 Res.
346
347 fake_request(Socket, ContentType, Length) ->
348 mochiweb_request:new(Socket,
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
349 'POST',
350 "/multipart",
351 {1,1},
352 mochiweb_headers:make(
353 [{"content-type", ContentType},
354 {"content-length", Length}])).
4842019 @mdempsky first post
mdempsky authored
355
d69cd76 @etrepum increase test code coverage
etrepum authored
356 test_callback({body, <<>>}, Rest=[body_end | _]) ->
357 %% When expecting the body_end we might get an empty binary
358 fun (Next) -> test_callback(Next, Rest) end;
359 test_callback({body, Got}, [{body, Expect} | Rest]) when Got =/= Expect ->
360 %% Partial response
361 GotSize = size(Got),
362 <<Got:GotSize/binary, Expect1/binary>> = Expect,
363 fun (Next) -> test_callback(Next, [{body, Expect1} | Rest]) end;
364 test_callback(Got, [Expect | Rest]) ->
365 ?assertEqual(Got, Expect),
25c0f1d @etrepum new mochiweb_multipart:parse_form/2 API, add more MIME guessing, unquote...
etrepum authored
366 case Rest of
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
367 [] ->
368 ok;
369 _ ->
370 fun (Next) -> test_callback(Next, Rest) end
d69cd76 @etrepum increase test code coverage
etrepum authored
371 end.
4842019 @mdempsky first post
mdempsky authored
372
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
373 parse3_http_test() ->
374 parse3(plain).
375
376 parse3_https_test() ->
377 parse3(ssl).
378
379 parse3(Transport) ->
4842019 @mdempsky first post
mdempsky authored
380 ContentType = "multipart/form-data; boundary=---------------------------7386909285754635891697677882",
381 BinContent = <<"-----------------------------7386909285754635891697677882\r\nContent-Disposition: form-data; name=\"hidden\"\r\n\r\nmultipart message\r\n-----------------------------7386909285754635891697677882\r\nContent-Disposition: form-data; name=\"file\"; filename=\"test_file.txt\"\r\nContent-Type: text/plain\r\n\r\nWoo multiline text file\n\nLa la la\r\n-----------------------------7386909285754635891697677882--\r\n">>,
382 Expect = [{headers,
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
383 [{"content-disposition",
384 {"form-data", [{"name", "hidden"}]}}]},
385 {body, <<"multipart message">>},
386 body_end,
387 {headers,
388 [{"content-disposition",
389 {"form-data", [{"name", "file"}, {"filename", "test_file.txt"}]}},
390 {"content-type", {"text/plain", []}}]},
391 {body, <<"Woo multiline text file\n\nLa la la">>},
392 body_end,
393 eof],
4842019 @mdempsky first post
mdempsky authored
394 TestCallback = fun (Next) -> test_callback(Next, Expect) end,
ae0b9aa @ddosia Add recbuf config option.
ddosia authored
395 ServerFun = fun (Socket, _Opts) ->
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
396 ok = mochiweb_socket:send(Socket, BinContent),
52182ce @etrepum convert tabs to spaces
etrepum authored
397 exit(normal)
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
398 end,
4842019 @mdempsky first post
mdempsky authored
399 ClientFun = fun (Socket) ->
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
400 Req = fake_request(Socket, ContentType,
6d300db @etrepum patch from Kostis Sagonas, obtained by using tidier, a tool that tidies ...
etrepum authored
401 byte_size(BinContent)),
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
402 Res = parse_multipart_request(Req, TestCallback),
403 {0, <<>>, ok} = Res,
404 ok
405 end,
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
406 ok = with_socket_server(Transport, ServerFun, ClientFun),
4842019 @mdempsky first post
mdempsky authored
407 ok.
408
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
409 parse2_http_test() ->
410 parse2(plain).
25c0f1d @etrepum new mochiweb_multipart:parse_form/2 API, add more MIME guessing, unquote...
etrepum authored
411
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
412 parse2_https_test() ->
413 parse2(ssl).
414
415 parse2(Transport) ->
4842019 @mdempsky first post
mdempsky authored
416 ContentType = "multipart/form-data; boundary=---------------------------6072231407570234361599764024",
417 BinContent = <<"-----------------------------6072231407570234361599764024\r\nContent-Disposition: form-data; name=\"hidden\"\r\n\r\nmultipart message\r\n-----------------------------6072231407570234361599764024\r\nContent-Disposition: form-data; name=\"file\"; filename=\"\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n-----------------------------6072231407570234361599764024--\r\n">>,
418 Expect = [{headers,
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
419 [{"content-disposition",
420 {"form-data", [{"name", "hidden"}]}}]},
421 {body, <<"multipart message">>},
422 body_end,
423 {headers,
424 [{"content-disposition",
425 {"form-data", [{"name", "file"}, {"filename", ""}]}},
426 {"content-type", {"application/octet-stream", []}}]},
427 {body, <<>>},
428 body_end,
429 eof],
4842019 @mdempsky first post
mdempsky authored
430 TestCallback = fun (Next) -> test_callback(Next, Expect) end,
ae0b9aa @ddosia Add recbuf config option.
ddosia authored
431 ServerFun = fun (Socket, _Opts) ->
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
432 ok = mochiweb_socket:send(Socket, BinContent),
52182ce @etrepum convert tabs to spaces
etrepum authored
433 exit(normal)
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
434 end,
4842019 @mdempsky first post
mdempsky authored
435 ClientFun = fun (Socket) ->
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
436 Req = fake_request(Socket, ContentType,
6d300db @etrepum patch from Kostis Sagonas, obtained by using tidier, a tool that tidies ...
etrepum authored
437 byte_size(BinContent)),
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
438 Res = parse_multipart_request(Req, TestCallback),
439 {0, <<>>, ok} = Res,
440 ok
441 end,
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
442 ok = with_socket_server(Transport, ServerFun, ClientFun),
4842019 @mdempsky first post
mdempsky authored
443 ok.
444
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
445 parse_form_http_test() ->
446 do_parse_form(plain).
447
448 parse_form_https_test() ->
449 do_parse_form(ssl).
450
451 do_parse_form(Transport) ->
25c0f1d @etrepum new mochiweb_multipart:parse_form/2 API, add more MIME guessing, unquote...
etrepum authored
452 ContentType = "multipart/form-data; boundary=AaB03x",
453 "AaB03x" = get_boundary(ContentType),
454 Content = mochiweb_util:join(
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
455 ["--AaB03x",
456 "Content-Disposition: form-data; name=\"submit-name\"",
457 "",
458 "Larry",
459 "--AaB03x",
460 "Content-Disposition: form-data; name=\"files\";"
461 ++ "filename=\"file1.txt\"",
462 "Content-Type: text/plain",
463 "",
464 "... contents of file1.txt ...",
465 "--AaB03x--",
466 ""], "\r\n"),
25c0f1d @etrepum new mochiweb_multipart:parse_form/2 API, add more MIME guessing, unquote...
etrepum authored
467 BinContent = iolist_to_binary(Content),
ae0b9aa @ddosia Add recbuf config option.
ddosia authored
468 ServerFun = fun (Socket, _Opts) ->
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
469 ok = mochiweb_socket:send(Socket, BinContent),
52182ce @etrepum convert tabs to spaces
etrepum authored
470 exit(normal)
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
471 end,
25c0f1d @etrepum new mochiweb_multipart:parse_form/2 API, add more MIME guessing, unquote...
etrepum authored
472 ClientFun = fun (Socket) ->
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
473 Req = fake_request(Socket, ContentType,
6d300db @etrepum patch from Kostis Sagonas, obtained by using tidier, a tool that tidies ...
etrepum authored
474 byte_size(BinContent)),
ee16b1e @mdempsky add mochiweb_multipart:parse_form/1 with a default file handler
mdempsky authored
475 Res = parse_form(Req),
25c0f1d @etrepum new mochiweb_multipart:parse_form/2 API, add more MIME guessing, unquote...
etrepum authored
476 [{"submit-name", "Larry"},
477 {"files", {"file1.txt", {"text/plain",[]},
478 <<"... contents of file1.txt ...">>}
479 }] = Res,
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
480 ok
481 end,
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
482 ok = with_socket_server(Transport, ServerFun, ClientFun),
25c0f1d @etrepum new mochiweb_multipart:parse_form/2 API, add more MIME guessing, unquote...
etrepum authored
483 ok.
484
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
485 parse_http_test() ->
486 do_parse(plain).
487
488 parse_https_test() ->
489 do_parse(ssl).
490
491 do_parse(Transport) ->
4842019 @mdempsky first post
mdempsky authored
492 ContentType = "multipart/form-data; boundary=AaB03x",
493 "AaB03x" = get_boundary(ContentType),
494 Content = mochiweb_util:join(
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
495 ["--AaB03x",
496 "Content-Disposition: form-data; name=\"submit-name\"",
497 "",
498 "Larry",
499 "--AaB03x",
500 "Content-Disposition: form-data; name=\"files\";"
501 ++ "filename=\"file1.txt\"",
502 "Content-Type: text/plain",
503 "",
504 "... contents of file1.txt ...",
505 "--AaB03x--",
506 ""], "\r\n"),
4842019 @mdempsky first post
mdempsky authored
507 BinContent = iolist_to_binary(Content),
508 Expect = [{headers,
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
509 [{"content-disposition",
510 {"form-data", [{"name", "submit-name"}]}}]},
511 {body, <<"Larry">>},
512 body_end,
513 {headers,
514 [{"content-disposition",
515 {"form-data", [{"name", "files"}, {"filename", "file1.txt"}]}},
516 {"content-type", {"text/plain", []}}]},
517 {body, <<"... contents of file1.txt ...">>},
518 body_end,
519 eof],
4842019 @mdempsky first post
mdempsky authored
520 TestCallback = fun (Next) -> test_callback(Next, Expect) end,
ae0b9aa @ddosia Add recbuf config option.
ddosia authored
521 ServerFun = fun (Socket, _Opts) ->
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
522 ok = mochiweb_socket:send(Socket, BinContent),
52182ce @etrepum convert tabs to spaces
etrepum authored
523 exit(normal)
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
524 end,
4842019 @mdempsky first post
mdempsky authored
525 ClientFun = fun (Socket) ->
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
526 Req = fake_request(Socket, ContentType,
6d300db @etrepum patch from Kostis Sagonas, obtained by using tidier, a tool that tidies ...
etrepum authored
527 byte_size(BinContent)),
3f7ef81 @etrepum detab source, chunked-like responses for HTTP/1.0 clients
etrepum authored
528 Res = parse_multipart_request(Req, TestCallback),
529 {0, <<>>, ok} = Res,
530 ok
531 end,
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
532 ok = with_socket_server(Transport, ServerFun, ClientFun),
4842019 @mdempsky first post
mdempsky authored
533 ok.
534
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
535 parse_partial_body_boundary_http_test() ->
536 parse_partial_body_boundary(plain).
537
538 parse_partial_body_boundary_https_test() ->
539 parse_partial_body_boundary(ssl).
540
541 parse_partial_body_boundary(Transport) ->
d69cd76 @etrepum increase test code coverage
etrepum authored
542 Boundary = string:copies("$", 2048),
543 ContentType = "multipart/form-data; boundary=" ++ Boundary,
544 ?assertEqual(Boundary, get_boundary(ContentType)),
545 Content = mochiweb_util:join(
546 ["--" ++ Boundary,
547 "Content-Disposition: form-data; name=\"submit-name\"",
548 "",
549 "Larry",
550 "--" ++ Boundary,
551 "Content-Disposition: form-data; name=\"files\";"
552 ++ "filename=\"file1.txt\"",
553 "Content-Type: text/plain",
554 "",
555 "... contents of file1.txt ...",
556 "--" ++ Boundary ++ "--",
557 ""], "\r\n"),
558 BinContent = iolist_to_binary(Content),
559 Expect = [{headers,
560 [{"content-disposition",
561 {"form-data", [{"name", "submit-name"}]}}]},
562 {body, <<"Larry">>},
563 body_end,
564 {headers,
565 [{"content-disposition",
566 {"form-data", [{"name", "files"}, {"filename", "file1.txt"}]}},
567 {"content-type", {"text/plain", []}}
568 ]},
569 {body, <<"... contents of file1.txt ...">>},
570 body_end,
571 eof],
572 TestCallback = fun (Next) -> test_callback(Next, Expect) end,
ae0b9aa @ddosia Add recbuf config option.
ddosia authored
573 ServerFun = fun (Socket, _Opts) ->
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
574 ok = mochiweb_socket:send(Socket, BinContent),
52182ce @etrepum convert tabs to spaces
etrepum authored
575 exit(normal)
d69cd76 @etrepum increase test code coverage
etrepum authored
576 end,
577 ClientFun = fun (Socket) ->
578 Req = fake_request(Socket, ContentType,
579 byte_size(BinContent)),
580 Res = parse_multipart_request(Req, TestCallback),
581 {0, <<>>, ok} = Res,
582 ok
583 end,
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
584 ok = with_socket_server(Transport, ServerFun, ClientFun),
d69cd76 @etrepum increase test code coverage
etrepum authored
585 ok.
586
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
587 parse_large_header_http_test() ->
588 parse_large_header(plain).
d69cd76 @etrepum increase test code coverage
etrepum authored
589
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
590 parse_large_header_https_test() ->
591 parse_large_header(ssl).
592
593 parse_large_header(Transport) ->
d69cd76 @etrepum increase test code coverage
etrepum authored
594 ContentType = "multipart/form-data; boundary=AaB03x",
595 "AaB03x" = get_boundary(ContentType),
596 Content = mochiweb_util:join(
597 ["--AaB03x",
598 "Content-Disposition: form-data; name=\"submit-name\"",
599 "",
600 "Larry",
601 "--AaB03x",
602 "Content-Disposition: form-data; name=\"files\";"
603 ++ "filename=\"file1.txt\"",
604 "Content-Type: text/plain",
605 "x-large-header: " ++ string:copies("%", 4096),
606 "",
607 "... contents of file1.txt ...",
608 "--AaB03x--",
609 ""], "\r\n"),
610 BinContent = iolist_to_binary(Content),
611 Expect = [{headers,
612 [{"content-disposition",
613 {"form-data", [{"name", "submit-name"}]}}]},
614 {body, <<"Larry">>},
615 body_end,
616 {headers,
617 [{"content-disposition",
618 {"form-data", [{"name", "files"}, {"filename", "file1.txt"}]}},
619 {"content-type", {"text/plain", []}},
620 {"x-large-header", {string:copies("%", 4096), []}}
621 ]},
622 {body, <<"... contents of file1.txt ...">>},
623 body_end,
624 eof],
625 TestCallback = fun (Next) -> test_callback(Next, Expect) end,
ae0b9aa @ddosia Add recbuf config option.
ddosia authored
626 ServerFun = fun (Socket, _Opts) ->
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
627 ok = mochiweb_socket:send(Socket, BinContent),
52182ce @etrepum convert tabs to spaces
etrepum authored
628 exit(normal)
d69cd76 @etrepum increase test code coverage
etrepum authored
629 end,
630 ClientFun = fun (Socket) ->
631 Req = fake_request(Socket, ContentType,
632 byte_size(BinContent)),
633 Res = parse_multipart_request(Req, TestCallback),
634 {0, <<>>, ok} = Res,
635 ok
636 end,
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
637 ok = with_socket_server(Transport, ServerFun, ClientFun),
d69cd76 @etrepum increase test code coverage
etrepum authored
638 ok.
639
ca962ce @etrepum refactor to use eunit, generate code coverage, update Makefiles and skel...
etrepum authored
640 find_boundary_test() ->
4842019 @mdempsky first post
mdempsky authored
641 B = <<"\r\n--X">>,
642 {next_boundary, 0, 7} = find_boundary(B, <<"\r\n--X\r\nRest">>),
643 {next_boundary, 1, 7} = find_boundary(B, <<"!\r\n--X\r\nRest">>),
644 {end_boundary, 0, 9} = find_boundary(B, <<"\r\n--X--\r\nRest">>),
645 {end_boundary, 1, 9} = find_boundary(B, <<"!\r\n--X--\r\nRest">>),
646 not_found = find_boundary(B, <<"--X\r\nRest">>),
647 {maybe, 0} = find_boundary(B, <<"\r\n--X\r">>),
648 {maybe, 1} = find_boundary(B, <<"!\r\n--X\r">>),
34a7654 @etrepum boundary edge case
etrepum authored
649 P = <<"\r\n-----------------------------16037454351082272548568224146">>,
650 B0 = <<55,212,131,77,206,23,216,198,35,87,252,118,252,8,25,211,132,229,
651 182,42,29,188,62,175,247,243,4,4,0,59, 13,10,45,45,45,45,45,45,45,
652 45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,
653 49,54,48,51,55,52,53,52,51,53,49>>,
654 {maybe, 30} = find_boundary(P, B0),
d69cd76 @etrepum increase test code coverage
etrepum authored
655 not_found = find_boundary(B, <<"\r\n--XJOPKE">>),
4842019 @mdempsky first post
mdempsky authored
656 ok.
657
ca962ce @etrepum refactor to use eunit, generate code coverage, update Makefiles and skel...
etrepum authored
658 find_in_binary_test() ->
4842019 @mdempsky first post
mdempsky authored
659 {exact, 0} = find_in_binary(<<"foo">>, <<"foobarbaz">>),
660 {exact, 1} = find_in_binary(<<"oo">>, <<"foobarbaz">>),
661 {exact, 8} = find_in_binary(<<"z">>, <<"foobarbaz">>),
662 not_found = find_in_binary(<<"q">>, <<"foobarbaz">>),
663 {partial, 7, 2} = find_in_binary(<<"azul">>, <<"foobarbaz">>),
664 {exact, 0} = find_in_binary(<<"foobarbaz">>, <<"foobarbaz">>),
665 {partial, 0, 3} = find_in_binary(<<"foobar">>, <<"foo">>),
666 {partial, 1, 3} = find_in_binary(<<"foobar">>, <<"afoo">>),
667 ok.
668
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
669 flash_parse_http_test() ->
670 flash_parse(plain).
671
672 flash_parse_https_test() ->
673 flash_parse(ssl).
674
675 flash_parse(Transport) ->
20b4bb4 @etrepum finally fixing http://code.google.com/p/mochiweb/issues/detail?id=22
etrepum authored
676 ContentType = "multipart/form-data; boundary=----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5",
677 "----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5" = get_boundary(ContentType),
678 BinContent = <<"------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Filename\"\r\n\r\nhello.txt\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"success_action_status\"\r\n\r\n201\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"file\"; filename=\"hello.txt\"\r\nContent-Type: application/octet-stream\r\n\r\nhello\n\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Upload\"\r\n\r\nSubmit Query\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5--">>,
679 Expect = [{headers,
680 [{"content-disposition",
681 {"form-data", [{"name", "Filename"}]}}]},
682 {body, <<"hello.txt">>},
683 body_end,
684 {headers,
685 [{"content-disposition",
686 {"form-data", [{"name", "success_action_status"}]}}]},
687 {body, <<"201">>},
688 body_end,
689 {headers,
690 [{"content-disposition",
691 {"form-data", [{"name", "file"}, {"filename", "hello.txt"}]}},
692 {"content-type", {"application/octet-stream", []}}]},
693 {body, <<"hello\n">>},
694 body_end,
695 {headers,
696 [{"content-disposition",
697 {"form-data", [{"name", "Upload"}]}}]},
698 {body, <<"Submit Query">>},
699 body_end,
700 eof],
701 TestCallback = fun (Next) -> test_callback(Next, Expect) end,
ae0b9aa @ddosia Add recbuf config option.
ddosia authored
702 ServerFun = fun (Socket, _Opts) ->
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
703 ok = mochiweb_socket:send(Socket, BinContent),
52182ce @etrepum convert tabs to spaces
etrepum authored
704 exit(normal)
20b4bb4 @etrepum finally fixing http://code.google.com/p/mochiweb/issues/detail?id=22
etrepum authored
705 end,
706 ClientFun = fun (Socket) ->
707 Req = fake_request(Socket, ContentType,
6d300db @etrepum patch from Kostis Sagonas, obtained by using tidier, a tool that tidies ...
etrepum authored
708 byte_size(BinContent)),
20b4bb4 @etrepum finally fixing http://code.google.com/p/mochiweb/issues/detail?id=22
etrepum authored
709 Res = parse_multipart_request(Req, TestCallback),
710 {0, <<>>, ok} = Res,
711 ok
712 end,
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
713 ok = with_socket_server(Transport, ServerFun, ClientFun),
20b4bb4 @etrepum finally fixing http://code.google.com/p/mochiweb/issues/detail?id=22
etrepum authored
714 ok.
715
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
716 flash_parse2_http_test() ->
717 flash_parse2(plain).
718
719 flash_parse2_https_test() ->
720 flash_parse2(ssl).
721
722 flash_parse2(Transport) ->
20b4bb4 @etrepum finally fixing http://code.google.com/p/mochiweb/issues/detail?id=22
etrepum authored
723 ContentType = "multipart/form-data; boundary=----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5",
724 "----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5" = get_boundary(ContentType),
725 Chunk = iolist_to_binary(string:copies("%", 4096)),
726 BinContent = <<"------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Filename\"\r\n\r\nhello.txt\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"success_action_status\"\r\n\r\n201\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"file\"; filename=\"hello.txt\"\r\nContent-Type: application/octet-stream\r\n\r\n", Chunk/binary, "\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Upload\"\r\n\r\nSubmit Query\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5--">>,
727 Expect = [{headers,
728 [{"content-disposition",
729 {"form-data", [{"name", "Filename"}]}}]},
730 {body, <<"hello.txt">>},
731 body_end,
732 {headers,
733 [{"content-disposition",
734 {"form-data", [{"name", "success_action_status"}]}}]},
735 {body, <<"201">>},
736 body_end,
737 {headers,
738 [{"content-disposition",
739 {"form-data", [{"name", "file"}, {"filename", "hello.txt"}]}},
740 {"content-type", {"application/octet-stream", []}}]},
741 {body, Chunk},
742 body_end,
743 {headers,
744 [{"content-disposition",
745 {"form-data", [{"name", "Upload"}]}}]},
746 {body, <<"Submit Query">>},
747 body_end,
748 eof],
749 TestCallback = fun (Next) -> test_callback(Next, Expect) end,
ae0b9aa @ddosia Add recbuf config option.
ddosia authored
750 ServerFun = fun (Socket, _Opts) ->
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
751 ok = mochiweb_socket:send(Socket, BinContent),
52182ce @etrepum convert tabs to spaces
etrepum authored
752 exit(normal)
20b4bb4 @etrepum finally fixing http://code.google.com/p/mochiweb/issues/detail?id=22
etrepum authored
753 end,
754 ClientFun = fun (Socket) ->
755 Req = fake_request(Socket, ContentType,
6d300db @etrepum patch from Kostis Sagonas, obtained by using tidier, a tool that tidies ...
etrepum authored
756 byte_size(BinContent)),
20b4bb4 @etrepum finally fixing http://code.google.com/p/mochiweb/issues/detail?id=22
etrepum authored
757 Res = parse_multipart_request(Req, TestCallback),
758 {0, <<>>, ok} = Res,
759 ok
760 end,
f2f6806 @etrepum http://code.google.com/p/mochiweb/issues/detail?id=19
etrepum authored
761 ok = with_socket_server(Transport, ServerFun, ClientFun),
20b4bb4 @etrepum finally fixing http://code.google.com/p/mochiweb/issues/detail?id=22
etrepum authored
762 ok.
763
d69cd76 @etrepum increase test code coverage
etrepum authored
764 parse_headers_test() ->
765 ?assertEqual([], parse_headers(<<>>)).
766
767 flash_multipart_hack_test() ->
768 Buffer = <<"prefix-">>,
769 Prefix = <<"prefix">>,
770 State = #mp{length=0, buffer=Buffer, boundary=Prefix},
771 ?assertEqual(State,
772 flash_multipart_hack(State)).
773
8de2b0d @etrepum more coverage for mochiweb_multipart
etrepum authored
774 parts_to_body_single_test() ->
775 {HL, B} = parts_to_body([{0, 5, <<"01234">>}],
776 "text/plain",
777 10),
778 [{"Content-Range", Range},
779 {"Content-Type", Type}] = lists:sort(HL),
780 ?assertEqual(
781 <<"bytes 0-5/10">>,
782 iolist_to_binary(Range)),
783 ?assertEqual(
784 <<"text/plain">>,
785 iolist_to_binary(Type)),
786 ?assertEqual(
787 <<"01234">>,
788 iolist_to_binary(B)),
789 ok.
790
791 parts_to_body_multi_test() ->
792 {[{"Content-Type", Type}],
793 _B} = parts_to_body([{0, 5, <<"01234">>}, {5, 10, <<"56789">>}],
794 "text/plain",
795 10),
796 ?assertMatch(
797 <<"multipart/byteranges; boundary=", _/binary>>,
798 iolist_to_binary(Type)),
799 ok.
800
801 parts_to_multipart_body_test() ->
802 {[{"Content-Type", V}], B} = parts_to_multipart_body(
803 [{0, 5, <<"01234">>}, {5, 10, <<"56789">>}],
804 "text/plain",
805 10,
806 "BOUNDARY"),
807 MB = multipart_body(
808 [{0, 5, <<"01234">>}, {5, 10, <<"56789">>}],
809 "text/plain",
810 "BOUNDARY",
811 10),
812 ?assertEqual(
813 <<"multipart/byteranges; boundary=BOUNDARY">>,
814 iolist_to_binary(V)),
815 ?assertEqual(
816 iolist_to_binary(MB),
817 iolist_to_binary(B)),
818 ok.
819
820 multipart_body_test() ->
821 ?assertEqual(
822 <<"--BOUNDARY--\r\n">>,
823 iolist_to_binary(multipart_body([], "text/plain", "BOUNDARY", 0))),
824 ?assertEqual(
825 <<"--BOUNDARY\r\n"
826 "Content-Type: text/plain\r\n"
827 "Content-Range: bytes 0-5/10\r\n\r\n"
828 "01234\r\n"
829 "--BOUNDARY\r\n"
830 "Content-Type: text/plain\r\n"
831 "Content-Range: bytes 5-10/10\r\n\r\n"
832 "56789\r\n"
833 "--BOUNDARY--\r\n">>,
834 iolist_to_binary(multipart_body([{0, 5, <<"01234">>}, {5, 10, <<"56789">>}],
835 "text/plain",
836 "BOUNDARY",
837 10))),
838 ok.
839
045504f @etrepum run once in test
etrepum authored
840 %% @todo Move somewhere more appropriate than in the test suite
bd6eb60 @danabr Added multipart parsing benchmark
danabr authored
841
842 multipart_parsing_benchmark_test() ->
045504f @etrepum run once in test
etrepum authored
843 run_multipart_parsing_benchmark(1).
bd6eb60 @danabr Added multipart parsing benchmark
danabr authored
844
845 run_multipart_parsing_benchmark(0) -> ok;
846 run_multipart_parsing_benchmark(N) ->
847 multipart_parsing_benchmark(),
848 run_multipart_parsing_benchmark(N-1).
849
850 multipart_parsing_benchmark() ->
851 ContentType = "multipart/form-data; boundary=----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5",
852 Chunk = binary:copy(<<"This Is_%Some=Quite0Long4String2Used9For7BenchmarKing.5">>, 102400),
853 BinContent = <<"------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Filename\"\r\n\r\nhello.txt\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"success_action_status\"\r\n\r\n201\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"file\"; filename=\"hello.txt\"\r\nContent-Type: application/octet-stream\r\n\r\n", Chunk/binary, "\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Upload\"\r\n\r\nSubmit Query\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5--">>,
854 Expect = [{headers,
855 [{"content-disposition",
856 {"form-data", [{"name", "Filename"}]}}]},
857 {body, <<"hello.txt">>},
858 body_end,
859 {headers,
860 [{"content-disposition",
861 {"form-data", [{"name", "success_action_status"}]}}]},
862 {body, <<"201">>},
863 body_end,
864 {headers,
865 [{"content-disposition",
866 {"form-data", [{"name", "file"}, {"filename", "hello.txt"}]}},
867 {"content-type", {"application/octet-stream", []}}]},
868 {body, Chunk},
869 body_end,
870 {headers,
871 [{"content-disposition",
872 {"form-data", [{"name", "Upload"}]}}]},
873 {body, <<"Submit Query">>},
874 body_end,
875 eof],
876 TestCallback = fun (Next) -> test_callback(Next, Expect) end,
ae0b9aa @ddosia Add recbuf config option.
ddosia authored
877 ServerFun = fun (Socket, _Opts) ->
bd6eb60 @danabr Added multipart parsing benchmark
danabr authored
878 ok = mochiweb_socket:send(Socket, BinContent),
52182ce @etrepum convert tabs to spaces
etrepum authored
879 exit(normal)
bd6eb60 @danabr Added multipart parsing benchmark
danabr authored
880 end,
881 ClientFun = fun (Socket) ->
882 Req = fake_request(Socket, ContentType,
883 byte_size(BinContent)),
884 Res = parse_multipart_request(Req, TestCallback),
885 {0, <<>>, ok} = Res,
886 ok
887 end,
888 ok = with_socket_server(plain, ServerFun, ClientFun),
889 ok.
ca962ce @etrepum refactor to use eunit, generate code coverage, update Makefiles and skel...
etrepum authored
890 -endif.
Something went wrong with that request. Please try again.