diff options
Diffstat (limited to 'src/mochiweb')
-rw-r--r-- | src/mochiweb/mochijson.erl | 136 | ||||
-rw-r--r-- | src/mochiweb/mochijson2.erl | 150 | ||||
-rw-r--r-- | src/mochiweb/mochiweb.erl | 30 | ||||
-rw-r--r-- | src/mochiweb/mochiweb_cookies.erl | 66 | ||||
-rw-r--r-- | src/mochiweb/mochiweb_echo.erl | 26 | ||||
-rw-r--r-- | src/mochiweb/mochiweb_headers.erl | 38 | ||||
-rw-r--r-- | src/mochiweb/mochiweb_html.erl | 4 | ||||
-rw-r--r-- | src/mochiweb/mochiweb_http.erl | 92 | ||||
-rw-r--r-- | src/mochiweb/mochiweb_multipart.erl | 374 | ||||
-rw-r--r-- | src/mochiweb/mochiweb_request.erl | 282 | ||||
-rw-r--r-- | src/mochiweb/mochiweb_response.erl | 19 | ||||
-rw-r--r-- | src/mochiweb/mochiweb_socket_server.erl | 134 | ||||
-rw-r--r-- | src/mochiweb/mochiweb_util.erl | 120 | ||||
-rw-r--r-- | src/mochiweb/reloader.erl | 37 |
14 files changed, 776 insertions, 732 deletions
diff --git a/src/mochiweb/mochijson.erl b/src/mochiweb/mochijson.erl index 6ea7ec56..0ce5b5eb 100644 --- a/src/mochiweb/mochijson.erl +++ b/src/mochiweb/mochijson.erl @@ -39,13 +39,13 @@ %% @type binary_decoder_option() = {object_hook, function()} -record(encoder, {input_encoding=unicode, - handler=null}). + handler=null}). -record(decoder, {input_encoding=utf8, - object_hook=null, - line=1, - column=1, - state=null}). + object_hook=null, + line=1, + column=1, + state=null}). %% @spec encoder([encoder_option()]) -> function() %% @doc Create an encoder/1 with the given options. @@ -136,8 +136,8 @@ json_encode_array([], _State) -> "[]"; json_encode_array(L, State) -> F = fun (O, Acc) -> - [$,, json_encode(O, State) | Acc] - end, + [$,, json_encode(O, State) | Acc] + end, [$, | Acc1] = lists:foldl(F, "[", L), lists:reverse([$\] | Acc1]). @@ -145,17 +145,17 @@ json_encode_proplist([], _State) -> "{}"; json_encode_proplist(Props, State) -> F = fun ({K, V}, Acc) -> - KS = case K of - K when is_atom(K) -> - json_encode_string_utf8(atom_to_list(K)); - K when is_integer(K) -> - json_encode_string(integer_to_list(K), State); - K when is_list(K); is_binary(K) -> - json_encode_string(K, State) - end, - VS = json_encode(V, State), - [$,, VS, $:, KS | Acc] - end, + KS = case K of + K when is_atom(K) -> + json_encode_string_utf8(atom_to_list(K)); + K when is_integer(K) -> + json_encode_string(integer_to_list(K), State); + K when is_list(K); is_binary(K) -> + json_encode_string(K, State) + end, + VS = json_encode(V, State), + [$,, VS, $:, KS | Acc] + end, [$, | Acc1] = lists:foldl(F, "{", Props), lists:reverse([$\} | Acc1]). @@ -241,12 +241,12 @@ json_decode(L, S) -> decode1(L, S=#decoder{state=null}) -> case tokenize(L, S#decoder{state=any}) of - {{const, C}, L1, S1} -> - {C, L1, S1}; - {start_array, L1, S1} -> - decode_array(L1, S1#decoder{state=any}, []); - {start_object, L1, S1} -> - decode_object(L1, S1#decoder{state=key}, []) + {{const, C}, L1, S1} -> + {C, L1, S1}; + {start_array, L1, S1} -> + decode_array(L1, S1#decoder{state=any}, []); + {start_object, L1, S1} -> + decode_object(L1, S1#decoder{state=key}, []) end. make_object(V, #decoder{object_hook=null}) -> @@ -256,42 +256,42 @@ make_object(V, #decoder{object_hook=Hook}) -> decode_object(L, S=#decoder{state=key}, Acc) -> case tokenize(L, S) of - {end_object, Rest, S1} -> - V = make_object({struct, lists:reverse(Acc)}, S1), - {V, Rest, S1#decoder{state=null}}; - {{const, K}, Rest, S1} when is_list(K) -> - {colon, L2, S2} = tokenize(Rest, S1), - {V, L3, S3} = decode1(L2, S2#decoder{state=null}), - decode_object(L3, S3#decoder{state=comma}, [{K, V} | Acc]) + {end_object, Rest, S1} -> + V = make_object({struct, lists:reverse(Acc)}, S1), + {V, Rest, S1#decoder{state=null}}; + {{const, K}, Rest, S1} when is_list(K) -> + {colon, L2, S2} = tokenize(Rest, S1), + {V, L3, S3} = decode1(L2, S2#decoder{state=null}), + decode_object(L3, S3#decoder{state=comma}, [{K, V} | Acc]) end; decode_object(L, S=#decoder{state=comma}, Acc) -> case tokenize(L, S) of - {end_object, Rest, S1} -> - V = make_object({struct, lists:reverse(Acc)}, S1), - {V, Rest, S1#decoder{state=null}}; - {comma, Rest, S1} -> - decode_object(Rest, S1#decoder{state=key}, Acc) + {end_object, Rest, S1} -> + V = make_object({struct, lists:reverse(Acc)}, S1), + {V, Rest, S1#decoder{state=null}}; + {comma, Rest, S1} -> + decode_object(Rest, S1#decoder{state=key}, Acc) end. decode_array(L, S=#decoder{state=any}, Acc) -> case tokenize(L, S) of - {end_array, Rest, S1} -> - {{array, lists:reverse(Acc)}, Rest, S1#decoder{state=null}}; - {start_array, Rest, S1} -> - {Array, Rest1, S2} = decode_array(Rest, S1#decoder{state=any}, []), - decode_array(Rest1, S2#decoder{state=comma}, [Array | Acc]); - {start_object, Rest, S1} -> - {Array, Rest1, S2} = decode_object(Rest, S1#decoder{state=key}, []), - decode_array(Rest1, S2#decoder{state=comma}, [Array | Acc]); - {{const, Const}, Rest, S1} -> - decode_array(Rest, S1#decoder{state=comma}, [Const | Acc]) + {end_array, Rest, S1} -> + {{array, lists:reverse(Acc)}, Rest, S1#decoder{state=null}}; + {start_array, Rest, S1} -> + {Array, Rest1, S2} = decode_array(Rest, S1#decoder{state=any}, []), + decode_array(Rest1, S2#decoder{state=comma}, [Array | Acc]); + {start_object, Rest, S1} -> + {Array, Rest1, S2} = decode_object(Rest, S1#decoder{state=key}, []), + decode_array(Rest1, S2#decoder{state=comma}, [Array | Acc]); + {{const, Const}, Rest, S1} -> + decode_array(Rest, S1#decoder{state=comma}, [Const | Acc]) end; decode_array(L, S=#decoder{state=comma}, Acc) -> case tokenize(L, S) of - {end_array, Rest, S1} -> - {{array, lists:reverse(Acc)}, Rest, S1#decoder{state=null}}; - {comma, Rest, S1} -> - decode_array(Rest, S1#decoder{state=any}, Acc) + {end_array, Rest, S1} -> + {{array, lists:reverse(Acc)}, Rest, S1#decoder{state=null}}; + {comma, Rest, S1} -> + decode_array(Rest, S1#decoder{state=any}, Acc) end. tokenize_string(IoList=[C | _], S=#decoder{input_encoding=utf8}, Acc) @@ -319,9 +319,9 @@ tokenize_string("\\t" ++ Rest, S, Acc) -> tokenize_string([$\\, $u, C3, C2, C1, C0 | Rest], S, Acc) -> % coalesce UTF-16 surrogate pair? C = dehex(C0) bor - (dehex(C1) bsl 4) bor - (dehex(C2) bsl 8) bor - (dehex(C3) bsl 12), + (dehex(C1) bsl 4) bor + (dehex(C2) bsl 8) bor + (dehex(C3) bsl 12), tokenize_string(Rest, ?ADV_COL(S, 6), [C | Acc]); tokenize_string([C | Rest], S, Acc) when C >= $\s; C < 16#10FFFF -> tokenize_string(Rest, ?ADV_COL(S, 1), [C | Acc]). @@ -400,10 +400,10 @@ tokenize("\"" ++ Rest, S) -> {{const, String}, Rest1, S1}; tokenize(L=[C | _], S) when C >= $0, C =< $9; C == $- -> case tokenize_number(L, sign, S, []) of - {{int, Int}, Rest, S1} -> - {{const, list_to_integer(Int)}, Rest, S1}; - {{float, Float}, Rest, S1} -> - {{const, list_to_float(Float)}, Rest, S1} + {{int, Int}, Rest, S1} -> + {{const, list_to_integer(Int)}, Rest, S1}; + {{float, Float}, Rest, S1} -> + {{const, list_to_float(Float)}, Rest, S1} end. %% testing constructs borrowed from the Yaws JSON implementation. @@ -415,10 +415,10 @@ obj_new() -> is_obj({struct, Props}) -> F = fun ({K, _}) when is_list(K) -> - true; - (_) -> - false - end, + true; + (_) -> + false + end, lists:all(F, Props). obj_from_list(Props) -> @@ -437,8 +437,8 @@ equiv({struct, Props1}, {struct, Props2}) -> equiv_object(Props1, Props2); equiv({array, L1}, {array, L2}) -> equiv_list(L1, L2); -equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2; -equiv(S1, S2) when is_list(S1), is_list(S2) -> S1 == S2; +equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2; +equiv(S1, S2) when is_list(S1), is_list(S2) -> S1 == S2; equiv(true, true) -> true; equiv(false, false) -> true; equiv(null, null) -> true. @@ -451,7 +451,7 @@ equiv_object(Props1, Props2) -> L2 = lists:keysort(1, Props2), Pairs = lists:zip(L1, L2), true = lists:all(fun({{K1, V1}, {K2, V2}}) -> - equiv(K1, K2) and equiv(V1, V2) + equiv(K1, K2) and equiv(V1, V2) end, Pairs). %% Recursively compare tuple elements for equivalence. @@ -460,10 +460,10 @@ equiv_list([], []) -> true; equiv_list([V1 | L1], [V2 | L2]) -> case equiv(V1, V2) of - true -> - equiv_list(L1, L2); - false -> - false + true -> + equiv_list(L1, L2); + false -> + false end. test_all() -> diff --git a/src/mochiweb/mochijson2.erl b/src/mochiweb/mochijson2.erl index dfaffbab..592b4790 100644 --- a/src/mochiweb/mochijson2.erl +++ b/src/mochiweb/mochijson2.erl @@ -31,7 +31,7 @@ offset=1+S#decoder.offset} end). -define(IS_WHITESPACE(C), - (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). + (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). %% @type iolist() = [char() | binary() | iolist()] %% @type iodata() = iolist() | binary() @@ -45,10 +45,10 @@ -record(encoder, {handler=null}). -record(decoder, {object_hook=null, - offset=0, + offset=0, line=1, - column=1, - state=null}). + column=1, + state=null}). %% @spec encoder([encoder_option()]) -> function() %% @doc Create an encoder/1 with the given options. @@ -115,8 +115,8 @@ json_encode_array([], _State) -> <<"[]">>; json_encode_array(L, State) -> F = fun (O, Acc) -> - [$,, json_encode(O, State) | Acc] - end, + [$,, json_encode(O, State) | Acc] + end, [$, | Acc1] = lists:foldl(F, "[", L), lists:reverse([$\] | Acc1]). @@ -124,10 +124,10 @@ json_encode_proplist([], _State) -> <<"{}">>; json_encode_proplist(Props, State) -> F = fun ({K, V}, Acc) -> - KS = json_encode_string(K, State), - VS = json_encode(V, State), - [$,, VS, $:, KS | Acc] - end, + KS = json_encode_string(K, State), + VS = json_encode(V, State), + [$,, VS, $:, KS | Acc] + end, [$, | Acc1] = lists:foldl(F, "{", Props), lists:reverse([$\} | Acc1]). @@ -144,8 +144,8 @@ json_encode_string_unicode([], Acc) -> lists:reverse([$\" | Acc]); json_encode_string_unicode([C | Cs], Acc) -> Acc1 = case C of - ?Q -> - [?Q, $\\ | Acc]; + ?Q -> + [?Q, $\\ | Acc]; %% Escaping solidus is only useful when trying to protect %% against "</script>" injection attacks which are only %% possible when JSON is inserted into a HTML document @@ -153,28 +153,28 @@ json_encode_string_unicode([C | Cs], Acc) -> %% if you do insert directly into HTML then you need to %% uncomment the following case or escape the output of encode. %% - %% $/ -> - %% [$/, $\\ | Acc]; + %% $/ -> + %% [$/, $\\ | Acc]; %% - $\\ -> - [$\\, $\\ | Acc]; - $\b -> - [$b, $\\ | Acc]; - $\f -> - [$f, $\\ | Acc]; - $\n -> - [$n, $\\ | Acc]; - $\r -> - [$r, $\\ | Acc]; - $\t -> - [$t, $\\ | Acc]; - C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF -> - [unihex(C) | Acc]; - C when C < 16#7f -> - [C | Acc]; - _ -> - exit({json_encode, {bad_char, C}}) - end, + $\\ -> + [$\\, $\\ | Acc]; + $\b -> + [$b, $\\ | Acc]; + $\f -> + [$f, $\\ | Acc]; + $\n -> + [$n, $\\ | Acc]; + $\r -> + [$r, $\\ | Acc]; + $\t -> + [$t, $\\ | Acc]; + C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF -> + [unihex(C) | Acc]; + C when C < 16#7f -> + [C | Acc]; + _ -> + exit({json_encode, {bad_char, C}}) + end, json_encode_string_unicode(Cs, Acc1). hexdigit(C) when C >= 0, C =< 9 -> @@ -201,12 +201,12 @@ json_decode(B, S) -> decode1(B, S=#decoder{state=null}) -> case tokenize(B, S#decoder{state=any}) of - {{const, C}, S1} -> - {C, S1}; - {start_array, S1} -> - decode_array(B, S1); - {start_object, S1} -> - decode_object(B, S1) + {{const, C}, S1} -> + {C, S1}; + {start_array, S1} -> + decode_array(B, S1); + {start_object, S1} -> + decode_object(B, S1) end. make_object(V, #decoder{object_hook=null}) -> @@ -219,21 +219,21 @@ decode_object(B, S) -> decode_object(B, S=#decoder{state=key}, Acc) -> case tokenize(B, S) of - {end_object, S1} -> - V = make_object({struct, lists:reverse(Acc)}, S1), - {V, S1#decoder{state=null}}; - {{const, K}, S1} -> - {colon, S2} = tokenize(B, S1), - {V, S3} = decode1(B, S2#decoder{state=null}), - decode_object(B, S3#decoder{state=comma}, [{K, V} | Acc]) + {end_object, S1} -> + V = make_object({struct, lists:reverse(Acc)}, S1), + {V, S1#decoder{state=null}}; + {{const, K}, S1} -> + {colon, S2} = tokenize(B, S1), + {V, S3} = decode1(B, S2#decoder{state=null}), + decode_object(B, S3#decoder{state=comma}, [{K, V} | Acc]) end; decode_object(B, S=#decoder{state=comma}, Acc) -> case tokenize(B, S) of - {end_object, S1} -> - V = make_object({struct, lists:reverse(Acc)}, S1), - {V, S1#decoder{state=null}}; - {comma, S1} -> - decode_object(B, S1#decoder{state=key}, Acc) + {end_object, S1} -> + V = make_object({struct, lists:reverse(Acc)}, S1), + {V, S1#decoder{state=null}}; + {comma, S1} -> + decode_object(B, S1#decoder{state=key}, Acc) end. decode_array(B, S) -> @@ -241,23 +241,23 @@ decode_array(B, S) -> decode_array(B, S=#decoder{state=any}, Acc) -> case tokenize(B, S) of - {end_array, S1} -> - {lists:reverse(Acc), S1#decoder{state=null}}; - {start_array, S1} -> - {Array, S2} = decode_array(B, S1), - decode_array(B, S2#decoder{state=comma}, [Array | Acc]); - {start_object, S1} -> - {Array, S2} = decode_object(B, S1), - decode_array(B, S2#decoder{state=comma}, [Array | Acc]); - {{const, Const}, S1} -> - decode_array(B, S1#decoder{state=comma}, [Const | Acc]) + {end_array, S1} -> + {lists:reverse(Acc), S1#decoder{state=null}}; + {start_array, S1} -> + {Array, S2} = decode_array(B, S1), + decode_array(B, S2#decoder{state=comma}, [Array | Acc]); + {start_object, S1} -> + {Array, S2} = decode_object(B, S1), + decode_array(B, S2#decoder{state=comma}, [Array | Acc]); + {{const, Const}, S1} -> + decode_array(B, S1#decoder{state=comma}, [Const | Acc]) end; decode_array(B, S=#decoder{state=comma}, Acc) -> case tokenize(B, S) of - {end_array, S1} -> - {lists:reverse(Acc), S1#decoder{state=null}}; - {comma, S1} -> - decode_array(B, S1#decoder{state=any}, Acc) + {end_array, S1} -> + {lists:reverse(Acc), S1#decoder{state=null}}; + {comma, S1} -> + decode_array(B, S1#decoder{state=any}, Acc) end. tokenize_string(B, S) -> @@ -400,10 +400,10 @@ obj_new() -> is_obj({struct, Props}) -> F = fun ({K, _}) when is_binary(K) -> - true; - (_) -> - false - end, + true; + (_) -> + false + end, lists:all(F, Props). obj_from_list(Props) -> @@ -422,8 +422,8 @@ equiv({struct, Props1}, {struct, Props2}) -> equiv_object(Props1, Props2); equiv(L1, L2) when is_list(L1), is_list(L2) -> equiv_list(L1, L2); -equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2; -equiv(B1, B2) when is_binary(B1), is_binary(B2) -> B1 == B2; +equiv(N1, N2) when is_number(N1), is_number(N2) -> N1 == N2; +equiv(B1, B2) when is_binary(B1), is_binary(B2) -> B1 == B2; equiv(true, true) -> true; equiv(false, false) -> true; equiv(null, null) -> true. @@ -445,10 +445,10 @@ equiv_list([], []) -> true; equiv_list([V1 | L1], [V2 | L2]) -> case equiv(V1, V2) of - true -> - equiv_list(L1, L2); - false -> - false + true -> + equiv_list(L1, L2); + false -> + false end. test_all() -> diff --git a/src/mochiweb/mochiweb.erl b/src/mochiweb/mochiweb.erl index 6508f304..baf9c0c5 100644 --- a/src/mochiweb/mochiweb.erl +++ b/src/mochiweb/mochiweb.erl @@ -65,25 +65,25 @@ all_loaded(Base) -> %% @doc Return a mochiweb_request data structure. new_request({Socket, {Method, {abs_path, Uri}, Version}, Headers}) -> mochiweb_request:new(Socket, - Method, - Uri, - Version, - mochiweb_headers:make(Headers)); + Method, + Uri, + Version, + mochiweb_headers:make(Headers)); % this case probably doesn't "exist". new_request({Socket, {Method, {absoluteURI, _Protocol, _Host, _Port, Uri}, - Version}, Headers}) -> + Version}, Headers}) -> mochiweb_request:new(Socket, - Method, - Uri, - Version, - mochiweb_headers:make(Headers)). + Method, + Uri, + Version, + mochiweb_headers:make(Headers)). %% @spec new_response({Request, integer(), Headers}) -> MochiWebResponse %% @doc Return a mochiweb_response data structure. new_response({Request, Code, Headers}) -> mochiweb_response:new(Request, - Code, - mochiweb_headers:make(Headers)). + Code, + mochiweb_headers:make(Headers)). %% Internal API @@ -94,8 +94,8 @@ test_request() -> ensure_started(App) -> case application:start(App) of - ok -> - ok; - {error, {already_started, App}} -> - ok + ok -> + ok; + {error, {already_started, App}} -> + ok end. diff --git a/src/mochiweb/mochiweb_cookies.erl b/src/mochiweb/mochiweb_cookies.erl index 1961233c..acea12ae 100644 --- a/src/mochiweb/mochiweb_cookies.erl +++ b/src/mochiweb/mochiweb_cookies.erl @@ -9,15 +9,15 @@ -define(QUOTE, $\"). -define(IS_WHITESPACE(C), - (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). + (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). %% RFC 2616 separators (called tspecials in RFC 2068) -define(IS_SEPARATOR(C), - (C < 32 orelse - C =:= $\s orelse C =:= $\t orelse - C =:= $( orelse C =:= $) orelse C =:= $< orelse C =:= $> orelse - C =:= $@ orelse C =:= $, orelse C =:= $; orelse C =:= $: orelse - C =:= $\\ orelse C =:= $\" orelse C =:= $/ orelse + (C < 32 orelse + C =:= $\s orelse C =:= $\t orelse + C =:= $( orelse C =:= $) orelse C =:= $< orelse C =:= $> orelse + C =:= $@ orelse C =:= $, orelse C =:= $; orelse C =:= $: orelse + C =:= $\\ orelse C =:= $\" orelse C =:= $/ orelse C =:= $[ orelse C =:= $] orelse C =:= $? orelse C =:= $= orelse C =:= ${ orelse C =:= $})). @@ -137,13 +137,13 @@ parse_cookie([], Acc) -> parse_cookie(String, Acc) -> {{Token, Value}, Rest} = read_pair(String), Acc1 = case Token of - "" -> - Acc; - "$" ++ _ -> - Acc; - _ -> - [{Token, Value} | Acc] - end, + "" -> + Acc; + "$" ++ _ -> + Acc; + _ -> + [{Token, Value} | Acc] + end, parse_cookie(Rest, Acc1). read_pair(String) -> @@ -154,10 +154,10 @@ read_pair(String) -> read_value([$= | Value]) -> Value1 = skip_whitespace(Value), case Value1 of - [?QUOTE | _] -> - read_quoted(Value1); - _ -> - read_token(Value1) + [?QUOTE | _] -> + read_quoted(Value1); + _ -> + read_token(Value1) end; read_value(String) -> {"", String}. @@ -221,30 +221,30 @@ any_to_list(V) when is_integer(V) -> cookie_test() -> C1 = {"Set-Cookie", - "Customer=WILE_E_COYOTE; " - "Version=1; " - "Path=/acme"}, + "Customer=WILE_E_COYOTE; " + "Version=1; " + "Path=/acme"}, C1 = cookie("Customer", "WILE_E_COYOTE", [{path, "/acme"}]), C1 = cookie("Customer", "WILE_E_COYOTE", - [{path, "/acme"}, {badoption, "negatory"}]), + [{path, "/acme"}, {badoption, "negatory"}]), C1 = cookie('Customer', 'WILE_E_COYOTE', [{path, '/acme'}]), C1 = cookie(<<"Customer">>, <<"WILE_E_COYOTE">>, [{path, <<"/acme">>}]), {"Set-Cookie","=NoKey; Version=1"} = cookie("", "NoKey", []), - - LocalTime = calendar:universal_time_to_local_time({{2007, 5, 15}, {13, 45, 33}}), + + LocalTime = calendar:universal_time_to_local_time({{2007, 5, 15}, {13, 45, 33}}), C2 = {"Set-Cookie", - "Customer=WILE_E_COYOTE; " - "Version=1; " - "Expires=Tue, 15 May 2007 13:45:33 GMT; " - "Max-Age=0"}, + "Customer=WILE_E_COYOTE; " + "Version=1; " + "Expires=Tue, 15 May 2007 13:45:33 GMT; " + "Max-Age=0"}, C2 = cookie("Customer", "WILE_E_COYOTE", - [{max_age, -111}, {local_time, LocalTime}]), + [{max_age, -111}, {local_time, LocalTime}]), C3 = {"Set-Cookie", - "Customer=WILE_E_COYOTE; " - "Version=1; " - "Expires=Wed, 16 May 2007 13:45:50 GMT; " - "Max-Age=86417"}, + "Customer=WILE_E_COYOTE; " + "Version=1; " + "Expires=Wed, 16 May 2007 13:45:50 GMT; " + "Max-Age=86417"}, C3 = cookie("Customer", "WILE_E_COYOTE", - [{max_age, 86417}, {local_time, LocalTime}]), + [{max_age, 86417}, {local_time, LocalTime}]), ok. diff --git a/src/mochiweb/mochiweb_echo.erl b/src/mochiweb/mochiweb_echo.erl index f164f02a..f0c455a5 100644 --- a/src/mochiweb/mochiweb_echo.erl +++ b/src/mochiweb/mochiweb_echo.erl @@ -12,20 +12,20 @@ stop() -> start() -> mochiweb_socket_server:start([{name, ?MODULE}, - {port, 6789}, - {ip, "127.0.0.1"}, - {max, 1}, - {loop, {?MODULE, loop}}]). + {port, 6789}, + {ip, "127.0.0.1"}, + {max, 1}, + {loop, {?MODULE, loop}}]). loop(Socket) -> case gen_tcp:recv(Socket, 0, 30000) of - {ok, Data} -> - case gen_tcp:send(Socket, Data) of - ok -> - loop(Socket); - _ -> - exit(normal) - end; - _Other -> - exit(normal) + {ok, Data} -> + case gen_tcp:send(Socket, Data) of + ok -> + loop(Socket); + _ -> + exit(normal) + end; + _Other -> + exit(normal) end. diff --git a/src/mochiweb/mochiweb_headers.erl b/src/mochiweb/mochiweb_headers.erl index 5b538aa7..df67c7d0 100644 --- a/src/mochiweb/mochiweb_headers.erl +++ b/src/mochiweb/mochiweb_headers.erl @@ -71,11 +71,11 @@ default_from_list(List, T) -> %% preserved). to_list(T) -> F = fun ({K, {array, L}}, Acc) -> - L1 = lists:reverse(L), - lists:foldl(fun (V, Acc1) -> [{K, V} | Acc1] end, Acc, L1); - (Pair, Acc) -> - [Pair | Acc] - end, + L1 = lists:reverse(L), + lists:foldl(fun (V, Acc1) -> [{K, V} | Acc1] end, Acc, L1); + (Pair, Acc) -> + [Pair | Acc] + end, lists:reverse(lists:foldl(F, [], gb_trees:values(T))). %% @spec get_value(key(), headers()) -> string() | undefined @@ -83,10 +83,10 @@ to_list(T) -> %% undefined will be returned for keys that are not present. get_value(K, T) -> case lookup(K, T) of - {value, {_, V}} -> - expand(V); - none -> - undefined + {value, {_, V}} -> + expand(V); + none -> + undefined end. %% @spec get_primary_value(key(), headers()) -> string() | undefined @@ -107,10 +107,10 @@ get_primary_value(K, T) -> %% not present. lookup(K, T) -> case gb_trees:lookup(normalize(K), T) of - {value, {K0, V}} -> - {value, {K0, expand(V)}}; - none -> - none + {value, {K0, V}} -> + {value, {K0, expand(V)}}; + none -> + none end. %% @spec default(key(), value(), headers()) -> headers() @@ -120,8 +120,8 @@ default(K, V, T) -> V1 = any_to_list(V), try gb_trees:insert(K1, {K, V1}, T) catch - error:{key_exists, _} -> - T + error:{key_exists, _} -> + T end. %% @spec enter(key(), value(), headers()) -> headers() @@ -139,10 +139,10 @@ insert(K, V, T) -> V1 = any_to_list(V), try gb_trees:insert(K1, {K, V1}, T) catch - error:{key_exists, _} -> - {K0, V0} = gb_trees:get(K1, T), - V2 = merge(K1, V1, V0), - gb_trees:update(K1, {K0, V2}, T) + error:{key_exists, _} -> + {K0, V0} = gb_trees:get(K1, T), + V2 = merge(K1, V1, V0), + gb_trees:update(K1, {K0, V2}, T) end. %% Internal API diff --git a/src/mochiweb/mochiweb_html.erl b/src/mochiweb/mochiweb_html.erl index 85e6935e..3c7f9dfc 100644 --- a/src/mochiweb/mochiweb_html.erl +++ b/src/mochiweb/mochiweb_html.erl @@ -31,13 +31,13 @@ end). -define(IS_WHITESPACE(C), - (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). + (C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)). -define(IS_LITERAL_SAFE(C), ((C >= $A andalso C =< $Z) orelse (C >= $a andalso C =< $z) orelse (C >= $0 andalso C =< $9))). -record(decoder, {line=1, - column=1, + column=1, offset=0}). %% @type html_node() = {string(), [html_attr()], [html_node() | string()]} diff --git a/src/mochiweb/mochiweb_http.erl b/src/mochiweb/mochiweb_http.erl index 10c51220..7bbe3c81 100644 --- a/src/mochiweb/mochiweb_http.erl +++ b/src/mochiweb/mochiweb_http.erl @@ -11,14 +11,14 @@ -define(IDLE_TIMEOUT, 30000). -define(DEFAULTS, [{name, ?MODULE}, - {port, 8888}]). + {port, 8888}]). set_default({Prop, Value}, PropList) -> case proplists:is_defined(Prop, PropList) of - true -> - PropList; - false -> - [{Prop, Value} | PropList] + true -> + PropList; + false -> + [{Prop, Value} | PropList] end. set_defaults(Defaults, PropList) -> @@ -27,8 +27,8 @@ set_defaults(Defaults, PropList) -> parse_options(Options) -> {loop, HttpLoop} = proplists:lookup(loop, Options), Loop = fun (S) -> - ?MODULE:loop(S, HttpLoop) - end, + ?MODULE:loop(S, HttpLoop) + end, Options1 = [{loop, Loop} | proplists:delete(loop, Options)], set_defaults(?DEFAULTS, Options1). @@ -40,7 +40,7 @@ stop(Name) -> start() -> start([{ip, "127.0.0.1"}, - {loop, {?MODULE, default_body}}]). + {loop, {?MODULE, default_body}}]). start(Options) -> mochiweb_socket_server:start(parse_options(Options)). @@ -69,23 +69,23 @@ default_body(Req, M, "/chunked") when M =:= 'GET'; M =:= 'HEAD' -> Res:write_chunk(""); default_body(Req, M, _Path) when M =:= 'GET'; M =:= 'HEAD' -> Body = io_lib:format("~p~n", [[{parse_qs, Req:parse_qs()}, - {parse_cookie, Req:parse_cookie()}, - Req:dump()]]), + {parse_cookie, Req:parse_cookie()}, + Req:dump()]]), Req:ok({"text/html", - [mochiweb_cookies:cookie("mochiweb_http", "test_cookie")], - frm(Body)}); + [mochiweb_cookies:cookie("mochiweb_http", "test_cookie")], + frm(Body)}); default_body(Req, 'POST', "/multipart") -> Body = io_lib:format("~p~n", [[{parse_qs, Req:parse_qs()}, - {parse_cookie, Req:parse_cookie()}, - {body, Req:recv_body()}, - Req:dump()]]), + {parse_cookie, Req:parse_cookie()}, + {body, Req:recv_body()}, + Req:dump()]]), Req:ok({"text/html", [], frm(Body)}); 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)}); + {parse_cookie, Req:parse_cookie()}, + {parse_post, Req:parse_post()}, + Req:dump()]]), + Req:ok({"text/html", [], frm(Body)}); default_body(Req, _Method, _Path) -> Req:respond({501, [], []}). @@ -98,35 +98,35 @@ 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); - {error, {http_error, "\r\n"}} -> - request(Socket, Body); - {error, {http_error, "\n"}} -> - request(Socket, Body); - _Other -> - gen_tcp:close(Socket), - exit(normal) + {ok, {http_request, Method, Path, Version}} -> + headers(Socket, {Method, Path, Version}, [], Body); + {error, {http_error, "\r\n"}} -> + request(Socket, Body); + {error, {http_error, "\n"}} -> + request(Socket, Body); + _Other -> + gen_tcp:close(Socket), + exit(normal) end. headers(Socket, Request, Headers, Body) -> case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of - {ok, http_eoh} -> - inet:setopts(Socket, [{packet, raw}]), - 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; - {ok, {http_header, _, Name, _, Value}} -> - headers(Socket, Request, [{Name, Value} | Headers], Body); - _Other -> - gen_tcp:close(Socket), - exit(normal) + {ok, http_eoh} -> + inet:setopts(Socket, [{packet, raw}]), + 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; + {ok, {http_header, _, Name, _, Value}} -> + headers(Socket, Request, [{Name, Value} | Headers], Body); + _Other -> + gen_tcp:close(Socket), + exit(normal) end. diff --git a/src/mochiweb/mochiweb_multipart.erl b/src/mochiweb/mochiweb_multipart.erl index 804273cb..cac35a91 100644 --- a/src/mochiweb/mochiweb_multipart.erl +++ b/src/mochiweb/mochiweb_multipart.erl @@ -60,17 +60,17 @@ parse_multipart_request(Req, Callback) -> %% TODO: Support chunked? Length = list_to_integer(Req:get_header_value("content-length")), Boundary = iolist_to_binary( - get_boundary(Req:get_header_value("content-type"))), + get_boundary(Req:get_header_value("content-type"))), Prefix = <<"\r\n--", Boundary/binary>>, BS = size(Boundary), Chunk = read_chunk(Req, Length), Length1 = Length - size(Chunk), <<"--", Boundary:BS/binary, "\r\n", Rest/binary>> = Chunk, feed_mp(headers, #mp{boundary=Prefix, - length=Length1, - buffer=Rest, - callback=Callback, - req=Req}). + length=Length1, + buffer=Rest, + callback=Callback, + req=Req}). parse_headers(<<>>) -> []; @@ -79,91 +79,91 @@ parse_headers(Binary) -> parse_headers(Binary, Acc) -> case find_in_binary(<<"\r\n">>, Binary) of - {exact, N} -> - <<Line:N/binary, "\r\n", Rest/binary>> = Binary, - parse_headers(Rest, [split_header(Line) | Acc]); - not_found -> - lists:reverse([split_header(Binary) | Acc]) + {exact, N} -> + <<Line:N/binary, "\r\n", Rest/binary>> = Binary, + parse_headers(Rest, [split_header(Line) | Acc]); + not_found -> + lists:reverse([split_header(Binary) | Acc]) end. split_header(Line) -> {Name, [$: | Value]} = lists:splitwith(fun (C) -> C =/= $: end, - binary_to_list(Line)), + binary_to_list(Line)), {string:to_lower(string:strip(Name)), mochiweb_util:parse_header(Value)}. read_chunk(Req, Length) when Length > 0 -> case Length of - Length when Length < ?CHUNKSIZE -> - Req:recv(Length); - _ -> - Req:recv(?CHUNKSIZE) + Length when Length < ?CHUNKSIZE -> + Req:recv(Length); + _ -> + Req:recv(?CHUNKSIZE) end. 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}. + buffer=Buffer1}. feed_mp(headers, State=#mp{buffer=Buffer, callback=Callback}) -> {State1, P} = case find_in_binary(<<"\r\n\r\n">>, Buffer) of - {exact, N} -> - {State, N}; - _ -> - S1 = read_more(State), - %% Assume headers must be less than ?CHUNKSIZE - {exact, N} = find_in_binary(<<"\r\n\r\n">>, - S1#mp.buffer), - {S1, N} - end, + {exact, N} -> + {State, N}; + _ -> + S1 = read_more(State), + %% Assume headers must be less than ?CHUNKSIZE + {exact, N} = find_in_binary(<<"\r\n\r\n">>, + S1#mp.buffer), + {S1, N} + end, <<Headers:P/binary, "\r\n\r\n", Rest/binary>> = State1#mp.buffer, NextCallback = Callback({headers, parse_headers(Headers)}), feed_mp(body, State1#mp{buffer=Rest, - callback=NextCallback}); + callback=NextCallback}); feed_mp(body, State=#mp{boundary=Prefix, buffer=Buffer, callback=Callback}) -> case find_boundary(Prefix, Buffer) of - {end_boundary, Start, Skip} -> - <<Data:Start/binary, _:Skip/binary, Rest/binary>> = Buffer, - C1 = Callback({body, Data}), - C2 = C1(body_end), - {State#mp.length, Rest, C2(eof)}; - {next_boundary, Start, Skip} -> - <<Data:Start/binary, _:Skip/binary, Rest/binary>> = Buffer, - C1 = Callback({body, Data}), - feed_mp(headers, State#mp{callback=C1(body_end), - buffer=Rest}); - {maybe, Start} -> - <<Data:Start/binary, Rest/binary>> = Buffer, - feed_mp(body, read_more(State#mp{callback=Callback({body, Data}), - buffer=Rest})); - not_found -> - {Data, Rest} = {Buffer, <<>>}, - feed_mp(body, read_more(State#mp{callback=Callback({body, Data}), - buffer=Rest})) + {end_boundary, Start, Skip} -> + <<Data:Start/binary, _:Skip/binary, Rest/binary>> = Buffer, + C1 = Callback({body, Data}), + C2 = C1(body_end), + {State#mp.length, Rest, C2(eof)}; + {next_boundary, Start, Skip} -> + <<Data:Start/binary, _:Skip/binary, Rest/binary>> = Buffer, + C1 = Callback({body, Data}), + feed_mp(headers, State#mp{callback=C1(body_end), + buffer=Rest}); + {maybe, Start} -> + <<Data:Start/binary, Rest/binary>> = Buffer, + feed_mp(body, read_more(State#mp{callback=Callback({body, Data}), + buffer=Rest})); + not_found -> + {Data, Rest} = {Buffer, <<>>}, + feed_mp(body, read_more(State#mp{callback=Callback({body, Data}), + buffer=Rest})) end. get_boundary(ContentType) -> {"multipart/form-data", Opts} = mochiweb_util:parse_header(ContentType), case proplists:get_value("boundary", Opts) of - S when is_list(S) -> - S + S when is_list(S) -> + S end. find_in_binary(B, Data) when size(B) > 0 -> case size(Data) - size(B) of - Last when Last < 0 -> - partial_find(B, Data, 0, size(Data)); - Last -> - find_in_binary(B, size(B), Data, 0, Last) + Last when Last < 0 -> + partial_find(B, Data, 0, size(Data)); + Last -> + find_in_binary(B, size(B), Data, 0, Last) end. find_in_binary(B, BS, D, N, Last) when N =< Last-> case D of - <<_:N/binary, B:BS/binary, _/binary>> -> - {exact, N}; - _ -> - find_in_binary(B, BS, D, 1 + N, Last) + <<_:N/binary, B:BS/binary, _/binary>> -> + {exact, N}; + _ -> + find_in_binary(B, BS, D, 1 + N, Last) end; find_in_binary(B, BS, D, N, Last) when N =:= 1 + Last -> partial_find(B, D, N, BS - 1). @@ -173,92 +173,92 @@ partial_find(_B, _D, _N, 0) -> partial_find(B, D, N, K) -> <<B1:K/binary, _/binary>> = B, case D of - <<_Skip:N/binary, B1:K/binary>> -> - {partial, N, K}; - _ -> - partial_find(B, D, 1 + N, K - 1) + <<_Skip:N/binary, B1:K/binary>> -> + {partial, N, K}; + _ -> + partial_find(B, D, 1 + N, K - 1) end. find_boundary(Prefix, Data) -> case find_in_binary(Prefix, Data) of - {exact, Skip} -> - PrefixSkip = Skip + size(Prefix), - case Data of - <<_:PrefixSkip/binary, "\r\n", _/binary>> -> - {next_boundary, Skip, size(Prefix) + 2}; - <<_:PrefixSkip/binary, "--\r\n", _/binary>> -> - {end_boundary, Skip, size(Prefix) + 4}; - _ when size(Data) < PrefixSkip + 4 -> - %% Underflow - {maybe, Skip}; - _ -> - %% False positive - not_found - end; - {partial, Skip, Length} when (Skip + Length) =:= size(Data) -> + {exact, Skip} -> + PrefixSkip = Skip + size(Prefix), + case Data of + <<_:PrefixSkip/binary, "\r\n", _/binary>> -> + {next_boundary, Skip, size(Prefix) + 2}; + <<_:PrefixSkip/binary, "--\r\n", _/binary>> -> + {end_boundary, Skip, size(Prefix) + 4}; + _ when size(Data) < PrefixSkip + 4 -> + %% Underflow + {maybe, Skip}; + _ -> + %% False positive + not_found + end; + {partial, Skip, Length} when (Skip + Length) =:= size(Data) -> %% Underflow {maybe, Skip}; _ -> - not_found + not_found end. with_socket_server(ServerFun, ClientFun) -> {ok, Server} = mochiweb_socket_server:start([{ip, "127.0.0.1"}, - {port, 0}, - {loop, ServerFun}]), + {port, 0}, + {loop, ServerFun}]), Port = mochiweb_socket_server:get(Server, port), {ok, Client} = gen_tcp:connect("127.0.0.1", Port, - [binary, {active, false}]), + [binary, {active, false}]), Res = (catch ClientFun(Client)), mochiweb_socket_server:stop(Server), Res. fake_request(Socket, ContentType, Length) -> mochiweb_request:new(Socket, - 'POST', - "/multipart", - {1,1}, - mochiweb_headers:make( - [{"content-type", ContentType}, - {"content-length", Length}])). + 'POST', + "/multipart", + {1,1}, + mochiweb_headers:make( + [{"content-type", ContentType}, + {"content-length", Length}])). test_callback(Expect, [Expect | Rest]) -> case Rest of - [] -> - ok; - _ -> - fun (Next) -> test_callback(Next, Rest) end + [] -> + ok; + _ -> + fun (Next) -> test_callback(Next, Rest) end end. test_parse3() -> ContentType = "multipart/form-data; boundary=---------------------------7386909285754635891697677882", 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">>, Expect = [{headers, - [{"content-disposition", - {"form-data", [{"name", "hidden"}]}}]}, - {body, <<"multipart message">>}, - body_end, - {headers, - [{"content-disposition", - {"form-data", [{"name", "file"}, {"filename", "test_file.txt"}]}}, - {"content-type", {"text/plain", []}}]}, - {body, <<"Woo multiline text file\n\nLa la la">>}, - body_end, - eof], + [{"content-disposition", + {"form-data", [{"name", "hidden"}]}}]}, + {body, <<"multipart message">>}, + body_end, + {headers, + [{"content-disposition", + {"form-data", [{"name", "file"}, {"filename", "test_file.txt"}]}}, + {"content-type", {"text/plain", []}}]}, + {body, <<"Woo multiline text file\n\nLa la la">>}, + body_end, + eof], TestCallback = fun (Next) -> test_callback(Next, Expect) end, ServerFun = fun (Socket) -> - case gen_tcp:send(Socket, BinContent) of - ok -> - exit(normal) - end - end, + case gen_tcp:send(Socket, BinContent) of + ok -> + exit(normal) + end + end, ClientFun = fun (Socket) -> - Req = fake_request(Socket, ContentType, - size(BinContent)), - Res = parse_multipart_request(Req, TestCallback), - {0, <<>>, ok} = Res, - ok - end, + Req = fake_request(Socket, ContentType, + size(BinContent)), + Res = parse_multipart_request(Req, TestCallback), + {0, <<>>, ok} = Res, + ok + end, ok = with_socket_server(ServerFun, ClientFun), ok. @@ -267,31 +267,31 @@ test_parse2() -> ContentType = "multipart/form-data; boundary=---------------------------6072231407570234361599764024", 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">>, Expect = [{headers, - [{"content-disposition", - {"form-data", [{"name", "hidden"}]}}]}, - {body, <<"multipart message">>}, - body_end, - {headers, - [{"content-disposition", - {"form-data", [{"name", "file"}, {"filename", ""}]}}, - {"content-type", {"application/octet-stream", []}}]}, - {body, <<>>}, - body_end, - eof], + [{"content-disposition", + {"form-data", [{"name", "hidden"}]}}]}, + {body, <<"multipart message">>}, + body_end, + {headers, + [{"content-disposition", + {"form-data", [{"name", "file"}, {"filename", ""}]}}, + {"content-type", {"application/octet-stream", []}}]}, + {body, <<>>}, + body_end, + eof], TestCallback = fun (Next) -> test_callback(Next, Expect) end, ServerFun = fun (Socket) -> - case gen_tcp:send(Socket, BinContent) of - ok -> - exit(normal) - end - end, + case gen_tcp:send(Socket, BinContent) of + ok -> + exit(normal) + end + end, ClientFun = fun (Socket) -> - Req = fake_request(Socket, ContentType, - size(BinContent)), - Res = parse_multipart_request(Req, TestCallback), - {0, <<>>, ok} = Res, - ok - end, + Req = fake_request(Socket, ContentType, + size(BinContent)), + Res = parse_multipart_request(Req, TestCallback), + {0, <<>>, ok} = Res, + ok + end, ok = with_socket_server(ServerFun, ClientFun), ok. @@ -312,35 +312,35 @@ test_parse_form() -> ContentType = "multipart/form-data; boundary=AaB03x", "AaB03x" = get_boundary(ContentType), Content = mochiweb_util:join( - ["--AaB03x", - "Content-Disposition: form-data; name=\"submit-name\"", - "", - "Larry", - "--AaB03x", - "Content-Disposition: form-data; name=\"files\";" - ++ "filename=\"file1.txt\"", - "Content-Type: text/plain", - "", - "... contents of file1.txt ...", - "--AaB03x--", - ""], "\r\n"), + ["--AaB03x", + "Content-Disposition: form-data; name=\"submit-name\"", + "", + "Larry", + "--AaB03x", + "Content-Disposition: form-data; name=\"files\";" + ++ "filename=\"file1.txt\"", + "Content-Type: text/plain", + "", + "... contents of file1.txt ...", + "--AaB03x--", + ""], "\r\n"), BinContent = iolist_to_binary(Content), ServerFun = fun (Socket) -> - case gen_tcp:send(Socket, BinContent) of - ok -> - exit(normal) - end - end, + case gen_tcp:send(Socket, BinContent) of + ok -> + exit(normal) + end + end, ClientFun = fun (Socket) -> - Req = fake_request(Socket, ContentType, - size(BinContent)), - Res = parse_form(Req, fun handler_test/2), + Req = fake_request(Socket, ContentType, + size(BinContent)), + Res = parse_form(Req, fun handler_test/2), [{"submit-name", "Larry"}, {"files", {"file1.txt", {"text/plain",[]}, <<"... contents of file1.txt ...">>} }] = Res, - ok - end, + ok + end, ok = with_socket_server(ServerFun, ClientFun), ok. @@ -348,45 +348,45 @@ test_parse() -> ContentType = "multipart/form-data; boundary=AaB03x", "AaB03x" = get_boundary(ContentType), Content = mochiweb_util:join( - ["--AaB03x", - "Content-Disposition: form-data; name=\"submit-name\"", - "", - "Larry", - "--AaB03x", - "Content-Disposition: form-data; name=\"files\";" - ++ "filename=\"file1.txt\"", - "Content-Type: text/plain", - "", - "... contents of file1.txt ...", - "--AaB03x--", - ""], "\r\n"), + ["--AaB03x", + "Content-Disposition: form-data; name=\"submit-name\"", + "", + "Larry", + "--AaB03x", + "Content-Disposition: form-data; name=\"files\";" + ++ "filename=\"file1.txt\"", + "Content-Type: text/plain", + "", + "... contents of file1.txt ...", + "--AaB03x--", + ""], "\r\n"), BinContent = iolist_to_binary(Content), Expect = [{headers, - [{"content-disposition", - {"form-data", [{"name", "submit-name"}]}}]}, - {body, <<"Larry">>}, - body_end, - {headers, - [{"content-disposition", - {"form-data", [{"name", "files"}, {"filename", "file1.txt"}]}}, - {"content-type", {"text/plain", []}}]}, - {body, <<"... contents of file1.txt ...">>}, - body_end, - eof], + [{"content-disposition", + {"form-data", [{"name", "submit-name"}]}}]}, + {body, <<"Larry">>}, + body_end, + {headers, + [{"content-disposition", + {"form-data", [{"name", "files"}, {"filename", "file1.txt"}]}}, + {"content-type", {"text/plain", []}}]}, + {body, <<"... contents of file1.txt ...">>}, + body_end, + eof], TestCallback = fun (Next) -> test_callback(Next, Expect) end, ServerFun = fun (Socket) -> - case gen_tcp:send(Socket, BinContent) of - ok -> - exit(normal) - end - end, + case gen_tcp:send(Socket, BinContent) of + ok -> + exit(normal) + end + end, ClientFun = fun (Socket) -> - Req = fake_request(Socket, ContentType, - size(BinContent)), - Res = parse_multipart_request(Req, TestCallback), - {0, <<>>, ok} = Res, - ok - end, + Req = fake_request(Socket, ContentType, + size(BinContent)), + Res = parse_multipart_request(Req, TestCallback), + {0, <<>>, ok} = Res, + ok + end, ok = with_socket_server(ServerFun, ClientFun), ok. diff --git a/src/mochiweb/mochiweb_request.erl b/src/mochiweb/mochiweb_request.erl index fd15cea9..d9ca7eb0 100644 --- a/src/mochiweb/mochiweb_request.erl +++ b/src/mochiweb/mochiweb_request.erl @@ -29,6 +29,7 @@ -define(SAVE_BODY_LENGTH, mochiweb_request_body_length). -define(SAVE_POST, mochiweb_request_post). -define(SAVE_COOKIE, mochiweb_request_cookie). +-define(SAVE_FORCE_CLOSE, mochiweb_request_force_close). %% @type iolist() = [iolist() | binary() | char()]. %% @type iodata() = binary() | iolist(). @@ -68,32 +69,32 @@ get(headers) -> Headers; get(peer) -> case inet:peername(Socket) of - {ok, {Addr={10, _, _, _}, _Port}} -> - case get_header_value("x-forwarded-for") of - undefined -> - inet_parse:ntoa(Addr); - Hosts -> - string:strip(lists:last(string:tokens(Hosts, ","))) - end; - {ok, {{127, 0, 0, 1}, _Port}} -> - case get_header_value("x-forwarded-for") of - undefined -> - "127.0.0.1"; - Hosts -> - string:strip(lists:last(string:tokens(Hosts, ","))) - end; - {ok, {Addr, _Port}} -> - inet_parse:ntoa(Addr) + {ok, {Addr={10, _, _, _}, _Port}} -> + case get_header_value("x-forwarded-for") of + undefined -> + inet_parse:ntoa(Addr); + Hosts -> + string:strip(lists:last(string:tokens(Hosts, ","))) + end; + {ok, {{127, 0, 0, 1}, _Port}} -> + case get_header_value("x-forwarded-for") of + undefined -> + "127.0.0.1"; + Hosts -> + string:strip(lists:last(string:tokens(Hosts, ","))) + end; + {ok, {Addr, _Port}} -> + inet_parse:ntoa(Addr) end; get(path) -> case erlang:get(?SAVE_PATH) of - undefined -> - {Path0, _, _} = mochiweb_util:urlsplit_path(RawPath), + undefined -> + {Path0, _, _} = mochiweb_util:urlsplit_path(RawPath), Path = mochiweb_util:unquote(Path0), - put(?SAVE_PATH, Path), - Path; - Cached -> - Cached + put(?SAVE_PATH, Path), + Path; + Cached -> + Cached end; get(body_length) -> erlang:get(?SAVE_BODY_LENGTH); @@ -110,18 +111,18 @@ get(range) -> %% for debugging/inspection purposes. dump() -> {?MODULE, [{method, Method}, - {version, Version}, - {raw_path, RawPath}, - {headers, mochiweb_headers:to_list(Headers)}]}. + {version, Version}, + {raw_path, RawPath}, + {headers, mochiweb_headers:to_list(Headers)}]}. %% @spec send(iodata()) -> ok %% @doc Send data over the socket. send(Data) -> case gen_tcp:send(Socket, Data) of - ok -> - ok; - _ -> - exit(normal) + ok -> + ok; + _ -> + exit(normal) end. %% @spec recv(integer()) -> binary() @@ -135,11 +136,11 @@ recv(Length) -> %% Timeout in msec. recv(Length, Timeout) -> case gen_tcp:recv(Socket, Length, Timeout) of - {ok, Data} -> - put(?SAVE_RECV, true), - Data; - _ -> - exit(normal) + {ok, Data} -> + put(?SAVE_RECV, true), + Data; + _ -> + exit(normal) end. %% @spec body_length() -> undefined | chunked | unknown_transfer_encoding | integer() @@ -201,7 +202,7 @@ recv_body(MaxBody) -> start_response({Code, ResponseHeaders}) -> HResponse = mochiweb_headers:make(ResponseHeaders), HResponse1 = mochiweb_headers:default_from_list(server_headers(), - HResponse), + HResponse), start_raw_response({Code, HResponse1}). %% @spec start_raw_response({integer(), headers()}) -> response() @@ -209,10 +210,10 @@ start_response({Code, ResponseHeaders}) -> %% ResponseHeaders. start_raw_response({Code, ResponseHeaders}) -> F = fun ({K, V}, Acc) -> - [make_io(K), <<": ">>, V, <<"\r\n">> | Acc] - end, + [make_io(K), <<": ">>, V, <<"\r\n">> | Acc] + end, End = lists:foldl(F, [<<"\r\n">>], - mochiweb_headers:to_list(ResponseHeaders)), + mochiweb_headers:to_list(ResponseHeaders)), send([make_version(Version), make_code(Code), <<"\r\n">> | End]), mochiweb:new_response({THIS, Code, ResponseHeaders}). @@ -245,24 +246,32 @@ respond({Code, ResponseHeaders, {file, IoDevice}}) -> respond({Code, ResponseHeaders, chunked}) -> HResponse = mochiweb_headers:make(ResponseHeaders), HResponse1 = case Method of - 'HEAD' -> - %% This is what Google does, http://www.google.com/ - %% is chunked but HEAD gets Content-Length: 0. - %% The RFC is ambiguous so emulating Google is smart. - mochiweb_headers:enter("Content-Length", "0", - HResponse); - _ -> - mochiweb_headers:enter("Transfer-Encoding", "chunked", - HResponse) - end, + 'HEAD' -> + %% This is what Google does, http://www.google.com/ + %% is chunked but HEAD gets Content-Length: 0. + %% The RFC is ambiguous so emulating Google is smart. + mochiweb_headers:enter("Content-Length", "0", + HResponse); + _ when Version >= {1, 1} -> + %% Only use chunked encoding for HTTP/1.1 + mochiweb_headers:enter("Transfer-Encoding", "chunked", + HResponse); + _ -> + %% For pre-1.1 clients we send the data as-is + %% without a Content-Length header and without + %% chunk delimiters. Since the end of the document + %% is now ambiguous we must force a close. + put(?SAVE_FORCE_CLOSE, true), + HResponse + end, start_response({Code, HResponse1}); respond({Code, ResponseHeaders, Body}) -> Response = start_response_length({Code, ResponseHeaders, iolist_size(Body)}), case Method of - 'HEAD' -> - ok; - _ -> - send(Body) + 'HEAD' -> + ok; + _ -> + send(Body) end, Response. @@ -306,40 +315,42 @@ ok({ContentType, ResponseHeaders, Body}) -> %% @doc Return true if the connection must be closed. If false, using %% Keep-Alive should be safe. should_close() -> + ForceClose = erlang:get(mochiweb_request_force_close) =/= undefined, DidNotRecv = erlang:get(mochiweb_request_recv) =:= undefined, - Version < {1, 0} - % Connection: close - orelse get_header_value("connection") =:= "close" - % HTTP 1.0 requires Connection: Keep-Alive - orelse (Version =:= {1, 0} - andalso get_header_value("connection") /= "Keep-Alive") - % unread data left on the socket, can't safely continue - orelse (DidNotRecv - andalso get_header_value("content-length") /= undefined). + ForceClose orelse Version < {1, 0} + %% Connection: close + orelse get_header_value("connection") =:= "close" + %% HTTP 1.0 requires Connection: Keep-Alive + orelse (Version =:= {1, 0} + andalso get_header_value("connection") =/= "Keep-Alive") + %% unread data left on the socket, can't safely continue + orelse (DidNotRecv + andalso get_header_value("content-length") =/= undefined). %% @spec cleanup() -> ok %% @doc Clean up any junk in the process dictionary, required before continuing %% a Keep-Alive request. cleanup() -> [erase(K) || K <- [?SAVE_QS, - ?SAVE_PATH, - ?SAVE_RECV, - ?SAVE_BODY, - ?SAVE_POST, - ?SAVE_COOKIE]], + ?SAVE_PATH, + ?SAVE_RECV, + ?SAVE_BODY, + ?SAVE_POST, + ?SAVE_COOKIE, + ?SAVE_FORCE_CLOSE]], ok. %% @spec parse_qs() -> [{Key::string(), Value::string()}] %% @doc Parse the query string of the URL. parse_qs() -> case erlang:get(?SAVE_QS) of - undefined -> - {_, QueryString, _} = mochiweb_util:urlsplit_path(RawPath), - Parsed = mochiweb_util:parse_qs(QueryString), - put(?SAVE_QS, Parsed), - Parsed; - Cached -> - Cached + undefined -> + {_, QueryString, _} = mochiweb_util:urlsplit_path(RawPath), + Parsed = mochiweb_util:parse_qs(QueryString), + put(?SAVE_QS, Parsed), + Parsed; + Cached -> + Cached end. %% @spec get_cookie_value(Key::string) -> string() | undefined @@ -351,17 +362,17 @@ get_cookie_value(Key) -> %% @doc Parse the cookie header. parse_cookie() -> case erlang:get(?SAVE_COOKIE) of - undefined -> - Cookies = case get_header_value("cookie") of - undefined -> - []; - Value -> - mochiweb_cookies:parse_cookie(Value) - end, - put(?SAVE_COOKIE, Cookies), - Cookies; - Cached -> - Cached + undefined -> + Cookies = case get_header_value("cookie") of + undefined -> + []; + Value -> + mochiweb_cookies:parse_cookie(Value) + end, + put(?SAVE_COOKIE, Cookies), + Cookies; + Cached -> + Cached end. %% @spec parse_post() -> [{Key::string(), Value::string()}] @@ -369,33 +380,33 @@ parse_cookie() -> %% has the side-effect of calling recv_body(). parse_post() -> case erlang:get(?SAVE_POST) of - undefined -> - Parsed = case recv_body() of - undefined -> - []; - Binary -> - case get_primary_header_value("content-type") of - "application/x-www-form-urlencoded" -> - mochiweb_util:parse_qs(Binary); - _ -> - [] - end - end, - put(?SAVE_POST, Parsed), - Parsed; - Cached -> - Cached + undefined -> + Parsed = case recv_body() of + undefined -> + []; + Binary -> + case get_primary_header_value("content-type") of + "application/x-www-form-urlencoded" -> + mochiweb_util:parse_qs(Binary); + _ -> + [] + end + end, + put(?SAVE_POST, Parsed), + Parsed; + Cached -> + Cached end. read_chunked_body(Max, Acc) -> case read_chunk_length() of - 0 -> - read_chunk(0), - iolist_to_binary(lists:reverse(Acc)); - Length when Length > Max -> - exit({body_too_large, chunked}); - Length -> - read_chunked_body(Max - Length, [read_chunk(Length) | Acc]) + 0 -> + read_chunk(0), + iolist_to_binary(lists:reverse(Acc)); + Length when Length > Max -> + exit({body_too_large, chunked}); + Length -> + read_chunked_body(Max - Length, [read_chunk(Length) | Acc]) end. %% @spec read_chunk_length() -> integer() @@ -421,14 +432,14 @@ read_chunk(0) -> inet:setopts(Socket, [{packet, line}]), F = fun (F1, Acc) -> case gen_tcp:recv(Socket, 0, ?IDLE_TIMEOUT) of - {ok, <<"\r\n">>} -> - Acc; - {ok, Footer} -> - F1(F1, [Footer | Acc]); + {ok, <<"\r\n">>} -> + Acc; + {ok, Footer} -> + F1(F1, [Footer | Acc]); _ -> exit(normal) - end - end, + end + end, Footers = F(F, []), inet:setopts(Socket, [{packet, raw}]), Footers; @@ -445,24 +456,35 @@ read_chunk(Length) -> serve_file(Path, DocRoot) -> FullPath = filename:join([DocRoot, Path]), File = case filelib:is_dir(FullPath) of - true -> - filename:join([FullPath, "index.html"]); - false -> - FullPath - end, + true -> + filename:join([FullPath, "index.html"]); + false -> + FullPath + end, case lists:prefix(DocRoot, File) of - true -> - case file:open(File, [raw, binary]) of - {ok, IoDevice} -> - ContentType = mochiweb_util:guess_mime(File), - Res = ok({ContentType, {file, IoDevice}}), - file:close(IoDevice), - Res; - _ -> - not_found() - end; - false -> - not_found() + true -> + 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, [], ""}); + _ -> + case file:open(File, [raw, binary]) of + {ok, IoDevice} -> + ContentType = mochiweb_util:guess_mime(File), + Res = ok({ContentType, [{"last-modified", LastModified}], {file, IoDevice}}), + file:close(IoDevice), + Res; + _ -> + not_found() + end + end; + {error, _} -> + not_found() + end; + false -> + not_found() end. diff --git a/src/mochiweb/mochiweb_response.erl b/src/mochiweb/mochiweb_response.erl index 87917c40..d69c6726 100644 --- a/src/mochiweb/mochiweb_response.erl +++ b/src/mochiweb/mochiweb_response.erl @@ -37,16 +37,21 @@ dump() -> %% @doc Send data over the socket if the method is not HEAD. send(Data) -> case Request:get(method) of - 'HEAD' -> - ok; - _ -> - Request:send(Data) + 'HEAD' -> + ok; + _ -> + Request:send(Data) end. %% @spec write_chunk(iodata()) -> ok %% @doc Write a chunk of a HTTP chunked response. If Data is zero length, %% then the chunked response will be finished. write_chunk(Data) -> - Length = iolist_size(Data), - send(io_lib:format("~.16b\r\n", [Length])), - send([Data, <<"\r\n">>]). + case Request:get(version) of + Version when Version >= {1, 1} -> + Length = iolist_size(Data), + send(io_lib:format("~.16b\r\n", [Length])), + send([Data, <<"\r\n">>]); + _ -> + send(Data) + end. diff --git a/src/mochiweb/mochiweb_socket_server.erl b/src/mochiweb/mochiweb_socket_server.erl index 764481c4..a88ace76 100644 --- a/src/mochiweb/mochiweb_socket_server.erl +++ b/src/mochiweb/mochiweb_socket_server.erl @@ -9,19 +9,19 @@ -export([start/1, stop/1]). -export([init/1, handle_call/3, handle_cast/2, terminate/2, code_change/3, - handle_info/2]). + handle_info/2]). -export([get/2]). -export([acceptor_loop/1]). -record(mochiweb_socket_server, - {port, - loop, - name=undefined, - max=2048, - ip=any, - listen=null, - acceptor=null, + {port, + loop, + name=undefined, + max=2048, + ip=any, + listen=null, + acceptor=null, backlog=30}). start(State=#mochiweb_socket_server{}) -> @@ -66,14 +66,14 @@ parse_options([{port, Port} | Rest], State) -> parse_options(Rest, State#mochiweb_socket_server{port=Port}); parse_options([{ip, Ip} | Rest], State) -> ParsedIp = case Ip of - any -> - any; - Ip when is_tuple(Ip) -> - Ip; - Ip when is_list(Ip) -> - {ok, IpTuple} = inet_parse:address(Ip), - IpTuple - end, + any -> + any; + Ip when is_tuple(Ip) -> + Ip; + Ip when is_list(Ip) -> + {ok, IpTuple} = inet_parse:address(Ip), + IpTuple + end, parse_options(Rest, State#mochiweb_socket_server{ip=ParsedIp}); parse_options([{loop, Loop} | Rest], State) -> parse_options(Rest, State#mochiweb_socket_server{loop=Loop}); @@ -81,35 +81,35 @@ parse_options([{backlog, Backlog} | Rest], State) -> parse_options(Rest, State#mochiweb_socket_server{backlog=Backlog}); parse_options([{max, Max} | Rest], State) -> MaxInt = case Max of - Max when is_list(Max) -> - list_to_integer(Max); - Max when is_integer(Max) -> - Max - end, + Max when is_list(Max) -> + list_to_integer(Max); + Max when is_integer(Max) -> + Max + end, parse_options(Rest, State#mochiweb_socket_server{max=MaxInt}). start_server(State=#mochiweb_socket_server{name=Name}) -> case Name of - undefined -> - gen_server:start_link(?MODULE, State, []); - _ -> - gen_server:start_link(Name, ?MODULE, State, []) + undefined -> + gen_server:start_link(?MODULE, State, []); + _ -> + gen_server:start_link(Name, ?MODULE, State, []) end. init(State=#mochiweb_socket_server{ip=Ip, port=Port, backlog=Backlog}) -> process_flag(trap_exit, true), BaseOpts = [binary, - {reuseaddr, true}, - {packet, 0}, - {backlog, Backlog}, - {recbuf, 8192}, - {active, false}], + {reuseaddr, true}, + {packet, 0}, + {backlog, Backlog}, + {recbuf, 8192}, + {active, false}], Opts = case Ip of - any -> - BaseOpts; - Ip -> - [{ip, Ip} | BaseOpts] - end, + any -> + BaseOpts; + Ip -> + [{ip, Ip} | BaseOpts] + end, case gen_tcp_listen(Port, Opts, State) of {stop, eacces} -> case Port < 1024 of @@ -135,11 +135,11 @@ init(State=#mochiweb_socket_server{ip=Ip, port=Port, backlog=Backlog}) -> gen_tcp_listen(Port, Opts, State) -> case gen_tcp:listen(Port, Opts) of {ok, Listen} -> - {ok, ListenPort} = inet:port(Listen), - {ok, new_acceptor(State#mochiweb_socket_server{listen=Listen, + {ok, ListenPort} = inet:port(Listen), + {ok, new_acceptor(State#mochiweb_socket_server{listen=Listen, port=ListenPort})}; - {error, Reason} -> - {stop, Reason} + {error, Reason} -> + {stop, Reason} end. new_acceptor(State=#mochiweb_socket_server{max=0}) -> @@ -147,7 +147,7 @@ new_acceptor(State=#mochiweb_socket_server{max=0}) -> State#mochiweb_socket_server{acceptor=null}; new_acceptor(State=#mochiweb_socket_server{listen=Listen,loop=Loop}) -> Pid = proc_lib:spawn_link(?MODULE, acceptor_loop, - [{self(), Listen, Loop}]), + [{self(), Listen, Loop}]), State#mochiweb_socket_server{acceptor=Pid}. call_loop({M, F}, Socket) -> @@ -157,19 +157,19 @@ call_loop(Loop, Socket) -> acceptor_loop({Server, Listen, Loop}) -> case catch gen_tcp:accept(Listen) of - {ok, Socket} -> - gen_server:cast(Server, {accepted, self()}), - call_loop(Loop, Socket); - {error, closed} -> - exit({error, closed}); - Other -> - error_logger:error_report( - [{application, mochiweb}, - "Accept failed error", - lists:flatten(io_lib:format("~p", [Other]))]), - exit({error, accept_failed}) + {ok, Socket} -> + gen_server:cast(Server, {accepted, self()}), + call_loop(Loop, Socket); + {error, closed} -> + exit({error, closed}); + Other -> + error_logger:error_report( + [{application, mochiweb}, + "Accept failed error", + lists:flatten(io_lib:format("~p", [Other]))]), + exit({error, accept_failed}) end. - + do_get(port, #mochiweb_socket_server{port=Port}) -> Port. @@ -182,7 +182,7 @@ handle_call(_Message, _From, State) -> {reply, Res, State}. handle_cast({accepted, Pid}, - State=#mochiweb_socket_server{acceptor=Pid, max=Max}) -> + State=#mochiweb_socket_server{acceptor=Pid, max=Max}) -> % io:format("accepted ~p~n", [Pid]), State1 = State#mochiweb_socket_server{max=Max - 1}, {noreply, new_acceptor(State1)}; @@ -203,31 +203,31 @@ code_change(_OldVsn, State, _Extra) -> State. handle_info({'EXIT', Pid, normal}, - State=#mochiweb_socket_server{acceptor=Pid}) -> + State=#mochiweb_socket_server{acceptor=Pid}) -> % io:format("normal acceptor down~n"), {noreply, new_acceptor(State)}; handle_info({'EXIT', Pid, Reason}, - State=#mochiweb_socket_server{acceptor=Pid}) -> + State=#mochiweb_socket_server{acceptor=Pid}) -> error_logger:error_report({?MODULE, ?LINE, - {acceptor_error, Reason}}), + {acceptor_error, Reason}}), timer:sleep(100), {noreply, new_acceptor(State)}; handle_info({'EXIT', _LoopPid, Reason}, - State=#mochiweb_socket_server{acceptor=Pid, max=Max}) -> + State=#mochiweb_socket_server{acceptor=Pid, max=Max}) -> case Reason of - normal -> - ok; - _ -> - error_logger:error_report({?MODULE, ?LINE, - {child_error, Reason}}) + normal -> + ok; + _ -> + error_logger:error_report({?MODULE, ?LINE, + {child_error, Reason}}) end, State1 = State#mochiweb_socket_server{max=Max + 1}, State2 = case Pid of - null -> - new_acceptor(State1); - _ -> - State1 - end, + null -> + new_acceptor(State1); + _ -> + State1 + end, {noreply, State2}; handle_info(Info, State) -> error_logger:info_report([{'INFO', Info}, {'State', State}]), diff --git a/src/mochiweb/mochiweb_util.erl b/src/mochiweb/mochiweb_util.erl index a2b6b2fb..7b66877d 100644 --- a/src/mochiweb/mochiweb_util.erl +++ b/src/mochiweb/mochiweb_util.erl @@ -16,13 +16,13 @@ -define(PERCENT, 37). % $\% -define(FULLSTOP, 46). % $\. -define(IS_HEX(C), ((C >= $0 andalso C =< $9) orelse - (C >= $a andalso C =< $f) orelse - (C >= $A andalso C =< $F))). + (C >= $a andalso C =< $f) orelse + (C >= $A andalso C =< $F))). -define(QS_SAFE(C), ((C >= $a andalso C =< $z) orelse - (C >= $A andalso C =< $Z) orelse - (C >= $0 andalso C =< $9) orelse - (C =:= ?FULLSTOP orelse C =:= $- orelse C =:= $~ orelse - C =:= $_))). + (C >= $A andalso C =< $Z) orelse + (C >= $0 andalso C =< $9) orelse + (C =:= ?FULLSTOP orelse C =:= $- orelse C =:= $~ orelse + C =:= $_))). hexdigit(C) when C < 10 -> $0 + C; hexdigit(C) when C < 16 -> $A + (C - 10). @@ -92,8 +92,8 @@ quote_plus([C | Rest], Acc) -> %% @doc URL encode the property list. urlencode(Props) -> RevPairs = lists:foldl(fun ({K, V}, Acc) -> - [[quote_plus(K), $=, quote_plus(V)] | Acc] - end, [], Props), + [[quote_plus(K), $=, quote_plus(V)] | Acc] + end, [], Props), lists:flatten(revjoin(RevPairs, $&, [])). %% @spec parse_qs(string() | binary()) -> [{Key, Value}] @@ -204,15 +204,15 @@ path_split([C | Rest], Acc) -> %% @doc Assemble a URL from the 5-tuple. Path must be absolute. urlunsplit({Scheme, Netloc, Path, Query, Fragment}) -> lists:flatten([case Scheme of "" -> ""; _ -> [Scheme, "://"] end, - Netloc, - urlunsplit_path({Path, Query, Fragment})]). + Netloc, + urlunsplit_path({Path, Query, Fragment})]). %% @spec urlunsplit_path({Path, Query, Fragment}) -> string() %% @doc Assemble a URL path from the 3-tuple. urlunsplit_path({Path, Query, Fragment}) -> lists:flatten([Path, - case Query of "" -> ""; _ -> [$? | Query] end, - case Fragment of "" -> ""; _ -> [$# | Fragment] end]). + case Query of "" -> ""; _ -> [$? | Query] end, + case Fragment of "" -> ""; _ -> [$# | Fragment] end]). %% @spec urlsplit_path(Url) -> {Path, Query, Fragment} %% @doc Return a 3-tuple, does not expand % escapes. Only supports HTTP style @@ -244,36 +244,36 @@ urlsplit_query([C | Rest], Acc) -> %% @doc Guess the mime type of a file by the extension of its filename. guess_mime(File) -> case filename:extension(File) of - ".html" -> - "text/html"; - ".xhtml" -> - "application/xhtml+xml"; - ".xml" -> - "application/xml"; - ".css" -> - "text/css"; - ".js" -> - "application/x-javascript"; - ".jpg" -> - "image/jpeg"; - ".gif" -> - "image/gif"; - ".png" -> - "image/png"; - ".swf" -> - "application/x-shockwave-flash"; - ".zip" -> - "application/zip"; - ".bz2" -> - "application/x-bzip2"; - ".gz" -> - "application/x-gzip"; - ".tar" -> - "application/x-tar"; - ".tgz" -> - "application/x-gzip"; - ".txt" -> - "text/plain"; + ".html" -> + "text/html"; + ".xhtml" -> + "application/xhtml+xml"; + ".xml" -> + "application/xml"; + ".css" -> + "text/css"; + ".js" -> + "application/x-javascript"; + ".jpg" -> + "image/jpeg"; + ".gif" -> + "image/gif"; + ".png" -> + "image/png"; + ".swf" -> + "application/x-shockwave-flash"; + ".zip" -> + "application/zip"; + ".bz2" -> + "application/x-bzip2"; + ".gz" -> + "application/x-gzip"; + ".tar" -> + "application/x-tar"; + ".tgz" -> + "application/x-gzip"; + ".txt" -> + "text/plain"; ".doc" -> "application/msword"; ".pdf" -> @@ -314,18 +314,18 @@ parse_header(String) -> %% Should parse properly like mochiweb_cookies. [Type | Parts] = [string:strip(S) || S <- string:tokens(String, ";")], F = fun (S, Acc) -> - case lists:splitwith(fun (C) -> C =/= $= end, S) of - {"", _} -> - %% Skip anything with no name - Acc; - {_, ""} -> - %% Skip anything with no value - Acc; - {Name, [$\= | Value]} -> - [{string:to_lower(string:strip(Name)), - unquote_header(string:strip(Value))} | Acc] - end - end, + case lists:splitwith(fun (C) -> C =/= $= end, S) of + {"", _} -> + %% Skip anything with no name + Acc; + {_, ""} -> + %% Skip anything with no value + Acc; + {Name, [$\= | Value]} -> + [{string:to_lower(string:strip(Name)), + unquote_header(string:strip(Value))} | Acc] + end + end, {string:to_lower(Type), lists:foldr(F, [], Parts)}. @@ -401,7 +401,7 @@ test_cmd_string() -> test_parse_header() -> {"multipart/form-data", [{"boundary", "AaB03x"}]} = - parse_header("multipart/form-data; boundary=AaB03x"), + parse_header("multipart/form-data; boundary=AaB03x"), ok. test_guess_mime() -> @@ -422,7 +422,7 @@ test_path_split() -> test_urlsplit() -> {"", "", "/foo", "", "bar?baz"} = urlsplit("/foo#bar?baz"), {"http", "host:port", "/foo", "", "bar?baz"} = - urlsplit("http://host:port/foo#bar?baz"), + urlsplit("http://host:port/foo#bar?baz"), ok. test_urlsplit_path() -> @@ -437,7 +437,7 @@ test_urlsplit_path() -> test_urlunsplit() -> "/foo#bar?baz" = urlunsplit({"", "", "/foo", "", "bar?baz"}), "http://host:port/foo#bar?baz" = - urlunsplit({"http", "host:port", "/foo", "", "bar?baz"}), + urlunsplit({"http", "host:port", "/foo", "", "bar?baz"}), ok. test_urlunsplit_path() -> @@ -476,11 +476,11 @@ test_unquote() -> test_urlencode() -> "foo=bar&baz=wibble+%0D%0A&z=1" = urlencode([{foo, "bar"}, - {"baz", "wibble \r\n"}, - {z, 1}]), + {"baz", "wibble \r\n"}, + {z, 1}]), ok. test_parse_qs() -> [{"foo", "bar"}, {"baz", "wibble \r\n"}, {"z", "1"}] = - parse_qs("foo=bar&baz=wibble+%0D%0A&z=1"), + parse_qs("foo=bar&baz=wibble+%0D%0A&z=1"), ok. diff --git a/src/mochiweb/reloader.erl b/src/mochiweb/reloader.erl index fcb27c1c..2ff154ba 100644 --- a/src/mochiweb/reloader.erl +++ b/src/mochiweb/reloader.erl @@ -80,16 +80,7 @@ doit(From, To) -> [case file:read_file_info(Filename) of {ok, FileInfo} when FileInfo#file_info.mtime >= From, FileInfo#file_info.mtime < To -> - io:format("Reloading ~p ...", [Module]), - code:purge(Module), - case code:load_file(Module) of - {module, Module} -> - io:format(" ok.~n"), - reload; - {error, Reason} -> - io:format(" ~p.~n", [Reason]), - error - end; + reload(Module); {ok, _} -> unmodified; {error, enoent} -> @@ -103,5 +94,31 @@ doit(From, To) -> error end || {Module, Filename} <- code:all_loaded(), is_list(Filename)]. +reload(Module) -> + io:format("Reloading ~p ...", [Module]), + code:purge(Module), + case code:load_file(Module) of + {module, Module} -> + io:format(" ok.~n"), + case erlang:function_exported(Module, test, 0) of + true -> + io:format(" - Calling ~p:test() ...", [Module]), + case catch Module:test() of + ok -> + io:format(" ok.~n"), + reload; + Reason -> + io:format(" fail: ~p.~n", [Reason]), + reload_but_test_failed + end; + false -> + reload + end; + {error, Reason} -> + io:format(" fail: ~p.~n", [Reason]), + error + end. + + stamp() -> erlang:localtime(). |