summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/couchdb/couch_db.hrl4
-rw-r--r--src/couchdb/couch_util.erl14
-rw-r--r--src/mochiweb/Makefile.am2
-rw-r--r--src/mochiweb/mochijson.erl17
-rw-r--r--src/mochiweb/mochijson2.erl102
-rw-r--r--src/mochiweb/mochiweb.app.in2
-rw-r--r--src/mochiweb/mochiweb_html.erl15
-rw-r--r--src/mochiweb/mochiweb_http.erl26
-rw-r--r--src/mochiweb/mochiweb_multipart.erl165
-rw-r--r--src/mochiweb/mochiweb_request.erl119
-rw-r--r--src/mochiweb/mochiweb_skel.erl6
-rw-r--r--src/mochiweb/mochiweb_socket_server.erl2
-rw-r--r--src/mochiweb/mochiweb_util.erl6
-rw-r--r--src/mochiweb/reloader.erl3
14 files changed, 352 insertions, 131 deletions
diff --git a/src/couchdb/couch_db.hrl b/src/couchdb/couch_db.hrl
index bd297b2a..f2ccb453 100644
--- a/src/couchdb/couch_db.hrl
+++ b/src/couchdb/couch_db.hrl
@@ -17,8 +17,8 @@
-define(MIN_STR, <<"">>).
-define(MAX_STR, <<255>>). % illegal utf string
--define(JSON_ENCODE(V), mochijson2:encode(V)).
--define(JSON_DECODE(V), mochijson2:decode(V)).
+-define(JSON_ENCODE(V), couch_util:json_encode(V)).
+-define(JSON_DECODE(V), couch_util:json_decode(V)).
-define(b2a(V), list_to_atom(binary_to_list(V))).
-define(b2l(V), binary_to_list(V)).
diff --git a/src/couchdb/couch_util.erl b/src/couchdb/couch_util.erl
index d02be8d9..da62375c 100644
--- a/src/couchdb/couch_util.erl
+++ b/src/couchdb/couch_util.erl
@@ -20,6 +20,7 @@
to_hex/1,parse_term/1, dict_find/3]).
-export([file_read_size/1, get_nested_json_value/2, json_user_ctx/1]).
-export([to_binary/1, to_integer/1, to_list/1, url_encode/1]).
+-export([json_encode/1, json_decode/1]).
-include("couch_db.hrl").
-include_lib("kernel/include/file.hrl").
@@ -406,3 +407,16 @@ url_encode([H|T]) ->
end;
url_encode([]) ->
[].
+
+json_encode(V) ->
+ Handler =
+ fun({L}) when is_list(L) ->
+ {struct,L};
+ (Bad) ->
+ exit({json_encode, {bad_term, Bad}})
+ end,
+ (mochijson2:encoder([{handler, Handler}]))(V).
+
+json_decode(V) ->
+ try (mochijson2:decoder([{object_hook, fun({struct,L}) -> {L} end}]))(V)
+ catch _:_ -> throw({invalid_json,V}) end.
diff --git a/src/mochiweb/Makefile.am b/src/mochiweb/Makefile.am
index 608d4dcd..c191abfa 100644
--- a/src/mochiweb/Makefile.am
+++ b/src/mochiweb/Makefile.am
@@ -10,7 +10,7 @@
## License for the specific language governing permissions and limitations under
## the License.
-mochiwebebindir = $(localerlanglibdir)/mochiweb-r97/ebin
+mochiwebebindir = $(localerlanglibdir)/mochiweb-r113/ebin
mochiweb_file_collection = \
mochifmt.erl \
diff --git a/src/mochiweb/mochijson.erl b/src/mochiweb/mochijson.erl
index 029642ac..74695a75 100644
--- a/src/mochiweb/mochijson.erl
+++ b/src/mochiweb/mochijson.erl
@@ -186,7 +186,8 @@ json_encode_string_utf8_1([C | Cs]) when C >= 0, C =< 16#7f ->
end,
[NewC | json_encode_string_utf8_1(Cs)];
json_encode_string_utf8_1(All=[C | _]) when C >= 16#80, C =< 16#10FFFF ->
- json_encode_string_unicode(xmerl_ucs:from_utf8(All));
+ [?Q | Rest] = json_encode_string_unicode(xmerl_ucs:from_utf8(All)),
+ Rest;
json_encode_string_utf8_1([]) ->
"\"".
@@ -459,16 +460,18 @@ equiv_object(Props1, Props2) ->
equiv_list([], []) ->
true;
equiv_list([V1 | L1], [V2 | L2]) ->
- case equiv(V1, V2) of
- true ->
- equiv_list(L1, L2);
- false ->
- false
- end.
+ equiv(V1, V2) andalso equiv_list(L1, L2).
test_all() ->
+ test_issue33(),
test_one(e2j_test_vec(utf8), 1).
+test_issue33() ->
+ %% http://code.google.com/p/mochiweb/issues/detail?id=33
+ Js = {struct, [{"key", [194, 163]}]},
+ Encoder = encoder([{input_encoding, utf8}]),
+ "{\"key\":\"\\u00a3\"}" = lists:flatten(Encoder(Js)).
+
test_one([], _N) ->
%% io:format("~p tests passed~n", [N-1]),
ok;
diff --git a/src/mochiweb/mochijson2.erl b/src/mochiweb/mochijson2.erl
index ee19458c..66f68bf0 100644
--- a/src/mochiweb/mochijson2.erl
+++ b/src/mochiweb/mochijson2.erl
@@ -42,7 +42,8 @@
%% @type json_term() = json_string() | json_number() | json_array() |
%% json_object()
--record(encoder, {handler=null}).
+-record(encoder, {handler=null,
+ utf8=false}).
-record(decoder, {object_hook=null,
offset=0,
@@ -52,6 +53,8 @@
%% @spec encoder([encoder_option()]) -> function()
%% @doc Create an encoder/1 with the given options.
+%% @type encoder_option() = handler_option() | utf8_option()
+%% @type utf8_option() = boolean(). Emit unicode as utf8 (default - false)
encoder(Options) ->
State = parse_encoder_options(Options, #encoder{}),
fun (O) -> json_encode(O, State) end.
@@ -70,10 +73,7 @@ decoder(Options) ->
%% @spec decode(iolist()) -> json_term()
%% @doc Decode the given iolist to Erlang terms.
decode(S) ->
- try json_decode(S, #decoder{})
- catch
- _:_ -> throw({invalid_json, S})
- end.
+ json_decode(S, #decoder{}).
test() ->
test_all().
@@ -83,7 +83,9 @@ test() ->
parse_encoder_options([], State) ->
State;
parse_encoder_options([{handler, Handler} | Rest], State) ->
- parse_encoder_options(Rest, State#encoder{handler=Handler}).
+ parse_encoder_options(Rest, State#encoder{handler=Handler});
+parse_encoder_options([{utf8, Switch} | Rest], State) ->
+ parse_encoder_options(Rest, State#encoder{utf8=Switch}).
parse_decoder_options([], State) ->
State;
@@ -96,15 +98,18 @@ json_encode(false, _State) ->
<<"false">>;
json_encode(null, _State) ->
<<"null">>;
-json_encode(I, _State) when is_integer(I) ->
+json_encode(I, _State) when is_integer(I) andalso I >= -2147483648 andalso I =< 2147483647 ->
+ %% Anything outside of 32-bit integers should be encoded as a float
integer_to_list(I);
+json_encode(I, _State) when is_integer(I) ->
+ mochinum:digits(float(I));
json_encode(F, _State) when is_float(F) ->
mochinum:digits(F);
json_encode(S, State) when is_binary(S); is_atom(S) ->
json_encode_string(S, State);
json_encode(Array, State) when is_list(Array) ->
json_encode_array(Array, State);
-json_encode({Props}, State) when is_list(Props) ->
+json_encode({struct, Props}, State) when is_list(Props) ->
json_encode_proplist(Props, State);
json_encode(Bad, #encoder{handler=null}) ->
exit({json_encode, {bad_term, Bad}});
@@ -131,29 +136,29 @@ json_encode_proplist(Props, State) ->
[$, | Acc1] = lists:foldl(F, "{", Props),
lists:reverse([$\} | Acc1]).
-json_encode_string(A, _State) when is_atom(A) ->
+json_encode_string(A, State) when is_atom(A) ->
L = atom_to_list(A),
case json_string_is_safe(L) of
true ->
[?Q, L, ?Q];
false ->
- json_encode_string_unicode(xmerl_ucs:from_utf8(L), [?Q])
+ json_encode_string_unicode(xmerl_ucs:from_utf8(L), State, [?Q])
end;
-json_encode_string(B, _State) when is_binary(B) ->
+json_encode_string(B, State) when is_binary(B) ->
case json_bin_is_safe(B) of
true ->
[?Q, B, ?Q];
false ->
- json_encode_string_unicode(xmerl_ucs:from_utf8(B), [?Q])
+ json_encode_string_unicode(xmerl_ucs:from_utf8(B), State, [?Q])
end;
json_encode_string(I, _State) when is_integer(I) ->
[?Q, integer_to_list(I), ?Q];
-json_encode_string(L, _State) when is_list(L) ->
+json_encode_string(L, State) when is_list(L) ->
case json_string_is_safe(L) of
true ->
[?Q, L, ?Q];
false ->
- json_encode_string_unicode(L, [?Q])
+ json_encode_string_unicode(L, State, [?Q])
end.
json_string_is_safe([]) ->
@@ -208,9 +213,9 @@ json_bin_is_safe(<<C, Rest/binary>>) ->
false
end.
-json_encode_string_unicode([], Acc) ->
+json_encode_string_unicode([], _State, Acc) ->
lists:reverse([$\" | Acc]);
-json_encode_string_unicode([C | Cs], Acc) ->
+json_encode_string_unicode([C | Cs], State, Acc) ->
Acc1 = case C of
?Q ->
[?Q, $\\ | Acc];
@@ -236,14 +241,18 @@ json_encode_string_unicode([C | Cs], Acc) ->
[$r, $\\ | Acc];
$\t ->
[$t, $\\ | Acc];
- C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF ->
+ C when C >= 0, C < $\s ->
+ [unihex(C) | Acc];
+ C when C >= 16#7f, C =< 16#10FFFF, State#encoder.utf8 ->
+ [xmerl_ucs:to_utf8(C) | Acc];
+ C when C >= 16#7f, C =< 16#10FFFF, not State#encoder.utf8 ->
[unihex(C) | Acc];
C when C < 16#7f ->
[C | Acc];
_ ->
exit({json_encode, {bad_char, C}})
end,
- json_encode_string_unicode(Cs, Acc1).
+ json_encode_string_unicode(Cs, State, Acc1).
hexdigit(C) when C >= 0, C =< 9 ->
C + $0;
@@ -288,7 +297,7 @@ decode_object(B, S) ->
decode_object(B, S=#decoder{state=key}, Acc) ->
case tokenize(B, S) of
{end_object, S1} ->
- V = make_object({lists:reverse(Acc)}, S1),
+ V = make_object({struct, lists:reverse(Acc)}, S1),
{V, S1#decoder{state=null}};
{{const, K}, S1} ->
{colon, S2} = tokenize(B, S1),
@@ -298,7 +307,7 @@ decode_object(B, S=#decoder{state=key}, Acc) ->
decode_object(B, S=#decoder{state=comma}, Acc) ->
case tokenize(B, S) of
{end_object, S1} ->
- V = make_object({lists:reverse(Acc)}, S1),
+ V = make_object({struct, lists:reverse(Acc)}, S1),
{V, S1#decoder{state=null}};
{comma, S1} ->
decode_object(B, S1#decoder{state=key}, Acc)
@@ -507,9 +516,9 @@ tokenize(B, S=#decoder{offset=O}) ->
%% Create an object from a list of Key/Value pairs.
obj_new() ->
- {[]}.
+ {struct, []}.
-is_obj({Props}) ->
+is_obj({struct, Props}) ->
F = fun ({K, _}) when is_binary(K) ->
true;
(_) ->
@@ -518,7 +527,7 @@ is_obj({Props}) ->
lists:all(F, Props).
obj_from_list(Props) ->
- Obj = {Props},
+ Obj = {struct, Props},
case is_obj(Obj) of
true -> Obj;
false -> exit({json_bad_object, Obj})
@@ -529,7 +538,7 @@ obj_from_list(Props) ->
%% compare unequal as erlang terms, so we need to carefully recurse
%% through aggregates (tuples and objects).
-equiv({Props1}, {Props2}) ->
+equiv({struct, Props1}, {struct, Props2}) ->
equiv_object(Props1, Props2);
equiv(L1, L2) when is_list(L1), is_list(L2) ->
equiv_list(L1, L2);
@@ -555,16 +564,13 @@ equiv_object(Props1, Props2) ->
equiv_list([], []) ->
true;
equiv_list([V1 | L1], [V2 | L2]) ->
- case equiv(V1, V2) of
- true ->
- equiv_list(L1, L2);
- false ->
- false
- end.
+ equiv(V1, V2) andalso equiv_list(L1, L2).
test_all() ->
[1199344435545.0, 1] = decode(<<"[1199344435545.0,1]">>),
<<16#F0,16#9D,16#9C,16#95>> = decode([34,"\\ud835","\\udf15",34]),
+ test_encoder_utf8(),
+ test_input_validation(),
test_one(e2j_test_vec(utf8), 1).
test_one([], _N) ->
@@ -619,3 +625,39 @@ e2j_test_vec(utf8) ->
{[-123, <<"foo">>, obj_from_list([{<<"bar">>, []}]), null],
"[-123,\"foo\",{\"bar\":[]},null]"}
].
+
+%% test utf8 encoding
+test_encoder_utf8() ->
+ %% safe conversion case (default)
+ [34,"\\u0001","\\u0442","\\u0435","\\u0441","\\u0442",34] =
+ encode(<<1,"\321\202\320\265\321\201\321\202">>),
+
+ %% raw utf8 output (optional)
+ Enc = mochijson2:encoder([{utf8, true}]),
+ [34,"\\u0001",[209,130],[208,181],[209,129],[209,130],34] =
+ Enc(<<1,"\321\202\320\265\321\201\321\202">>).
+
+test_input_validation() ->
+ Good = [
+ {16#00A3, <<?Q, 16#C2, 16#A3, ?Q>>}, % pound
+ {16#20AC, <<?Q, 16#E2, 16#82, 16#AC, ?Q>>}, % euro
+ {16#10196, <<?Q, 16#F0, 16#90, 16#86, 16#96, ?Q>>} % denarius
+ ],
+ lists:foreach(fun({CodePoint, UTF8}) ->
+ Expect = list_to_binary(xmerl_ucs:to_utf8(CodePoint)),
+ Expect = decode(UTF8)
+ end, Good),
+
+ Bad = [
+ % 2nd, 3rd, or 4th byte of a multi-byte sequence w/o leading byte
+ <<?Q, 16#80, ?Q>>,
+ % missing continuations, last byte in each should be 80-BF
+ <<?Q, 16#C2, 16#7F, ?Q>>,
+ <<?Q, 16#E0, 16#80,16#7F, ?Q>>,
+ <<?Q, 16#F0, 16#80, 16#80, 16#7F, ?Q>>,
+ % we don't support code points > 10FFFF per RFC 3629
+ <<?Q, 16#F5, 16#80, 16#80, 16#80, ?Q>>
+ ],
+ lists:foreach(fun(X) ->
+ ok = try decode(X) catch invalid_utf8 -> ok end
+ end, Bad).
diff --git a/src/mochiweb/mochiweb.app.in b/src/mochiweb/mochiweb.app.in
index cd8dbb25..b0f90144 100644
--- a/src/mochiweb/mochiweb.app.in
+++ b/src/mochiweb/mochiweb.app.in
@@ -1,6 +1,6 @@
{application, mochiweb,
[{description, "MochiMedia Web Server"},
- {vsn, "0.01"},
+ {vsn, "113"},
{modules, [
mochihex,
mochijson,
diff --git a/src/mochiweb/mochiweb_html.erl b/src/mochiweb/mochiweb_html.erl
index 0e030c13..77100d50 100644
--- a/src/mochiweb/mochiweb_html.erl
+++ b/src/mochiweb/mochiweb_html.erl
@@ -305,6 +305,18 @@ test_tokens() ->
{data, <<" A= B <= C ">>, false},
{end_tag, <<"script">>}] =
tokens(<<"<script type=\"text/javascript\"> A= B <= C </script>">>),
+ [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
+ {data, <<" A= B <= C ">>, false},
+ {end_tag, <<"script">>}] =
+ tokens(<<"<script type =\"text/javascript\"> A= B <= C </script>">>),
+ [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
+ {data, <<" A= B <= C ">>, false},
+ {end_tag, <<"script">>}] =
+ tokens(<<"<script type = \"text/javascript\"> A= B <= C </script>">>),
+ [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
+ {data, <<" A= B <= C ">>, false},
+ {end_tag, <<"script">>}] =
+ tokens(<<"<script type= \"text/javascript\"> A= B <= C </script>">>),
[{start_tag, <<"textarea">>, [], false},
{data, <<"<html></body>">>, false},
{end_tag, <<"textarea">>}] =
@@ -672,7 +684,8 @@ tokenize_attr_value(Attr, B, S) ->
O = S1#decoder.offset,
case B of
<<_:O/binary, "=", _/binary>> ->
- tokenize_word_or_literal(B, ?INC_COL(S1));
+ S2 = skip_whitespace(B, ?INC_COL(S1)),
+ tokenize_word_or_literal(B, S2);
_ ->
{Attr, S1}
end.
diff --git a/src/mochiweb/mochiweb_http.erl b/src/mochiweb/mochiweb_http.erl
index 14a36578..f1821f40 100644
--- a/src/mochiweb/mochiweb_http.erl
+++ b/src/mochiweb/mochiweb_http.erl
@@ -7,6 +7,7 @@
-author('bob@mochimedia.com').
-export([start/0, start/1, stop/0, stop/1]).
-export([loop/2, default_body/1]).
+-export([after_response/2, reentry/1]).
-define(IDLE_TIMEOUT, 30000).
@@ -110,6 +111,11 @@ request(Socket, Body) ->
exit(normal)
end.
+reentry(Body) ->
+ fun (Req) ->
+ ?MODULE:after_response(Body, Req)
+ end.
+
headers(Socket, Request, Headers, _Body, ?MAX_HEADERS) ->
%% Too many headers sent, bad request.
inet:setopts(Socket, [{packet, raw}]),
@@ -125,14 +131,7 @@ headers(Socket, Request, Headers, Body, HeaderCount) ->
Req = mochiweb:new_request({Socket, Request,
lists:reverse(Headers)}),
Body(Req),
- case Req:should_close() of
- true ->
- gen_tcp:close(Socket),
- exit(normal);
- false ->
- Req:cleanup(),
- ?MODULE:loop(Socket, Body)
- end;
+ ?MODULE:after_response(Body, Req);
{ok, {http_header, _, Name, _, Value}} ->
headers(Socket, Request, [{Name, Value} | Headers], Body,
1 + HeaderCount);
@@ -140,3 +139,14 @@ headers(Socket, Request, Headers, Body, HeaderCount) ->
gen_tcp:close(Socket),
exit(normal)
end.
+
+after_response(Body, Req) ->
+ Socket = Req:get(socket),
+ case Req:should_close() of
+ true ->
+ gen_tcp:close(Socket),
+ exit(normal);
+ false ->
+ Req:cleanup(),
+ ?MODULE:loop(Socket, Body)
+ end.
diff --git a/src/mochiweb/mochiweb_multipart.erl b/src/mochiweb/mochiweb_multipart.erl
index b9631613..0368a9a6 100644
--- a/src/mochiweb/mochiweb_multipart.erl
+++ b/src/mochiweb/mochiweb_multipart.erl
@@ -76,15 +76,15 @@ parse_multipart_request(Req, Callback) ->
Boundary = iolist_to_binary(
get_boundary(Req:get_header_value("content-type"))),
Prefix = <<"\r\n--", Boundary/binary>>,
- BS = size(Boundary),
+ BS = byte_size(Boundary),
Chunk = read_chunk(Req, Length),
- Length1 = Length - size(Chunk),
+ Length1 = Length - byte_size(Chunk),
<<"--", Boundary:BS/binary, "\r\n", Rest/binary>> = Chunk,
- feed_mp(headers, #mp{boundary=Prefix,
- length=Length1,
- buffer=Rest,
- callback=Callback,
- req=Req}).
+ feed_mp(headers, flash_multipart_hack(#mp{boundary=Prefix,
+ length=Length1,
+ buffer=Rest,
+ callback=Callback,
+ req=Req})).
parse_headers(<<>>) ->
[];
@@ -117,8 +117,27 @@ read_chunk(Req, Length) when Length > 0 ->
read_more(State=#mp{length=Length, buffer=Buffer, req=Req}) ->
Data = read_chunk(Req, Length),
Buffer1 = <<Buffer/binary, Data/binary>>,
- State#mp{length=Length - size(Data),
- buffer=Buffer1}.
+ flash_multipart_hack(State#mp{length=Length - byte_size(Data),
+ buffer=Buffer1}).
+
+flash_multipart_hack(State=#mp{length=0, buffer=Buffer, boundary=Prefix}) ->
+ %% http://code.google.com/p/mochiweb/issues/detail?id=22
+ %% Flash doesn't terminate multipart with \r\n properly so we fix it up here
+ PrefixSize = size(Prefix),
+ case size(Buffer) - (2 + PrefixSize) of
+ Seek when Seek >= 0 ->
+ case Buffer of
+ <<_:Seek/binary, Prefix:PrefixSize/binary, "--">> ->
+ Buffer1 = <<Buffer/binary, "\r\n">>,
+ State#mp{buffer=Buffer1};
+ _ ->
+ State
+ end;
+ _ ->
+ State
+ end;
+flash_multipart_hack(State) ->
+ State.
feed_mp(headers, State=#mp{buffer=Buffer, callback=Callback}) ->
{State1, P} = case find_in_binary(<<"\r\n\r\n">>, Buffer) of
@@ -136,7 +155,8 @@ feed_mp(headers, State=#mp{buffer=Buffer, callback=Callback}) ->
feed_mp(body, State1#mp{buffer=Rest,
callback=NextCallback});
feed_mp(body, State=#mp{boundary=Prefix, buffer=Buffer, callback=Callback}) ->
- case find_boundary(Prefix, Buffer) of
+ Boundary = find_boundary(Prefix, Buffer),
+ case Boundary of
{end_boundary, Start, Skip} ->
<<Data:Start/binary, _:Skip/binary, Rest/binary>> = Buffer,
C1 = Callback({body, Data}),
@@ -158,7 +178,7 @@ feed_mp(body, State=#mp{boundary=Prefix, buffer=Buffer, callback=Callback}) ->
end.
get_boundary(ContentType) ->
- {"multipart/" ++ _, Opts} = mochiweb_util:parse_header(ContentType),
+ {"multipart/form-data", Opts} = mochiweb_util:parse_header(ContentType),
case proplists:get_value("boundary", Opts) of
S when is_list(S) ->
S
@@ -242,7 +262,11 @@ test_callback(Expect, [Expect | Rest]) ->
ok;
_ ->
fun (Next) -> test_callback(Next, Rest) end
- end.
+ end;
+test_callback({body, Got}, [{body, Expect} | Rest]) ->
+ GotSize = size(Got),
+ <<Got:GotSize/binary, Expect1/binary>> = Expect,
+ fun (Next) -> test_callback(Next, [{body, Expect1} | Rest]) end.
test_parse3() ->
ContentType = "multipart/form-data; boundary=---------------------------7386909285754635891697677882",
@@ -261,14 +285,12 @@ test_parse3() ->
eof],
TestCallback = fun (Next) -> test_callback(Next, Expect) end,
ServerFun = fun (Socket) ->
- case gen_tcp:send(Socket, BinContent) of
- ok ->
- exit(normal)
- end
+ ok = gen_tcp:send(Socket, BinContent),
+ exit(normal)
end,
ClientFun = fun (Socket) ->
Req = fake_request(Socket, ContentType,
- size(BinContent)),
+ byte_size(BinContent)),
Res = parse_multipart_request(Req, TestCallback),
{0, <<>>, ok} = Res,
ok
@@ -294,14 +316,12 @@ test_parse2() ->
eof],
TestCallback = fun (Next) -> test_callback(Next, Expect) end,
ServerFun = fun (Socket) ->
- case gen_tcp:send(Socket, BinContent) of
- ok ->
- exit(normal)
- end
+ ok = gen_tcp:send(Socket, BinContent),
+ exit(normal)
end,
ClientFun = fun (Socket) ->
Req = fake_request(Socket, ContentType,
- size(BinContent)),
+ byte_size(BinContent)),
Res = parse_multipart_request(Req, TestCallback),
{0, <<>>, ok} = Res,
ok
@@ -327,14 +347,12 @@ test_parse_form() ->
""], "\r\n"),
BinContent = iolist_to_binary(Content),
ServerFun = fun (Socket) ->
- case gen_tcp:send(Socket, BinContent) of
- ok ->
- exit(normal)
- end
+ ok = gen_tcp:send(Socket, BinContent),
+ exit(normal)
end,
ClientFun = fun (Socket) ->
Req = fake_request(Socket, ContentType,
- size(BinContent)),
+ byte_size(BinContent)),
Res = parse_form(Req),
[{"submit-name", "Larry"},
{"files", {"file1.txt", {"text/plain",[]},
@@ -376,14 +394,12 @@ test_parse() ->
eof],
TestCallback = fun (Next) -> test_callback(Next, Expect) end,
ServerFun = fun (Socket) ->
- case gen_tcp:send(Socket, BinContent) of
- ok ->
- exit(normal)
- end
+ ok = gen_tcp:send(Socket, BinContent),
+ exit(normal)
end,
ClientFun = fun (Socket) ->
Req = fake_request(Socket, ContentType,
- size(BinContent)),
+ byte_size(BinContent)),
Res = parse_multipart_request(Req, TestCallback),
{0, <<>>, ok} = Res,
ok
@@ -419,6 +435,89 @@ test_find_in_binary() ->
{partial, 1, 3} = find_in_binary(<<"foobar">>, <<"afoo">>),
ok.
+test_flash_parse() ->
+ ContentType = "multipart/form-data; boundary=----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5",
+ "----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5" = get_boundary(ContentType),
+ 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--">>,
+ Expect = [{headers,
+ [{"content-disposition",
+ {"form-data", [{"name", "Filename"}]}}]},
+ {body, <<"hello.txt">>},
+ body_end,
+ {headers,
+ [{"content-disposition",
+ {"form-data", [{"name", "success_action_status"}]}}]},
+ {body, <<"201">>},
+ body_end,
+ {headers,
+ [{"content-disposition",
+ {"form-data", [{"name", "file"}, {"filename", "hello.txt"}]}},
+ {"content-type", {"application/octet-stream", []}}]},
+ {body, <<"hello\n">>},
+ body_end,
+ {headers,
+ [{"content-disposition",
+ {"form-data", [{"name", "Upload"}]}}]},
+ {body, <<"Submit Query">>},
+ body_end,
+ eof],
+ TestCallback = fun (Next) -> test_callback(Next, Expect) end,
+ ServerFun = fun (Socket) ->
+ ok = gen_tcp:send(Socket, BinContent),
+ exit(normal)
+ end,
+ ClientFun = fun (Socket) ->
+ Req = fake_request(Socket, ContentType,
+ byte_size(BinContent)),
+ Res = parse_multipart_request(Req, TestCallback),
+ {0, <<>>, ok} = Res,
+ ok
+ end,
+ ok = with_socket_server(ServerFun, ClientFun),
+ ok.
+
+test_flash_parse2() ->
+ ContentType = "multipart/form-data; boundary=----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5",
+ "----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5" = get_boundary(ContentType),
+ Chunk = iolist_to_binary(string:copies("%", 4096)),
+ 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--">>,
+ Expect = [{headers,
+ [{"content-disposition",
+ {"form-data", [{"name", "Filename"}]}}]},
+ {body, <<"hello.txt">>},
+ body_end,
+ {headers,
+ [{"content-disposition",
+ {"form-data", [{"name", "success_action_status"}]}}]},
+ {body, <<"201">>},
+ body_end,
+ {headers,
+ [{"content-disposition",
+ {"form-data", [{"name", "file"}, {"filename", "hello.txt"}]}},
+ {"content-type", {"application/octet-stream", []}}]},
+ {body, Chunk},
+ body_end,
+ {headers,
+ [{"content-disposition",
+ {"form-data", [{"name", "Upload"}]}}]},
+ {body, <<"Submit Query">>},
+ body_end,
+ eof],
+ TestCallback = fun (Next) -> test_callback(Next, Expect) end,
+ ServerFun = fun (Socket) ->
+ ok = gen_tcp:send(Socket, BinContent),
+ exit(normal)
+ end,
+ ClientFun = fun (Socket) ->
+ Req = fake_request(Socket, ContentType,
+ byte_size(BinContent)),
+ Res = parse_multipart_request(Req, TestCallback),
+ {0, <<>>, ok} = Res,
+ ok
+ end,
+ ok = with_socket_server(ServerFun, ClientFun),
+ ok.
+
test() ->
test_find_in_binary(),
test_find_boundary(),
@@ -426,4 +525,6 @@ test() ->
test_parse2(),
test_parse3(),
test_parse_form(),
+ test_flash_parse(),
+ test_flash_parse2(),
ok.
diff --git a/src/mochiweb/mochiweb_request.erl b/src/mochiweb/mochiweb_request.erl
index e8f0a67c..fc296f40 100644
--- a/src/mochiweb/mochiweb_request.erl
+++ b/src/mochiweb/mochiweb_request.erl
@@ -190,8 +190,13 @@ stream_body(MaxChunkSize, ChunkFun, FunState) ->
stream_body(MaxChunkSize, ChunkFun, FunState, undefined).
stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength) ->
-
- case get_header_value("expect") of
+ Expect = case get_header_value("expect") of
+ undefined ->
+ undefined;
+ Value when is_list(Value) ->
+ string:to_lower(Value)
+ end,
+ case Expect of
"100-continue" ->
start_raw_response({100, gb_trees:empty()});
_Else ->
@@ -214,7 +219,7 @@ stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength) ->
MaxBodyLength when is_integer(MaxBodyLength), MaxBodyLength < Length ->
exit({body_too_large, content_length});
_ ->
- stream_unchunked_body(Length, MaxChunkSize, ChunkFun, FunState)
+ stream_unchunked_body(Length, ChunkFun, FunState)
end;
Length ->
exit({length_not_integer, Length})
@@ -449,13 +454,20 @@ stream_chunked_body(MaxChunkSize, Fun, FunState) ->
stream_chunked_body(MaxChunkSize, Fun, NewState)
end.
-stream_unchunked_body(0, _MaxChunkSize, Fun, FunState) ->
+stream_unchunked_body(0, Fun, FunState) ->
Fun({0, <<>>}, FunState);
-stream_unchunked_body(Length, _, Fun, FunState) when Length > 0 ->
+stream_unchunked_body(Length, Fun, FunState) when Length > 0 ->
Bin = recv(0),
- BinSize = size(Bin),
- NewState = Fun({BinSize, Bin}, FunState),
- stream_unchunked_body(Length - BinSize, 0, Fun, NewState).
+ BinSize = byte_size(Bin),
+ if BinSize > Length ->
+ <<OurBody:Length/binary, Extra/binary>> = Bin,
+ gen_tcp:unrecv(Socket, Extra),
+ NewState = Fun({Length, OurBody}, FunState),
+ stream_unchunked_body(0, Fun, NewState);
+ true ->
+ NewState = Fun({BinSize, Bin}, FunState),
+ stream_unchunked_body(Length - BinSize, Fun, NewState)
+ end.
%% @spec read_chunk_length() -> integer()
@@ -521,40 +533,69 @@ serve_file(Path, DocRoot, ExtraHeaders) ->
not_found(ExtraHeaders);
RelPath ->
FullPath = filename:join([DocRoot, RelPath]),
- File = case filelib:is_dir(FullPath) of
- true ->
- filename:join([FullPath, "index.html"]);
- false ->
- FullPath
- end,
- case file:read_file_info(File) of
- {ok, FileInfo} ->
- LastModified = httpd_util:rfc1123_date(FileInfo#file_info.mtime),
- case get_header_value("if-modified-since") of
- LastModified ->
- respond({304, ExtraHeaders, ""});
- _ ->
- case file:open(File, [raw, binary]) of
- {ok, IoDevice} ->
- ContentType = mochiweb_util:guess_mime(File),
- Res = ok({ContentType,
- [{"last-modified", LastModified}
- | ExtraHeaders],
- {file, IoDevice}}),
- file:close(IoDevice),
- Res;
- _ ->
- not_found(ExtraHeaders)
- end
- end;
- {error, _} ->
- not_found(ExtraHeaders)
+ case filelib:is_dir(FullPath) of
+ true ->
+ maybe_redirect(RelPath, FullPath, ExtraHeaders);
+ false ->
+ maybe_serve_file(FullPath, ExtraHeaders)
end
end.
-
%% Internal API
+%% This has the same effect as the DirectoryIndex directive in httpd
+directory_index(FullPath) ->
+ filename:join([FullPath, "index.html"]).
+
+maybe_redirect([], FullPath, ExtraHeaders) ->
+ maybe_serve_file(directory_index(FullPath), ExtraHeaders);
+
+maybe_redirect(RelPath, FullPath, ExtraHeaders) ->
+ case string:right(RelPath, 1) of
+ "/" ->
+ maybe_serve_file(directory_index(FullPath), ExtraHeaders);
+ _ ->
+ Host = mochiweb_headers:get_value("host", Headers),
+ Location = "http://" ++ Host ++ "/" ++ RelPath ++ "/",
+ LocationBin = list_to_binary(Location),
+ MoreHeaders = [{"Location", Location},
+ {"Content-Type", "text/html"} | ExtraHeaders],
+ Top = <<"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">"
+ "<html><head>"
+ "<title>301 Moved Permanently</title>"
+ "</head><body>"
+ "<h1>Moved Permanently</h1>"
+ "<p>The document has moved <a href=\"">>,
+ Bottom = <<">here</a>.</p></body></html>\n">>,
+ Body = <<Top/binary, LocationBin/binary, Bottom/binary>>,
+ respond({301, MoreHeaders, Body})
+ end.
+
+maybe_serve_file(File, ExtraHeaders) ->
+ case file:read_file_info(File) of
+ {ok, FileInfo} ->
+ LastModified = httpd_util:rfc1123_date(FileInfo#file_info.mtime),
+ case get_header_value("if-modified-since") of
+ LastModified ->
+ respond({304, ExtraHeaders, ""});
+ _ ->
+ case file:open(File, [raw, binary]) of
+ {ok, IoDevice} ->
+ ContentType = mochiweb_util:guess_mime(File),
+ Res = ok({ContentType,
+ [{"last-modified", LastModified}
+ | ExtraHeaders],
+ {file, IoDevice}}),
+ file:close(IoDevice),
+ Res;
+ _ ->
+ not_found(ExtraHeaders)
+ end
+ end;
+ {error, _} ->
+ not_found(ExtraHeaders)
+ end.
+
server_headers() ->
[{"Server", "MochiWeb/1.0 (" ++ ?QUIP ++ ")"},
{"Date", httpd_util:rfc1123_date()}].
@@ -639,7 +680,6 @@ range_parts({file, IoDevice}, Ranges) ->
end,
LocNums, Data),
{Bodies, Size};
-
range_parts(Body0, Ranges) ->
Body = iolist_to_binary(Body0),
Size = size(Body),
@@ -706,7 +746,6 @@ test_range() ->
[{none, 20}] = parse_range_request("bytes=-20"),
io:format(".. ok ~n"),
-
%% invalid, single ranges
io:format("Testing parse_range_request with invalid ranges~n"),
io:format("1"),
@@ -734,7 +773,7 @@ test_range() ->
io:format(".. ok~n"),
Body = <<"012345678901234567890123456789012345678901234567890123456789">>,
- BodySize = size(Body), %% 60
+ BodySize = byte_size(Body), %% 60
BodySize = 60,
%% these values assume BodySize =:= 60
diff --git a/src/mochiweb/mochiweb_skel.erl b/src/mochiweb/mochiweb_skel.erl
index 098951be..36b48be5 100644
--- a/src/mochiweb/mochiweb_skel.erl
+++ b/src/mochiweb/mochiweb_skel.erl
@@ -29,7 +29,8 @@ skel() ->
"skel".
skelcopy(Src, DestDir, Name, LDst) ->
- {ok, Dest, _} = regexp:gsub(filename:basename(Src), skel(), Name),
+ Dest = re:replace(filename:basename(Src), skel(), Name,
+ [global, {return, list}]),
case file:read_file_info(Src) of
{ok, #file_info{type=directory, mode=Mode}} ->
Dir = DestDir ++ "/" ++ Dest,
@@ -50,7 +51,8 @@ skelcopy(Src, DestDir, Name, LDst) ->
{ok, #file_info{type=regular, mode=Mode}} ->
OutFile = filename:join(DestDir, Dest),
{ok, B} = file:read_file(Src),
- {ok, S, _} = regexp:gsub(binary_to_list(B), skel(), Name),
+ S = re:replace(binary_to_list(B), skel(), Name,
+ [{return, list}, global]),
ok = file:write_file(OutFile, list_to_binary(S)),
ok = file:write_file_info(OutFile, #file_info{mode=Mode}),
io:format(" ~s~n", [filename:basename(Src)]),
diff --git a/src/mochiweb/mochiweb_socket_server.erl b/src/mochiweb/mochiweb_socket_server.erl
index a483c3d0..7aafe290 100644
--- a/src/mochiweb/mochiweb_socket_server.erl
+++ b/src/mochiweb/mochiweb_socket_server.erl
@@ -22,7 +22,7 @@
ip=any,
listen=null,
acceptor=null,
- backlog=30}).
+ backlog=128}).
start(State=#mochiweb_socket_server{}) ->
start_server(State);
diff --git a/src/mochiweb/mochiweb_util.erl b/src/mochiweb/mochiweb_util.erl
index 7bf18d15..73cacea4 100644
--- a/src/mochiweb/mochiweb_util.erl
+++ b/src/mochiweb/mochiweb_util.erl
@@ -78,7 +78,7 @@ safe_relative_path("", Acc) ->
[] ->
"";
_ ->
- join(lists:reverse(Acc), "/")
+ string:join(lists:reverse(Acc), "/")
end;
safe_relative_path(P, Acc) ->
case partition(P, "/") of
@@ -423,9 +423,7 @@ record_to_proplist(Record, Fields) ->
%% Fields should be obtained by calling record_info(fields, record_type)
%% where record_type is the record type of Record
record_to_proplist(Record, Fields, TypeKey)
- when is_tuple(Record),
- is_list(Fields),
- size(Record) - 1 =:= length(Fields) ->
+ when tuple_size(Record) - 1 =:= length(Fields) ->
lists:zip([TypeKey | Fields], tuple_to_list(Record)).
diff --git a/src/mochiweb/reloader.erl b/src/mochiweb/reloader.erl
index 2ff154ba..6835f8f9 100644
--- a/src/mochiweb/reloader.erl
+++ b/src/mochiweb/reloader.erl
@@ -78,8 +78,7 @@ code_change(_Vsn, State, _Extra) ->
doit(From, To) ->
[case file:read_file_info(Filename) of
- {ok, FileInfo} when FileInfo#file_info.mtime >= From,
- FileInfo#file_info.mtime < To ->
+ {ok, #file_info{mtime = Mtime}} when Mtime >= From, Mtime < To ->
reload(Module);
{ok, _} ->
unmodified;