summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/mochiweb/mochijson.erl4
-rw-r--r--src/mochiweb/mochijson2.erl100
-rw-r--r--src/mochiweb/mochiweb.app5
-rw-r--r--src/mochiweb/mochiweb_headers.erl10
-rw-r--r--src/mochiweb/mochiweb_html.erl49
-rw-r--r--src/mochiweb/mochiweb_http.erl22
-rw-r--r--src/mochiweb/mochiweb_util.erl7
7 files changed, 176 insertions, 21 deletions
diff --git a/src/mochiweb/mochijson.erl b/src/mochiweb/mochijson.erl
index 0ce5b5eb..0e887a91 100644
--- a/src/mochiweb/mochijson.erl
+++ b/src/mochiweb/mochijson.erl
@@ -478,10 +478,6 @@ test_one([{E, J} | Rest], N) ->
true = equiv(E, decode(encode(E))),
test_one(Rest, 1+N).
-e2j_test_vec(unicode) ->
- [
- {"foo" ++ [500] ++ "bar", [$", $f, $o, $o, 500, $b, $a, $r, $"]}
- ];
e2j_test_vec(utf8) ->
[
{1, "1"},
diff --git a/src/mochiweb/mochijson2.erl b/src/mochiweb/mochijson2.erl
index 9b59c7df..8bfd23c8 100644
--- a/src/mochiweb/mochijson2.erl
+++ b/src/mochiweb/mochijson2.erl
@@ -132,13 +132,81 @@ json_encode_proplist(Props, State) ->
lists:reverse([$\} | Acc1]).
json_encode_string(A, _State) when is_atom(A) ->
- json_encode_string_unicode(xmerl_ucs:from_utf8(atom_to_list(A)), [?Q]);
+ 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])
+ end;
json_encode_string(B, _State) when is_binary(B) ->
- json_encode_string_unicode(xmerl_ucs:from_utf8(B), [?Q]);
+ case json_bin_is_safe(B) of
+ true ->
+ [?Q, B, ?Q];
+ false ->
+ json_encode_string_unicode(xmerl_ucs:from_utf8(B), [?Q])
+ end;
json_encode_string(I, _State) when is_integer(I) ->
- json_encode_string_unicode(integer_to_list(I), [?Q]);
+ [?Q, integer_to_list(I), ?Q];
json_encode_string(L, _State) when is_list(L) ->
- json_encode_string_unicode(L, [?Q]).
+ case json_string_is_safe(L) of
+ true ->
+ [?Q, L, ?Q];
+ false ->
+ json_encode_string_unicode(L, [?Q])
+ end.
+
+json_string_is_safe([]) ->
+ true;
+json_string_is_safe([C | Rest]) ->
+ case C of
+ ?Q ->
+ false;
+ $\\ ->
+ false;
+ $\b ->
+ false;
+ $\f ->
+ false;
+ $\n ->
+ false;
+ $\r ->
+ false;
+ $\t ->
+ false;
+ C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF ->
+ false;
+ C when C < 16#7f ->
+ json_string_is_safe(Rest);
+ _ ->
+ false
+ end.
+
+json_bin_is_safe(<<>>) ->
+ true;
+json_bin_is_safe(<<C, Rest/binary>>) ->
+ case C of
+ ?Q ->
+ false;
+ $\\ ->
+ false;
+ $\b ->
+ false;
+ $\f ->
+ false;
+ $\n ->
+ false;
+ $\r ->
+ false;
+ $\t ->
+ false;
+ C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF ->
+ false;
+ C when C < 16#7f ->
+ json_bin_is_safe(Rest);
+ _ ->
+ false
+ end.
json_encode_string_unicode([], Acc) ->
lists:reverse([$\" | Acc]);
@@ -260,8 +328,28 @@ decode_array(B, S=#decoder{state=comma}, Acc) ->
decode_array(B, S1#decoder{state=any}, Acc)
end.
-tokenize_string(B, S) ->
- tokenize_string(B, S, []).
+tokenize_string(B, S=#decoder{offset=O}) ->
+ case tokenize_string_fast(B, O) of
+ {escape, O1} ->
+ Length = O1 - O,
+ S1 = ?ADV_COL(S, Length),
+ <<_:O/binary, Head:Length/binary, _/binary>> = B,
+ tokenize_string(B, S1, lists:reverse(binary_to_list(Head)));
+ O1 ->
+ Length = O1 - O,
+ <<_:O/binary, String:Length/binary, ?Q, _/binary>> = B,
+ {{const, String}, ?ADV_COL(S, Length + 1)}
+ end.
+
+tokenize_string_fast(B, O) ->
+ case B of
+ <<_:O/binary, ?Q, _/binary>> ->
+ O;
+ <<_:O/binary, C, _/binary>> when C =/= $\\ ->
+ tokenize_string_fast(B, 1 + O);
+ _ ->
+ {escape, O}
+ end.
tokenize_string(B, S=#decoder{offset=O}, Acc) ->
case B of
diff --git a/src/mochiweb/mochiweb.app b/src/mochiweb/mochiweb.app
index dea08989..cd8dbb25 100644
--- a/src/mochiweb/mochiweb.app
+++ b/src/mochiweb/mochiweb.app
@@ -21,7 +21,10 @@
mochiweb_socket_server,
mochiweb_sup,
mochiweb_util,
- reloader
+ reloader,
+ mochifmt,
+ mochifmt_std,
+ mochifmt_records
]},
{registered, []},
{mod, {mochiweb_app, []}},
diff --git a/src/mochiweb/mochiweb_headers.erl b/src/mochiweb/mochiweb_headers.erl
index 371df9f5..4c0a2d75 100644
--- a/src/mochiweb/mochiweb_headers.erl
+++ b/src/mochiweb/mochiweb_headers.erl
@@ -6,7 +6,7 @@
-module(mochiweb_headers).
-author('bob@mochimedia.com').
-export([empty/0, from_list/1, insert/3, enter/3, get_value/2, lookup/2]).
--export([get_primary_value/2]).
+-export([delete_any/2, get_primary_value/2]).
-export([default/3, enter_from_list/2, default_from_list/2]).
-export([to_list/1, make/1]).
-export([test/0]).
@@ -35,6 +35,8 @@ test() ->
H3),
"application/x-www-form-urlencoded" = ?MODULE:get_primary_value(
"content-type", H4),
+ H4 = ?MODULE:delete_any("nonexistent-header", H4),
+ H3 = ?MODULE:delete_any("content-type", H4),
ok.
%% @spec empty() -> headers()
@@ -145,6 +147,12 @@ insert(K, V, T) ->
gb_trees:update(K1, {K0, V2}, T)
end.
+%% @spec delete_any(key(), headers()) -> headers()
+%% @doc Delete the header corresponding to key if it is present.
+delete_any(K, T) ->
+ K1 = normalize(K),
+ gb_trees:delete_any(K1, T).
+
%% Internal API
expand({array, L}) ->
diff --git a/src/mochiweb/mochiweb_html.erl b/src/mochiweb/mochiweb_html.erl
index 59181686..d9a3cf2e 100644
--- a/src/mochiweb/mochiweb_html.erl
+++ b/src/mochiweb/mochiweb_html.erl
@@ -129,7 +129,9 @@ escape_attr(F) when is_float(F) ->
test() ->
test_destack(),
test_tokens(),
+ test_tokens2(),
test_parse(),
+ test_parse2(),
test_parse_tokens(),
test_escape(),
test_escape_attr(),
@@ -426,6 +428,34 @@ test_parse() ->
Expect = parse(D0),
ok.
+test_tokens2() ->
+ D0 = <<"<channel><title>from __future__ import *</title><link>http://bob.pythonmac.org</link><description>Bob's Rants</description></channel>">>,
+ Expect = [{start_tag,<<"channel">>,[],false},
+ {start_tag,<<"title">>,[],false},
+ {data,<<"from __future__ import *">>,false},
+ {end_tag,<<"title">>},
+ {start_tag,<<"link">>,[],true},
+ {data,<<"http://bob.pythonmac.org">>,false},
+ {end_tag,<<"link">>},
+ {start_tag,<<"description">>,[],false},
+ {data,<<"Bob's Rants">>,false},
+ {end_tag,<<"description">>},
+ {end_tag,<<"channel">>}],
+ Expect = tokens(D0),
+ ok.
+
+test_parse2() ->
+ D0 = <<"<channel><title>from __future__ import *</title><link>http://bob.pythonmac.org<br>foo</link><description>Bob's Rants</description></channel>">>,
+ Expect = {<<"channel">>,[],
+ [{<<"title">>,[],[<<"from __future__ import *">>]},
+ {<<"link">>,[],[
+ <<"http://bob.pythonmac.org">>,
+ {<<"br">>,[],[]},
+ <<"foo">>]},
+ {<<"description">>,[],[<<"Bob's Rants">>]}]},
+ Expect = parse(D0),
+ ok.
+
test_parse_tokens() ->
D0 = [{doctype,[<<"HTML">>,<<"PUBLIC">>,<<"-//W3C//DTD HTML 4.01 Transitional//EN">>]},
{data,<<"\n">>,true},
@@ -562,8 +592,23 @@ destack(TagName, Stack) when is_list(Stack) ->
end,
case lists:splitwith(F, Stack) of
{_, []} ->
- %% No match, no state change
- Stack;
+ %% If we're parsing something like XML we might find
+ %% a <link>tag</link> that is normally a singleton
+ %% in HTML but isn't here
+ case {is_singleton(TagName), Stack} of
+ {true, [{T0, A0, Acc0} | Post0]} ->
+ case lists:splitwith(F, Acc0) of
+ {_, []} ->
+ %% Actually was a singleton
+ Stack;
+ {Pre, [{T1, A1, []} | Post1]} ->
+ [{T0, A0, [{T1, A1, lists:reverse(Pre)} | Post1]}
+ | Post0]
+ end;
+ _ ->
+ %% No match, no state change
+ Stack
+ end;
{_Pre, [_T]} ->
%% Unfurl the whole stack, we're done
destack(Stack);
diff --git a/src/mochiweb/mochiweb_http.erl b/src/mochiweb/mochiweb_http.erl
index 7bbe3c81..14a36578 100644
--- a/src/mochiweb/mochiweb_http.erl
+++ b/src/mochiweb/mochiweb_http.erl
@@ -10,6 +10,7 @@
-define(IDLE_TIMEOUT, 30000).
+-define(MAX_HEADERS, 1000).
-define(DEFAULTS, [{name, ?MODULE},
{port, 8888}]).
@@ -37,7 +38,7 @@ stop() ->
stop(Name) ->
mochiweb_socket_server:stop(Name).
-
+
start() ->
start([{ip, "127.0.0.1"},
{loop, {?MODULE, default_body}}]).
@@ -80,12 +81,12 @@ default_body(Req, 'POST', "/multipart") ->
{body, Req:recv_body()},
Req:dump()]]),
Req:ok({"text/html", [], frm(Body)});
-default_body(Req, 'POST', _Path) ->
+default_body(Req, 'POST', _Path) ->
Body = io_lib:format("~p~n", [[{parse_qs, Req:parse_qs()},
{parse_cookie, Req:parse_cookie()},
{parse_post, Req:parse_post()},
Req:dump()]]),
- Req:ok({"text/html", [], frm(Body)});
+ Req:ok({"text/html", [], frm(Body)});
default_body(Req, _Method, _Path) ->
Req:respond({501, [], []}).
@@ -99,7 +100,7 @@ loop(Socket, Body) ->
request(Socket, Body) ->
case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of
{ok, {http_request, Method, Path, Version}} ->
- headers(Socket, {Method, Path, Version}, [], Body);
+ headers(Socket, {Method, Path, Version}, [], Body, 0);
{error, {http_error, "\r\n"}} ->
request(Socket, Body);
{error, {http_error, "\n"}} ->
@@ -109,7 +110,15 @@ request(Socket, Body) ->
exit(normal)
end.
-headers(Socket, Request, Headers, Body) ->
+headers(Socket, Request, Headers, _Body, ?MAX_HEADERS) ->
+ %% Too many headers sent, bad request.
+ inet:setopts(Socket, [{packet, raw}]),
+ Req = mochiweb:new_request({Socket, Request,
+ lists:reverse(Headers)}),
+ Req:respond({400, [], []}),
+ gen_tcp:close(Socket),
+ exit(normal);
+headers(Socket, Request, Headers, Body, HeaderCount) ->
case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of
{ok, http_eoh} ->
inet:setopts(Socket, [{packet, raw}]),
@@ -125,7 +134,8 @@ headers(Socket, Request, Headers, Body) ->
?MODULE:loop(Socket, Body)
end;
{ok, {http_header, _, Name, _, Value}} ->
- headers(Socket, Request, [{Name, Value} | Headers], Body);
+ headers(Socket, Request, [{Name, Value} | Headers], Body,
+ 1 + HeaderCount);
_Other ->
gen_tcp:close(Socket),
exit(normal)
diff --git a/src/mochiweb/mochiweb_util.erl b/src/mochiweb/mochiweb_util.erl
index 63d0b986..9a0675f5 100644
--- a/src/mochiweb/mochiweb_util.erl
+++ b/src/mochiweb/mochiweb_util.erl
@@ -134,12 +134,16 @@ revjoin([S | Rest], Separator, []) ->
revjoin([S | Rest], Separator, Acc) ->
revjoin(Rest, Separator, [S, Separator | Acc]).
-%% @spec quote_plus(atom() | integer() | string()) -> string()
+%% @spec quote_plus(atom() | integer() | float() | string() | binary()) -> string()
%% @doc URL safe encoding of the given term.
quote_plus(Atom) when is_atom(Atom) ->
quote_plus(atom_to_list(Atom));
quote_plus(Int) when is_integer(Int) ->
quote_plus(integer_to_list(Int));
+quote_plus(Binary) when is_binary(Binary) ->
+ quote_plus(binary_to_list(Binary));
+quote_plus(Float) when is_float(Float) ->
+ quote_plus(mochinum:digits(Float));
quote_plus(String) ->
quote_plus(String, []).
@@ -542,6 +546,7 @@ test_join() ->
test_quote_plus() ->
"foo" = quote_plus(foo),
"1" = quote_plus(1),
+ "1.1" = quote_plus(1.1),
"foo" = quote_plus("foo"),
"foo+bar" = quote_plus("foo bar"),
"foo%0A" = quote_plus("foo\n"),