From 1ba7a12c72cfb645c36187bbb95ea9160c8a3284 Mon Sep 17 00:00:00 2001 From: Christopher Lenz Date: Tue, 24 Feb 2009 22:10:04 +0000 Subject: Update MochiWeb in trunk to r97. Closes COUCHDB-255. git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@747575 13f79535-47bb-0310-9956-ffa450edef68 --- src/mochiweb/mochijson.erl | 4 -- src/mochiweb/mochijson2.erl | 100 +++++++++++++++++++++++++++++++++++--- src/mochiweb/mochiweb.app | 5 +- src/mochiweb/mochiweb_headers.erl | 10 +++- src/mochiweb/mochiweb_html.erl | 49 ++++++++++++++++++- src/mochiweb/mochiweb_http.erl | 22 ++++++--- src/mochiweb/mochiweb_util.erl | 7 ++- 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(<>) -> + 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 = <<"from __future__ import *http://bob.pythonmac.orgBob's Rants">>, + 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 = <<"from __future__ import *http://bob.pythonmac.org
fooBob's Rants
">>, + 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 tag 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"), -- cgit v1.2.3