summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristopher Lenz <cmlenz@apache.org>2008-04-30 00:45:50 +0000
committerChristopher Lenz <cmlenz@apache.org>2008-04-30 00:45:50 +0000
commit2f40a05f2631b9c92758720c2e9703cb0c9198d7 (patch)
tree1a3bee7cc5e62f1f8f88e4727b3d92f70dd0787b
parent31168059f61685443a30450dc0a0623e11f1bbc6 (diff)
Update MochiWeb code in trunk to r66.
git-svn-id: https://svn.apache.org/repos/asf/incubator/couchdb/trunk@652206 13f79535-47bb-0310-9956-ffa450edef68
-rw-r--r--src/mochiweb/mochijson.erl136
-rw-r--r--src/mochiweb/mochijson2.erl150
-rw-r--r--src/mochiweb/mochiweb.erl30
-rw-r--r--src/mochiweb/mochiweb_cookies.erl66
-rw-r--r--src/mochiweb/mochiweb_echo.erl26
-rw-r--r--src/mochiweb/mochiweb_headers.erl38
-rw-r--r--src/mochiweb/mochiweb_html.erl4
-rw-r--r--src/mochiweb/mochiweb_http.erl92
-rw-r--r--src/mochiweb/mochiweb_multipart.erl374
-rw-r--r--src/mochiweb/mochiweb_request.erl282
-rw-r--r--src/mochiweb/mochiweb_response.erl19
-rw-r--r--src/mochiweb/mochiweb_socket_server.erl134
-rw-r--r--src/mochiweb/mochiweb_util.erl120
-rw-r--r--src/mochiweb/reloader.erl37
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().