Merge branch 'upstream-2.17.0'
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..d03550e
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,17 @@
+# EditorConfig file: http://EditorConfig.org
+
+# top-most EditorConfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+end_of_line = lf
+insert_final_newline = true
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+# 4 space indentation
+[*.{erl,src,hrl}]
+indent_style = space
+indent_size = 4
diff --git a/.travis.yml b/.travis.yml
index d9c6fd8..60783fd 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,8 +1,11 @@
+sudo: false
language: erlang
notifications:
email: false
otp_release:
- - 17.1
- - 17.0
+ - 20.0
+ - 19.0
+ - 18.3
+ - 17.5
- R16B03-1
- R15B03
diff --git a/CHANGES.md b/CHANGES.md
index 24a59d6..6653723 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -1,3 +1,73 @@
+Version 2.17.0 released 2017-08-12
+
+* Fix deprecation warnings for Erlang/OTP 20.0
+ https://github.com/mochi/mochiweb/pull/186
+* Updated mochiweb_html singleton tag heuristic for HTML5
+ https://github.com/mochi/mochiweb/pull/190
+* Send 400 Bad Request if request line exceeds recbuf (regression fix)
+ https://github.com/mochi/mochiweb/pull/191
+
+Version 2.16.0 released 2016-12-19
+
+* Added support for encoding maps to mochijson2 (where available)
+ https://github.com/mochi/mochiweb/pull/184
+* Added missing RFC1918 address spaces to the allowed x-forwarded-for header
+ https://github.com/mochi/mochiweb/pull/183
+
+Version 2.15.1 released 2016-06-24
+
+* Fixed deprecation warnings in Erlang/OTP 19.0
+ https://github.com/mochi/mochiweb/pull/177
+
+Version 2.15.0 released 2016-05-08
+
+* mochiweb_request now normalizes paths such that duplicate slashes are
+ discarded (and thus all path segments except the last are non-empty).
+ https://github.com/mochi/mochiweb/pull/173
+
+Version 2.14.0 released 2016-04-11
+
+* mochiweb_html now requires a letter to begin a HTML tag
+ https://github.com/mochi/mochiweb/pull/171
+
+Version 2.13.2 released 2016-03-18
+
+* Allow mochijson2 to handle code points that xmerl_ucs considered
+ invalid
+ https://github.com/mochi/mochiweb/issues/168
+
+Version 2.13.1 released 2016-03-13
+
+* Fix mochiweb_html regression parsing invalid charref sequences
+ https://github.com/mochi/mochiweb/issues/167
+
+Version 2.13.0 released 2016-02-08
+
+* Support parsing of UTF-16 surrogate pairs encoded as character
+ references in mochiweb_html
+ https://github.com/mochi/mochiweb/issues/164
+* Avoid swallowing messages that are not related to the socket
+ during request parsing
+ https://github.com/mochi/mochiweb/pull/161
+* Ensure correct ordering of Set-Cookie headers: first in, first out
+ https://github.com/mochi/mochiweb/issues/162
+* Improve response times by caching a formatted date once per second
+ for the response headers with a mochiweb_clock service
+ https://github.com/mochi/mochiweb/pull/158
+
+Version 2.12.2 released 2015-02-21
+
+* Close connections quietly when setopts fails with a closed socket.
+ https://github.com/mochi/mochiweb/pull/152
+
+Version 2.12.1 released 2015-02-01
+
+* Fix active_socket accounting
+ https://github.com/mochi/mochiweb/issues/149
+* Added full MIT license preludes to each source file to make it
+ easier for mochiweb's code to be used piecemeal
+ https://github.com/mochi/mochiweb/pull/148
+
Version 2.12.0 released 2015-01-16
* Send "Connection: close" header when the server is going to close
@@ -87,7 +157,7 @@
call instead of an asynchronous cast
* `mochiweb_html:parse_tokens/1` (and `parse/1`) will now create a
html element to wrap documents that have a HTML5 doctype
- (`<!doctype html>`) but no html element
+ (`<!doctype html>`) but no html element
https://github.com/mochi/mochiweb/issues/110
Version 2.6.0 released 2013-04-15
@@ -106,7 +176,7 @@
(URL and Filename safe alphabet, see RFC 4648).
* Fix rebar.config in mochiwebapp_skel to use {branch, "master"}
https://github.com/mochi/mochiweb/issues/105
-
+
Version 2.4.2 released 2013-02-05
* Fixed issue in mochiweb_response introduced in v2.4.0
diff --git a/Makefile b/Makefile
index 33601c7..983c304 100644
--- a/Makefile
+++ b/Makefile
@@ -20,4 +20,5 @@
@$(REBAR) clean
app:
+ @[ -z "$(PROJECT)" ] && echo "ERROR: required variable PROJECT missing" 1>&2 && exit 1 || true
@$(REBAR) -r create template=mochiwebapp dest=$(DEST) appid=$(PROJECT)
diff --git a/rebar.config b/rebar.config
index 0ae370c..adc1d78 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,7 +1,10 @@
% -*- mode: erlang -*-
{erl_opts, [debug_info,
- {platform_define, "R15", 'gen_tcp_r15b_workaround'},
- {platform_define, "(R14|R15|R16B-)", 'crypto_compatibility'}]}.
+ {platform_define, "^R15", 'gen_tcp_r15b_workaround'},
+ {platform_define, "^(R14|R15|R16B-)", 'crypto_compatibility'},
+ {platform_define, "^(R14|R15|R16B|17)", 'rand_mod_unavailable'},
+ {platform_define, "^(R14|R15|R16B|17)", 'sni_unavailable'},
+ {platform_define, "^(R14|R15|R16)", 'map_unavailable'}]}.
{cover_enabled, true}.
{eunit_opts, [verbose, {report,{eunit_surefire,[{dir,"."}]}}]}.
{dialyzer_opts, [{warnings, [no_return,
diff --git a/src/mochifmt.erl b/src/mochifmt.erl
index fc95e4f..6381bb7 100644
--- a/src/mochifmt.erl
+++ b/src/mochifmt.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2008 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc String Formatting for Erlang, inspired by Python 2.6
%% (<a href="http://www.python.org/dev/peps/pep-3101/">PEP 3101</a>).
diff --git a/src/mochifmt_records.erl b/src/mochifmt_records.erl
index 7d166ff..3dccaa4 100644
--- a/src/mochifmt_records.erl
+++ b/src/mochifmt_records.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2008 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Formatter that understands records.
%%
diff --git a/src/mochifmt_std.erl b/src/mochifmt_std.erl
index ea68c4a..6067451 100644
--- a/src/mochifmt_std.erl
+++ b/src/mochifmt_std.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2008 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Template module for a mochifmt formatter.
diff --git a/src/mochiglobal.erl b/src/mochiglobal.erl
index ea645b0..8df007f 100644
--- a/src/mochiglobal.erl
+++ b/src/mochiglobal.erl
@@ -1,5 +1,25 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2010 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
+
+
%% @doc Abuse module constant pools as a "read-only shared heap" (since erts 5.6)
%% <a href="http://www.erlang.org/pipermail/erlang-questions/2009-March/042503.html">[1]</a>.
-module(mochiglobal).
diff --git a/src/mochihex.erl b/src/mochihex.erl
index 796f3ad..91b2789 100644
--- a/src/mochihex.erl
+++ b/src/mochihex.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2006 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Utilities for working with hexadecimal strings.
diff --git a/src/mochijson.erl b/src/mochijson.erl
index d283189..fb9b1dc 100644
--- a/src/mochijson.erl
+++ b/src/mochijson.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2006 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Yet another JSON (RFC 4627) library for Erlang.
-module(mochijson).
diff --git a/src/mochijson2.erl b/src/mochijson2.erl
index 2b8d16e..e39c522 100644
--- a/src/mochijson2.erl
+++ b/src/mochijson2.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2007 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Yet another JSON (RFC 4627) library for Erlang. mochijson2 works
%% with binaries as strings, arrays as lists (without an {array, _})
@@ -64,6 +82,12 @@
-define(IS_WHITESPACE(C),
(C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)).
+-ifdef(map_unavailable).
+-define(IS_MAP(_), false).
+-else.
+-define(IS_MAP(X), is_map(X)).
+-endif.
+
%% @type json_string() = atom | binary()
%% @type json_number() = integer() | float()
%% @type json_array() = [json_term()]
@@ -131,6 +155,7 @@
when Format =:= struct orelse Format =:= eep18 orelse Format =:= proplist ->
parse_decoder_options(Rest, State#decoder{object_hook=Format}).
+
json_encode(true, _State) ->
<<"true">>;
json_encode(false, _State) ->
@@ -157,6 +182,8 @@
json_encode_array(Array, State);
json_encode({array, Array}, State) when is_list(Array) ->
json_encode_array(Array, State);
+json_encode(M, State) when ?IS_MAP(M) ->
+ json_encode_map(M, State);
json_encode({json, IoList}, _State) ->
IoList;
json_encode(Bad, #encoder{handler=null}) ->
@@ -184,20 +211,31 @@
[$, | Acc1] = lists:foldl(F, "{", Props),
lists:reverse([$\} | Acc1]).
+-ifdef(map_unavailable).
+json_encode_map(Bad, _State) ->
+ %% IS_MAP definition guarantees that this branch is dead
+ exit({json_encode, {bad_term, Bad}}).
+-else.
+json_encode_map(Map, _State) when map_size(Map) =:= 0 ->
+ <<"{}">>;
+json_encode_map(Map, State) ->
+ F = fun(K, V, Acc) ->
+ KS = json_encode_string(K, State),
+ VS = json_encode(V, State),
+ [$,, VS, $:, KS | Acc]
+ end,
+ [$, | Acc1] = maps:fold(F, "{", Map),
+ lists:reverse([$\} | Acc1]).
+-endif.
+
json_encode_string(A, State) when is_atom(A) ->
- L = atom_to_list(A),
- case json_string_is_safe(L) of
- true ->
- [?Q, L, ?Q];
- false ->
- json_encode_string_unicode(xmerl_ucs:from_utf8(L), State, [?Q])
- end;
+ json_encode_string(atom_to_binary(A, latin1), State);
json_encode_string(B, State) when is_binary(B) ->
case json_bin_is_safe(B) of
true ->
[?Q, B, ?Q];
false ->
- json_encode_string_unicode(xmerl_ucs:from_utf8(B), State, [?Q])
+ json_encode_string_unicode(unicode:characters_to_list(B), State, [?Q])
end;
json_encode_string(I, _State) when is_integer(I) ->
[?Q, integer_to_list(I), ?Q];
@@ -232,7 +270,7 @@
C when C < 16#7f ->
json_string_is_safe(Rest);
_ ->
- false
+ exit({json_encode, {bad_char, C}})
end.
json_bin_is_safe(<<>>) ->
@@ -290,12 +328,13 @@
C when C >= 0, C < $\s ->
[unihex(C) | Acc];
C when C >= 16#7f, C =< 16#10FFFF, State#encoder.utf8 ->
- [xmerl_ucs:to_utf8(C) | Acc];
+ [unicode:characters_to_binary([C]) | Acc];
C when C >= 16#7f, C =< 16#10FFFF, not State#encoder.utf8 ->
[unihex(C) | Acc];
C when C < 16#7f ->
[C | Acc];
_ ->
+ %% json_string_is_safe guarantees that this branch is dead
exit({json_encode, {bad_char, C}})
end,
json_encode_string_unicode(Cs, State, Acc1).
@@ -450,12 +489,14 @@
%% coalesce UTF-16 surrogate pair
<<"\\u", D3, D2, D1, D0, _/binary>> = Rest,
D = erlang:list_to_integer([D3,D2,D1,D0], 16),
- [CodePoint] = xmerl_ucs:from_utf16be(<<C:16/big-unsigned-integer,
- D:16/big-unsigned-integer>>),
- Acc1 = lists:reverse(xmerl_ucs:to_utf8(CodePoint), Acc),
+ Acc1 = [unicode:characters_to_binary(
+ <<C:16/big-unsigned-integer,
+ D:16/big-unsigned-integer>>,
+ utf16)
+ | Acc],
tokenize_string(B, ?ADV_COL(S, 12), Acc1);
true ->
- Acc1 = lists:reverse(xmerl_ucs:to_utf8(C), Acc),
+ Acc1 = [unicode:characters_to_binary([C]) | Acc],
tokenize_string(B, ?ADV_COL(S, 6), Acc1)
end;
<<_:O/binary, C1, _/binary>> when C1 < 128 ->
@@ -691,13 +732,13 @@
%% test utf8 encoding
encoder_utf8_test() ->
%% safe conversion case (default)
- [34,"\\u0001","\\u0442","\\u0435","\\u0441","\\u0442",34] =
- encode(<<1,"\321\202\320\265\321\201\321\202">>),
+ <<"\"\\u0001\\u0442\\u0435\\u0441\\u0442\"">> =
+ iolist_to_binary(encode(<<1,"\321\202\320\265\321\201\321\202">>)),
%% raw utf8 output (optional)
Enc = mochijson2:encoder([{utf8, true}]),
- [34,"\\u0001",[209,130],[208,181],[209,129],[209,130],34] =
- Enc(<<1,"\321\202\320\265\321\201\321\202">>).
+ <<34,"\\u0001",209,130,208,181,209,129,209,130,34>> =
+ iolist_to_binary(Enc(<<1,"\321\202\320\265\321\201\321\202">>)).
input_validation_test() ->
Good = [
@@ -706,7 +747,7 @@
{16#10196, <<?Q, 16#F0, 16#90, 16#86, 16#96, ?Q>>} %% denarius
],
lists:foreach(fun({CodePoint, UTF8}) ->
- Expect = list_to_binary(xmerl_ucs:to_utf8(CodePoint)),
+ Expect = unicode:characters_to_binary([CodePoint]),
Expect = decode(UTF8)
end, Good),
@@ -741,7 +782,7 @@
ok.
big_unicode_test() ->
- UTF8Seq = list_to_binary(xmerl_ucs:to_utf8(16#0001d120)),
+ UTF8Seq = unicode:characters_to_binary([16#0001d120]),
?assertEqual(
<<"\"\\ud834\\udd20\"">>,
iolist_to_binary(encode(UTF8Seq))),
@@ -773,7 +814,10 @@
iolist_to_binary(encode(foo))),
?assertEqual(
<<"\"\\ud834\\udd20\"">>,
- iolist_to_binary(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))),
+ iolist_to_binary(
+ encode(
+ binary_to_atom(
+ unicode:characters_to_binary([16#0001d120]), latin1)))),
ok.
key_encode_test() ->
@@ -818,18 +862,21 @@
json_string_is_safe([16#0001d120])),
?assertEqual(
false,
- json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8(16#0001d120)))),
+ json_bin_is_safe(unicode:characters_to_binary([16#0001d120]))),
?assertEqual(
[16#0001d120],
- xmerl_ucs:from_utf8(
- binary_to_list(
- decode(encode(list_to_atom(xmerl_ucs:to_utf8(16#0001d120))))))),
+ unicode:characters_to_list(
+ decode(
+ encode(
+ binary_to_atom(
+ unicode:characters_to_binary([16#0001d120]),
+ latin1))))),
?assertEqual(
false,
- json_string_is_safe([16#110000])),
+ json_string_is_safe([16#10ffff])),
?assertEqual(
false,
- json_bin_is_safe(list_to_binary(xmerl_ucs:to_utf8([16#110000])))),
+ json_bin_is_safe(unicode:characters_to_binary([16#10ffff]))),
%% solidus can be escaped but isn't unsafe by default
?assertEqual(
<<"/">>,
@@ -886,4 +933,47 @@
{eep18, {P}},
{proplist, P}]].
+array_test() ->
+ A = [<<"hello">>],
+ ?assertEqual(A, decode(encode({array, A}))).
+
+bad_char_test() ->
+ ?assertEqual(
+ {'EXIT', {json_encode, {bad_char, 16#110000}}},
+ catch json_string_is_safe([16#110000])).
+
+utf8_roundtrip_test_() ->
+ %% These are the boundary cases for UTF8 encoding
+ Codepoints = [%% 7 bits -> 1 byte
+ 16#00, 16#7f,
+ %% 11 bits -> 2 bytes
+ 16#080, 16#07ff,
+ %% 16 bits -> 3 bytes
+ 16#0800, 16#ffff,
+ 16#d7ff, 16#e000,
+ %% 21 bits -> 4 bytes
+ 16#010000, 16#10ffff],
+ UTF8 = unicode:characters_to_binary(Codepoints),
+ Encode = encoder([{utf8, true}]),
+ [{"roundtrip escaped",
+ ?_assertEqual(UTF8, decode(encode(UTF8)))},
+ {"roundtrip utf8",
+ ?_assertEqual(UTF8, decode(Encode(UTF8)))}].
+
+utf8_non_character_test_() ->
+ S = unicode:characters_to_binary([16#ffff, 16#fffe]),
+ [{"roundtrip escaped", ?_assertEqual(S, decode(encode(S)))},
+ {"roundtrip utf8", ?_assertEqual(S, decode((encoder([{utf8, true}]))(S)))}].
+
+-ifndef(map_unavailable).
+
+encode_map_test() ->
+ M = <<"{\"a\":1,\"b\":{\"c\":2}}">>,
+ ?assertEqual(M, iolist_to_binary(encode(#{a => 1, b => #{ c => 2}}))).
+
+encode_empty_map_test() ->
+ ?assertEqual(<<"{}">>, encode(#{})).
+
+-endif.
+
-endif.
diff --git a/src/mochilists.erl b/src/mochilists.erl
index d93b241..24fa2f3 100644
--- a/src/mochilists.erl
+++ b/src/mochilists.erl
@@ -1,5 +1,23 @@
%% @copyright Copyright (c) 2010 Mochi Media, Inc.
%% @author David Reid <dreid@mochimedia.com>
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Utility functions for dealing with proplists.
diff --git a/src/mochilogfile2.erl b/src/mochilogfile2.erl
index b4a7e3c..6ff8fec 100644
--- a/src/mochilogfile2.erl
+++ b/src/mochilogfile2.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2010 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Write newline delimited log files, ensuring that if a truncated
%% entry is found on log open then it is fixed before writing. Uses
diff --git a/src/mochinum.erl b/src/mochinum.erl
index c52b15c..d687370 100644
--- a/src/mochinum.erl
+++ b/src/mochinum.erl
@@ -1,5 +1,23 @@
%% @copyright 2007 Mochi Media, Inc.
%% @author Bob Ippolito <bob@mochimedia.com>
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Useful numeric algorithms for floats that cover some deficiencies
%% in the math module. More interesting is digits/1, which implements
diff --git a/src/mochitemp.erl b/src/mochitemp.erl
index dda7863..fe99cad 100644
--- a/src/mochitemp.erl
+++ b/src/mochitemp.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2010 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Create temporary files and directories. Requires crypto to be started.
@@ -87,7 +105,7 @@
[rngchar() | rngchars(N - 1)].
rngchar() ->
- rngchar(crypto:rand_uniform(0, tuple_size(?SAFE_CHARS))).
+ rngchar(mochiweb_util:rand_uniform(0, tuple_size(?SAFE_CHARS))).
rngchar(C) ->
element(1 + C, ?SAFE_CHARS).
diff --git a/src/mochiutf8.erl b/src/mochiutf8.erl
index 28f28c1..bf0e7cc 100644
--- a/src/mochiutf8.erl
+++ b/src/mochiutf8.erl
@@ -1,5 +1,23 @@
%% @copyright 2010 Mochi Media, Inc.
%% @author Bob Ippolito <bob@mochimedia.com>
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Algorithm to convert any binary to a valid UTF-8 sequence by ignoring
%% invalid bytes.
diff --git a/src/mochiweb.app.src b/src/mochiweb.app.src
index 8cb43ac..6bcdd5a 100644
--- a/src/mochiweb.app.src
+++ b/src/mochiweb.app.src
@@ -1,9 +1,15 @@
%% This is generated from src/mochiweb.app.src
{application, mochiweb,
[{description, "MochiMedia Web Server"},
- {vsn, "2.12.0"},
+ {vsn, "2.17.0"},
{modules, []},
{registered, []},
{env, []},
{applications, [kernel, stdlib, crypto, inets, ssl, xmerl,
- compiler, syntax_tools]}]}.
+ compiler, syntax_tools]},
+
+ {maintainers, ["Bob Ippolito"]},
+ {licenses, ["MIT"]},
+ {links, [{"Github", "https://github.com/mochi/mochiweb"}]}
+ ]
+}.
diff --git a/src/mochiweb.erl b/src/mochiweb.erl
index 5c4201c..14480c2 100644
--- a/src/mochiweb.erl
+++ b/src/mochiweb.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2007 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Start and stop the MochiWeb server.
diff --git a/src/mochiweb_acceptor.erl b/src/mochiweb_acceptor.erl
index 8a58fcf..44ce91f 100644
--- a/src/mochiweb_acceptor.erl
+++ b/src/mochiweb_acceptor.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2010 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc MochiWeb acceptor.
diff --git a/src/mochiweb_base64url.erl b/src/mochiweb_base64url.erl
index 5f552e0..e6a8e13 100644
--- a/src/mochiweb_base64url.erl
+++ b/src/mochiweb_base64url.erl
@@ -1,5 +1,27 @@
+%% @author Bob Ippolito <bob@mochimedia.com>
+%% @copyright 2013 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
+
-module(mochiweb_base64url).
-export([encode/1, decode/1]).
+
%% @doc URL and filename safe base64 variant with no padding,
%% also known as "base64url" per RFC 4648.
%%
diff --git a/src/mochiweb_charref.erl b/src/mochiweb_charref.erl
index 193c7c7..143452e 100644
--- a/src/mochiweb_charref.erl
+++ b/src/mochiweb_charref.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2007 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Converts HTML 5 charrefs and entities to codepoints (or lists of code points).
-module(mochiweb_charref).
diff --git a/src/mochiweb_clock.erl b/src/mochiweb_clock.erl
new file mode 100644
index 0000000..4f101c5
--- /dev/null
+++ b/src/mochiweb_clock.erl
@@ -0,0 +1,101 @@
+%% Copyright (c) 2011-2014, Loïc Hoguin <essen@ninenines.eu>
+%% Copyright (c) 2015, Robert Kowalski <rok@kowalski.gd>
+%%
+%% Permission to use, copy, modify, and/or distribute this software for any
+%% purpose with or without fee is hereby granted, provided that the above
+%% copyright notice and this permission notice appear in all copies.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+
+%% While a gen_server process runs in the background to update
+%% the cache of formatted dates every second, all API calls are
+%% local and directly read from the ETS cache table, providing
+%% fast time and date computations.
+
+-module(mochiweb_clock).
+
+-behaviour(gen_server).
+
+%% API.
+-export([start_link/0]).
+-export([start/0]).
+-export([stop/0]).
+-export([rfc1123/0]).
+
+%% gen_server.
+-export([init/1]).
+-export([handle_call/3]).
+-export([handle_cast/2]).
+-export([handle_info/2]).
+-export([terminate/2]).
+-export([code_change/3]).
+
+-record(state, {}).
+
+%% API.
+
+-spec start_link() -> {ok, pid()}.
+start_link() ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+-spec start() -> {ok, pid()}.
+start() ->
+ gen_server:start({local, ?MODULE}, ?MODULE, [], []).
+
+-spec stop() -> stopped.
+stop() ->
+ gen_server:call(?MODULE, stop).
+
+-spec rfc1123() -> string().
+rfc1123() ->
+ case ets:lookup(?MODULE, rfc1123) of
+ [{rfc1123, Date}] ->
+ Date;
+ [] ->
+ ""
+ end.
+
+%% gen_server.
+
+-spec init([]) -> {ok, #state{}}.
+init([]) ->
+ ?MODULE = ets:new(?MODULE, [named_table, protected, {read_concurrency, true}]),
+ handle_info(update_date, #state{}),
+ timer:send_interval(1000, update_date),
+ {ok, #state{}}.
+
+-type from() :: {pid(), term()}.
+-spec handle_call
+ (stop, from(), State) -> {stop, normal, stopped, State}
+ when State::#state{}.
+handle_call(stop, _From, State) ->
+ {stop, normal, stopped, State};
+handle_call(_Request, _From, State) ->
+ {reply, ignored, State}.
+
+-spec handle_cast(_, State) -> {noreply, State} when State::#state{}.
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+-spec handle_info(any(), State) -> {noreply, State} when State::#state{}.
+handle_info(update_date, State) ->
+ Date = httpd_util:rfc1123_date(),
+ ets:insert(?MODULE, {rfc1123, Date}),
+ {noreply, State};
+handle_info(_Info, State) ->
+ {noreply, State}.
+
+-spec terminate(_, _) -> ok.
+terminate(_Reason, _State) ->
+ ok.
+
+-spec code_change(_, State, _) -> {ok, State} when State::#state{}.
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
diff --git a/src/mochiweb_cookies.erl b/src/mochiweb_cookies.erl
index 1cc4e91..9539041 100644
--- a/src/mochiweb_cookies.erl
+++ b/src/mochiweb_cookies.erl
@@ -1,5 +1,23 @@
%% @author Emad El-Haraty <emad@mochimedia.com>
%% @copyright 2007 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc HTTP Cookie parsing and generating (RFC 2109, RFC 2965).
diff --git a/src/mochiweb_cover.erl b/src/mochiweb_cover.erl
index aa075d5..ebc2c18 100644
--- a/src/mochiweb_cover.erl
+++ b/src/mochiweb_cover.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2010 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Workarounds for various cover deficiencies.
-module(mochiweb_cover).
diff --git a/src/mochiweb_echo.erl b/src/mochiweb_echo.erl
index e145840..b14505c 100644
--- a/src/mochiweb_echo.erl
+++ b/src/mochiweb_echo.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2007 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Simple and stupid echo server to demo mochiweb_socket_server.
diff --git a/src/mochiweb_headers.erl b/src/mochiweb_headers.erl
index b49cf9e..457758f 100644
--- a/src/mochiweb_headers.erl
+++ b/src/mochiweb_headers.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2007 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Case preserving (but case insensitive) HTTP Header dictionary.
diff --git a/src/mochiweb_html.erl b/src/mochiweb_html.erl
index 3732924..95a2b44 100644
--- a/src/mochiweb_html.erl
+++ b/src/mochiweb_html.erl
@@ -1,11 +1,28 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2007 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Loosely tokenizes and generates parse trees for HTML 4.
-module(mochiweb_html).
-export([tokens/1, parse/1, parse_tokens/1, to_tokens/1, escape/1,
escape_attr/1, to_html/1]).
--compile([export_all]).
-ifdef(TEST).
-export([destack/1, destack/2, is_singleton/1]).
-endif.
@@ -36,6 +53,8 @@
-define(IS_WHITESPACE(C),
(C =:= $\s orelse C =:= $\t orelse C =:= $\r orelse C =:= $\n)).
+-define(IS_LETTER(C),
+ ((C >= $A andalso C =< $Z) orelse (C >= $a andalso C =< $z))).
-define(IS_LITERAL_SAFE(C),
((C >= $A andalso C =< $Z) orelse (C >= $a andalso C =< $z)
orelse (C >= $0 andalso C =< $9))).
@@ -331,7 +350,7 @@
{S2, _} = find_gt(B, S1),
{{end_tag, Tag}, S2};
<<_:O/binary, "<", C, _/binary>>
- when ?IS_WHITESPACE(C); not ?IS_LITERAL_SAFE(C) ->
+ when ?IS_WHITESPACE(C); not ?IS_LETTER(C) ->
%% This isn't really strict HTML
{{data, Data, _Whitespace}, S1} = tokenize_data(B, ?INC_COL(S)),
{{data, <<$<, Data/binary>>, false}, S1};
@@ -444,16 +463,21 @@
destack([{T1, A1, Acc1}, {T0, A0, Acc0} | Rest]) ->
destack([{T0, A0, [{T1, A1, lists:reverse(Acc1)} | Acc0]} | Rest]).
+is_singleton(<<"area">>) -> true;
+is_singleton(<<"base">>) -> true;
is_singleton(<<"br">>) -> true;
+is_singleton(<<"col">>) -> true;
+is_singleton(<<"embed">>) -> true;
is_singleton(<<"hr">>) -> true;
is_singleton(<<"img">>) -> true;
is_singleton(<<"input">>) -> true;
-is_singleton(<<"base">>) -> true;
-is_singleton(<<"meta">>) -> true;
+is_singleton(<<"keygen">>) -> true;
is_singleton(<<"link">>) -> true;
-is_singleton(<<"area">>) -> true;
+is_singleton(<<"meta">>) -> true;
is_singleton(<<"param">>) -> true;
-is_singleton(<<"col">>) -> true;
+is_singleton(<<"source">>) -> true;
+is_singleton(<<"track">>) -> true;
+is_singleton(<<"wbr">>) -> true;
is_singleton(_) -> false.
tokenize_data(B, S=#decoder{offset=O}) ->
@@ -621,13 +645,44 @@
tokenize_charref(Bin, S=#decoder{offset=O}) ->
try
- tokenize_charref(Bin, S, O)
+ case tokenize_charref_raw(Bin, S, O) of
+ {C1, S1} when C1 >= 16#D800 andalso C1 =< 16#DFFF ->
+ %% Surrogate pair
+ tokeninize_charref_surrogate_pair(Bin, S1, C1);
+ {Unichar, S1} when is_integer(Unichar) ->
+ {{data, mochiutf8:codepoint_to_bytes(Unichar), false},
+ S1};
+ {Unichars, S1} when is_list(Unichars) ->
+ {{data, unicode:characters_to_binary(Unichars), false},
+ S1};
+ {undefined, _} ->
+ throw(invalid_charref)
+ end
catch
throw:invalid_charref ->
{{data, <<"&">>, false}, S}
end.
-tokenize_charref(Bin, S=#decoder{offset=O}, Start) ->
+tokeninize_charref_surrogate_pair(Bin, S=#decoder{offset=O}, C1) ->
+ case Bin of
+ <<_:O/binary, $&, _/binary>> ->
+ case tokenize_charref_raw(Bin, ?INC_COL(S), O + 1) of
+ {C2, S1} when C2 >= 16#D800 andalso C1 =< 16#DFFF ->
+ {{data,
+ unicode:characters_to_binary(
+ <<C1:16, C2:16>>,
+ utf16,
+ utf8),
+ false},
+ S1};
+ _ ->
+ throw(invalid_charref)
+ end;
+ _ ->
+ throw(invalid_charref)
+ end.
+
+tokenize_charref_raw(Bin, S=#decoder{offset=O}, Start) ->
case Bin of
<<_:O/binary>> ->
throw(invalid_charref);
@@ -640,17 +695,9 @@
<<_:O/binary, $;, _/binary>> ->
Len = O - Start,
<<_:Start/binary, Raw:Len/binary, _/binary>> = Bin,
- Data = case mochiweb_charref:charref(Raw) of
- undefined ->
- throw(invalid_charref);
- Unichar when is_integer(Unichar) ->
- mochiutf8:codepoint_to_bytes(Unichar);
- Unichars when is_list(Unichars) ->
- unicode:characters_to_binary(Unichars)
- end,
- {{data, Data, false}, ?INC_COL(S)};
+ {mochiweb_charref:charref(Raw), ?INC_COL(S)};
_ ->
- tokenize_charref(Bin, ?INC_COL(S), Start)
+ tokenize_charref_raw(Bin, ?INC_COL(S), Start)
end.
tokenize_doctype(Bin, S) ->
diff --git a/src/mochiweb_http.erl b/src/mochiweb_http.erl
index 9e32d7d..3b02065 100644
--- a/src/mochiweb_http.erl
+++ b/src/mochiweb_http.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2007 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc HTTP server.
@@ -40,39 +58,53 @@
%% Option = {name, atom()} | {ip, string() | tuple()} | {backlog, integer()}
%% | {nodelay, boolean()} | {acceptor_pool_size, integer()}
%% | {ssl, boolean()} | {profile_fun, undefined | (Props) -> ok}
-%% | {link, false} | {recbuf, non_negative_integer()}
+%% | {link, false} | {recbuf, undefined | non_negative_integer()}
%% @doc Start a mochiweb server.
%% profile_fun is used to profile accept timing.
%% After each accept, if defined, profile_fun is called with a proplist of a subset of the mochiweb_socket_server state and timing information.
%% The proplist is as follows: [{name, Name}, {port, Port}, {active_sockets, ActiveSockets}, {timing, Timing}].
%% @end
start(Options) ->
+ ok = ensure_started(mochiweb_clock),
mochiweb_socket_server:start(parse_options(Options)).
start_link(Options) ->
+ ok = ensure_started(mochiweb_clock),
mochiweb_socket_server:start_link(parse_options(Options)).
+ensure_started(M) ->
+ case M:start() of
+ {ok, _Pid} ->
+ ok;
+ {error, {already_started, _Pid}} ->
+ ok
+ end.
+
loop(Socket, Opts, Body) ->
- ok = mochiweb_socket:setopts(Socket, [{packet, http}]),
+ ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, http}])),
request(Socket, Opts, Body).
request(Socket, Opts, Body) ->
- case mochiweb_socket:recv(Socket, 0, ?REQUEST_RECV_TIMEOUT) of
- {ok, {http_request, Method, Path, Version}} ->
- ok = mochiweb_socket:setopts(Socket, [{packet, httph}]),
+ ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{active, once}])),
+ receive
+ {Protocol, _, {http_request, Method, Path, Version}} when Protocol == http orelse Protocol == ssl ->
+ ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, httph}])),
headers(Socket, Opts, {Method, Path, Version}, [], Body, 0);
- {error, {http_error, "\r\n"}} ->
+ {Protocol, _, {http_error, "\r\n"}} when Protocol == http orelse Protocol == ssl ->
request(Socket, Opts, Body);
- {error, {http_error, "\n"}} ->
+ {Protocol, _, {http_error, "\n"}} when Protocol == http orelse Protocol == ssl ->
request(Socket, Opts, Body);
- {error, closed} ->
+ {tcp_closed, _} ->
mochiweb_socket:close(Socket),
exit(normal);
- {error, timeout} ->
+ {tcp_error, _, emsgsize} = Other ->
+ handle_invalid_msg_request(Other, Socket, Opts);
+ {ssl_closed, _} ->
mochiweb_socket:close(Socket),
- exit(normal);
- Other ->
- handle_invalid_msg_request(Other, Socket, Opts)
+ exit(normal)
+ after ?REQUEST_RECV_TIMEOUT ->
+ mochiweb_socket:close(Socket),
+ exit(normal)
end.
reentry(Body) ->
@@ -82,25 +114,26 @@
headers(Socket, Opts, Request, Headers, _Body, ?MAX_HEADERS) ->
%% Too many headers sent, bad request.
- ok = mochiweb_socket:setopts(Socket, [{packet, raw}]),
+ ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, raw}])),
handle_invalid_request(Socket, Opts, Request, Headers);
headers(Socket, Opts, Request, Headers, Body, HeaderCount) ->
- case mochiweb_socket:recv(Socket, 0, ?REQUEST_RECV_TIMEOUT) of
- {ok, http_eoh} ->
+ ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{active, once}])),
+ receive
+ {Protocol, _, http_eoh} when Protocol == http orelse Protocol == ssl ->
Req = new_request(Socket, Opts, Request, Headers),
call_body(Body, Req),
?MODULE:after_response(Body, Req);
- {ok, {http_header, _, Name, _, Value}} ->
+ {Protocol, _, {http_header, _, Name, _, Value}} when Protocol == http orelse Protocol == ssl ->
headers(Socket, Opts, Request, [{Name, Value} | Headers], Body,
1 + HeaderCount);
- {error, closed} ->
+ {tcp_closed, _} ->
mochiweb_socket:close(Socket),
exit(normal);
- {error, timeout} ->
- mochiweb_socket:close(Socket),
- exit(normal);
- Other ->
+ {tcp_error, _, emsgsize} = Other ->
handle_invalid_msg_request(Other, Socket, Opts, Request, Headers)
+ after ?HEADERS_RECV_TIMEOUT ->
+ mochiweb_socket:close(Socket),
+ exit(normal)
end.
call_body({M, F, A}, Req) ->
@@ -133,7 +166,7 @@
exit(normal).
new_request(Socket, Opts, Request, RevHeaders) ->
- ok = mochiweb_socket:setopts(Socket, [{packet, raw}]),
+ ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, raw}])),
mochiweb:new_request({Socket, Opts, Request, lists:reverse(RevHeaders)}).
after_response(Body, Req) ->
diff --git a/src/mochiweb_io.erl b/src/mochiweb_io.erl
index 8454b43..15b6b3a 100644
--- a/src/mochiweb_io.erl
+++ b/src/mochiweb_io.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2007 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Utilities for dealing with IO devices (open files).
diff --git a/src/mochiweb_mime.erl b/src/mochiweb_mime.erl
index 7d9f249..949d957 100644
--- a/src/mochiweb_mime.erl
+++ b/src/mochiweb_mime.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2007 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Gives a good MIME type guess based on file extension.
diff --git a/src/mochiweb_multipart.erl b/src/mochiweb_multipart.erl
index 90bc949..fd75443 100644
--- a/src/mochiweb_multipart.erl
+++ b/src/mochiweb_multipart.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2007 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Utilities for parsing multipart/form-data.
@@ -38,7 +56,7 @@
{HeaderList, Body};
parts_to_body(BodyList, ContentType, Size) when is_list(BodyList) ->
parts_to_multipart_body(BodyList, ContentType, Size,
- mochihex:to_hex(crypto:rand_bytes(8))).
+ mochihex:to_hex(crypto:strong_rand_bytes(8))).
%% @spec parts_to_multipart_body([bodypart()], ContentType::string(),
%% Size::integer(), Boundary::string()) ->
@@ -318,7 +336,7 @@
plain ->
gen_tcp:connect("127.0.0.1", Port, ClientOpts);
ssl ->
- ClientOpts1 = [{ssl_imp, new} | ClientOpts],
+ ClientOpts1 = mochiweb_test_util:ssl_client_opts(ClientOpts),
{ok, SslSocket} = ssl:connect("127.0.0.1", Port, ClientOpts1),
{ok, {ssl, SslSocket}}
end,
diff --git a/src/mochiweb_request.erl b/src/mochiweb_request.erl
index c97070f..ec0ad36 100644
--- a/src/mochiweb_request.erl
+++ b/src/mochiweb_request.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2007 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc MochiWeb HTTP Request abstraction.
@@ -13,7 +31,7 @@
-export([new/5, new/6]).
-export([get_header_value/2, get_primary_header_value/2, get_combined_header_value/2, get/2, dump/1]).
--export([send/2, recv/2, recv/3, recv_body/1, recv_body/2, stream_body/4]).
+-export([send/2, recv/2, recv/3, recv_body/1, recv_body/2, stream_body/4, stream_body/5]).
-export([start_response/2, start_response_length/2, start_raw_response/2]).
-export([respond/2, ok/2]).
-export([not_found/1, not_found/2]).
@@ -101,6 +119,21 @@
Hosts ->
string:strip(lists:last(string:tokens(Hosts, ",")))
end;
+ %% Copied this syntax from webmachine contributor Steve Vinoski
+ {ok, {Addr={172, Second, _, _}, _Port}} when (Second > 15) andalso (Second < 32) ->
+ case get_header_value("x-forwarded-for", THIS) of
+ undefined ->
+ inet_parse:ntoa(Addr);
+ Hosts ->
+ string:strip(lists:last(string:tokens(Hosts, ",")))
+ end;
+ {ok, {Addr={192, 168, _, _}, _Port}} ->
+ case get_header_value("x-forwarded-for", THIS) 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", THIS) of
undefined ->
@@ -117,7 +150,7 @@
case erlang:get(?SAVE_PATH) of
undefined ->
{Path0, _, _} = mochiweb_util:urlsplit_path(RawPath),
- Path = mochiweb_util:unquote(Path0),
+ Path = mochiweb_util:normalize_path(mochiweb_util:unquote(Path0)),
put(?SAVE_PATH, Path),
Path;
Cached ->
@@ -262,7 +295,7 @@
MaxBodyLength when is_integer(MaxBodyLength), MaxBodyLength < Length ->
exit({body_too_large, content_length});
_ ->
- stream_unchunked_body(Length, ChunkFun, FunState, THIS)
+ stream_unchunked_body(MaxChunkSize,Length, ChunkFun, FunState, THIS)
end
end.
@@ -308,12 +341,10 @@
false ->
HResponse1
end,
- F = fun ({K, V}, Acc) ->
- [mochiweb_util:make_io(K), <<": ">>, V, <<"\r\n">> | Acc]
- end,
- End = lists:foldl(F, [<<"\r\n">>], mochiweb_headers:to_list(HResponse2)),
+ End = [[mochiweb_util:make_io(K), <<": ">>, V, <<"\r\n">>]
+ || {K, V} <- mochiweb_headers:to_list(HResponse2)],
Response = mochiweb:new_response({THIS, Code, HResponse2}),
- {[make_version(Version), make_code(Code), <<"\r\n">> | End], Response};
+ {[make_version(Version), make_code(Code), <<"\r\n">> | [End, <<"\r\n">>]], Response};
format_response_header({Code, ResponseHeaders, Length},
{?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
HResponse = mochiweb_headers:make(ResponseHeaders),
@@ -526,28 +557,28 @@
stream_chunked_body(MaxChunkSize, Fun, NewState, THIS)
end.
-stream_unchunked_body(0, Fun, FunState, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}) ->
+stream_unchunked_body(_MaxChunkSize, 0, Fun, FunState, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}) ->
Fun({0, <<>>}, FunState);
-stream_unchunked_body(Length, Fun, FunState,
+stream_unchunked_body(MaxChunkSize, Length, Fun, FunState,
{?MODULE, [_Socket, Opts, _Method, _RawPath, _Version, _Headers]}=THIS) when Length > 0 ->
- RecBuf = mochilists:get_value(recbuf, Opts, ?RECBUF_SIZE),
- PktSize = case Length > RecBuf of
- true ->
- RecBuf;
- false ->
- Length
+ RecBuf = case mochilists:get_value(recbuf, Opts, ?RECBUF_SIZE) of
+ undefined -> %os controlled buffer size
+ MaxChunkSize;
+ Val ->
+ Val
end,
+ PktSize=min(Length,RecBuf),
Bin = recv(PktSize, THIS),
NewState = Fun({PktSize, Bin}, FunState),
- stream_unchunked_body(Length - PktSize, Fun, NewState, THIS).
+ stream_unchunked_body(MaxChunkSize, Length - PktSize, Fun, NewState, THIS).
%% @spec read_chunk_length(request()) -> integer()
%% @doc Read the length of the next HTTP chunk.
read_chunk_length({?MODULE, [Socket, _Opts, _Method, _RawPath, _Version, _Headers]}) ->
- ok = mochiweb_socket:setopts(Socket, [{packet, line}]),
+ ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, line}])),
case mochiweb_socket:recv(Socket, 0, ?IDLE_TIMEOUT) of
{ok, Header} ->
- ok = mochiweb_socket:setopts(Socket, [{packet, raw}]),
+ ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, raw}])),
Splitter = fun (C) ->
C =/= $\r andalso C =/= $\n andalso C =/= $
end,
@@ -561,7 +592,7 @@
%% @doc Read in a HTTP chunk of the given length. If Length is 0, then read the
%% HTTP footers (as a list of binaries, since they're nominal).
read_chunk(0, {?MODULE, [Socket, _Opts, _Method, _RawPath, _Version, _Headers]}) ->
- ok = mochiweb_socket:setopts(Socket, [{packet, line}]),
+ ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, line}])),
F = fun (F1, Acc) ->
case mochiweb_socket:recv(Socket, 0, ?IDLE_TIMEOUT) of
{ok, <<"\r\n">>} ->
@@ -573,7 +604,7 @@
end
end,
Footers = F(F, []),
- ok = mochiweb_socket:setopts(Socket, [{packet, raw}]),
+ ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, raw}])),
put(?SAVE_RECV, true),
Footers;
read_chunk(Length, {?MODULE, [Socket, _Opts, _Method, _RawPath, _Version, _Headers]}) ->
@@ -673,7 +704,7 @@
server_headers() ->
[{"Server", "MochiWeb/1.0 (" ++ ?QUIP ++ ")"},
- {"Date", httpd_util:rfc1123_date()}].
+ {"Date", mochiweb_clock:rfc1123()}].
make_code(X) when is_integer(X) ->
[integer_to_list(X), [" " | httpd_util:reason_phrase(X)]];
diff --git a/src/mochiweb_response.erl b/src/mochiweb_response.erl
index 308a26b..195e652 100644
--- a/src/mochiweb_response.erl
+++ b/src/mochiweb_response.erl
@@ -1,5 +1,23 @@
%% @author Bob Ippolito <bob@mochimedia.com>
%% @copyright 2007 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc Response abstraction.
diff --git a/src/mochiweb_session.erl b/src/mochiweb_session.erl
index bd78606..f89f19b 100644
--- a/src/mochiweb_session.erl
+++ b/src/mochiweb_session.erl
@@ -1,4 +1,23 @@
%% @author Asier Azkuenaga Batiz <asier@zebixe.com>
+%% @copyright 2013 Mochi Media, Inc.
+%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
%% @doc HTTP Cookie session. Note that the expiration time travels unencrypted
%% as far as this module is concerned. In order to achieve more security,
@@ -103,7 +122,7 @@
-ifdef(crypto_compatibility).
-spec encrypt_data(binary(), binary()) -> binary().
encrypt_data(Data, Key) ->
- IV = crypto:rand_bytes(16),
+ IV = crypto:strong_rand_bytes(16),
Crypt = crypto:aes_cfb_128_encrypt(Key, IV, Data),
<<IV/binary, Crypt/binary>>.
@@ -122,7 +141,7 @@
-else.
-spec encrypt_data(binary(), binary()) -> binary().
encrypt_data(Data, Key) ->
- IV = crypto:rand_bytes(16),
+ IV = crypto:strong_rand_bytes(16),
Crypt = crypto:block_encrypt(aes_cfb128, Key, IV, Data),
<<IV/binary, Crypt/binary>>.
diff --git a/src/mochiweb_socket.erl b/src/mochiweb_socket.erl
index 1e35e15..1756b8e 100644
--- a/src/mochiweb_socket.erl
+++ b/src/mochiweb_socket.erl
@@ -7,7 +7,7 @@
-export([listen/4,
accept/1, transport_accept/1, finish_accept/1,
recv/3, send/2, close/1, port/1, peername/1,
- setopts/2, getopts/2, type/1]).
+ setopts/2, getopts/2, type/1, exit_if_closed/1]).
-define(ACCEPT_TIMEOUT, 2000).
-define(SSL_TIMEOUT, 10000).
@@ -142,3 +142,7 @@
type(_) ->
plain.
+exit_if_closed({error, closed}) ->
+ exit(normal);
+exit_if_closed(Res) ->
+ Res.
diff --git a/src/mochiweb_socket_server.erl b/src/mochiweb_socket_server.erl
index 7f8587e..7a9b0d3 100644
--- a/src/mochiweb_socket_server.erl
+++ b/src/mochiweb_socket_server.erl
@@ -117,7 +117,20 @@
parse_options(Rest, State#mochiweb_socket_server{backlog=Backlog});
parse_options([{nodelay, NoDelay} | Rest], State) ->
parse_options(Rest, State#mochiweb_socket_server{nodelay=NoDelay});
-parse_options([{recbuf, RecBuf} | Rest], State) when is_integer(RecBuf) ->
+parse_options([{recbuf, RecBuf} | Rest], State) when is_integer(RecBuf) orelse
+ RecBuf == undefined ->
+ %% XXX: `recbuf' value which is passed to `gen_tcp'
+ %% and value reported by `inet:getopts(P, [recbuf])' may
+ %% differ. They depends on underlying OS. From linux mans:
+ %%
+ %% The kernel doubles this value (to allow space for
+ %% bookkeeping overhead) when it is set using setsockopt(2),
+ %% and this doubled value is returned by getsockopt(2).
+ %%
+ %% See: man 7 socket | grep SO_RCVBUF
+ %%
+ %% In case undefined is passed instead of the default buffer
+ %% size ?RECBUF_SIZE, no size is set and the OS can control it dynamically
parse_options(Rest, State#mochiweb_socket_server{recbuf=RecBuf});
parse_options([{acceptor_pool_size, Max} | Rest], State) ->
MaxInt = ensure_int(Max),
@@ -173,7 +186,6 @@
{reuseaddr, true},
{packet, 0},
{backlog, Backlog},
- {recbuf, RecBuf},
{exit_on_close, false},
{active, false},
{nodelay, NoDelay}],
@@ -188,42 +200,33 @@
{_, _, _, _, _, _, _, _} -> % IPv6
[inet6, {ip, Ip} | BaseOpts]
end,
- listen(Port, Opts, State).
+ OptsBuf=case RecBuf of
+ undefined ->
+ Opts;
+ _ ->
+ [{recbuf, RecBuf}|Opts]
+ end,
+ listen(Port, OptsBuf, State).
-new_acceptor_pool(Listen,
- State=#mochiweb_socket_server{acceptor_pool=Pool,
- acceptor_pool_size=Size,
- recbuf=RecBuf,
- loop=Loop}) ->
+new_acceptor_pool(State=#mochiweb_socket_server{acceptor_pool_size=Size}) ->
+ lists:foldl(fun (_, S) -> new_acceptor(S) end, State, lists:seq(1, Size)).
+
+new_acceptor(State=#mochiweb_socket_server{acceptor_pool=Pool,
+ recbuf=RecBuf,
+ loop=Loop,
+ listen=Listen}) ->
LoopOpts = [{recbuf, RecBuf}],
- F = fun (_, S) ->
- Pid = mochiweb_acceptor:start_link(
- self(), Listen, Loop, LoopOpts
- ),
- sets:add_element(Pid, S)
- end,
- Pool1 = lists:foldl(F, Pool, lists:seq(1, Size)),
- State#mochiweb_socket_server{acceptor_pool=Pool1}.
+ Pid = mochiweb_acceptor:start_link(self(), Listen, Loop, LoopOpts),
+ State#mochiweb_socket_server{
+ acceptor_pool=sets:add_element(Pid, Pool)}.
-listen(Port, Opts, State=#mochiweb_socket_server{ssl=Ssl, ssl_opts=SslOpts,
- recbuf=RecBuf}) ->
+listen(Port, Opts, State=#mochiweb_socket_server{ssl=Ssl, ssl_opts=SslOpts}) ->
case mochiweb_socket:listen(Ssl, Port, Opts, SslOpts) of
{ok, Listen} ->
- %% XXX: `recbuf' value which is passed to `gen_tcp'
- %% and value reported by `inet:getopts(P, [recbuf])' may
- %% differ. They depends on underlying OS. From linux mans:
- %%
- %% The kernel doubles this value (to allow space for
- %% bookkeeping overhead) when it is set using setsockopt(2),
- %% and this doubled value is returned by getsockopt(2).
- %%
- %% See: man 7 socket | grep SO_RCVBUF
{ok, ListenPort} = mochiweb_socket:port(Listen),
- {ok, new_acceptor_pool(
- Listen,
- State#mochiweb_socket_server{listen=Listen,
- port=ListenPort,
- recbuf=RecBuf})};
+ {ok, new_acceptor_pool(State#mochiweb_socket_server{
+ listen=Listen,
+ port=ListenPort})};
{error, Reason} ->
{stop, Reason}
end.
@@ -300,38 +303,30 @@
recycle_acceptor(Pid, State=#mochiweb_socket_server{
acceptor_pool=Pool,
acceptor_pool_size=PoolSize,
- listen=Listen,
- loop=Loop,
max=Max,
- recbuf=RecBuf,
active_sockets=ActiveSockets}) ->
- LoopOpts = [{recbuf, RecBuf}],
- case sets:is_element(Pid, Pool) of
- true ->
- Pool1 = sets:del_element(Pid, Pool),
- case ActiveSockets + sets:size(Pool1) < Max of
- true ->
- Acceptor = mochiweb_acceptor:start_link(
- self(), Listen, Loop, LoopOpts
- ),
- Pool2 = sets:add_element(Acceptor, Pool1),
- State#mochiweb_socket_server{acceptor_pool=Pool2};
- false ->
- State#mochiweb_socket_server{acceptor_pool=Pool1}
- end;
- false ->
- case sets:size(Pool) < PoolSize of
- true ->
- Acceptor = mochiweb_acceptor:start_link(
- self(), Listen, Loop, LoopOpts
- ),
- Pool1 = sets:add_element(Acceptor, Pool),
- State#mochiweb_socket_server{active_sockets=ActiveSockets,
- acceptor_pool=Pool1};
- false ->
- State#mochiweb_socket_server{active_sockets=ActiveSockets - 1,
- acceptor_pool=Pool}
- end
+ %% A socket is considered to be active from immediately after it
+ %% has been accepted (see the {accepted, Pid, Timing} cast above).
+ %% This function will be called when an acceptor is transitioning
+ %% to an active socket, or when either type of Pid dies. An acceptor
+ %% Pid will always be in the acceptor_pool set, and an active socket
+ %% will be in that set during the transition but not afterwards.
+ Pool1 = sets:del_element(Pid, Pool),
+ NewSize = sets:size(Pool1),
+ ActiveSockets1 = case NewSize =:= sets:size(Pool) of
+ %% Pid has died and it is not in the acceptor set,
+ %% it must be an active socket.
+ true -> max(0, ActiveSockets - 1);
+ false -> ActiveSockets
+ end,
+ State1 = State#mochiweb_socket_server{
+ acceptor_pool=Pool1,
+ active_sockets=ActiveSockets1},
+ %% Spawn a new acceptor only if it will not overrun the maximum socket
+ %% count or the maximum pool size.
+ case NewSize + ActiveSockets1 < Max andalso NewSize < PoolSize of
+ true -> new_acceptor(State1);
+ false -> State1
end.
handle_info(Msg, State) when ?is_old_state(State) ->
diff --git a/src/mochiweb_util.erl b/src/mochiweb_util.erl
index c606767..dde2962 100644
--- a/src/mochiweb_util.erl
+++ b/src/mochiweb_util.erl
@@ -14,6 +14,8 @@
-export([safe_relative_path/1, partition/2]).
-export([parse_qvalues/1, pick_accepted_encodings/3]).
-export([make_io/1]).
+-export([normalize_path/1]).
+-export([rand_uniform/2]).
-define(PERCENT, 37). % $\%
-define(FULLSTOP, 46). % $\.
@@ -586,6 +588,28 @@
make_io(Io) when is_list(Io); is_binary(Io) ->
Io.
+%% @spec normalize_path(string()) -> string()
+%% @doc Remove duplicate slashes from an uri path ("//foo///bar////" becomes
+%% "/foo/bar/").
+%% Per RFC 3986, all but the last path segment must be non-empty.
+normalize_path(Path) ->
+ normalize_path(Path, []).
+
+normalize_path([], Acc) ->
+ lists:reverse(Acc);
+normalize_path("/" ++ Path, "/" ++ _ = Acc) ->
+ normalize_path(Path, Acc);
+normalize_path([C|Path], Acc) ->
+ normalize_path(Path, [C|Acc]).
+
+-ifdef(rand_mod_unavailable).
+rand_uniform(Start, End) ->
+ crypto:rand_uniform(Start, End).
+-else.
+rand_uniform(Start, End) ->
+ Start + rand:uniform(End - Start) - 1.
+-endif.
+
%%
%% Tests
%%
@@ -990,4 +1014,35 @@
),
ok.
+normalize_path_test() ->
+ "" = normalize_path(""),
+ "/" = normalize_path("/"),
+ "/" = normalize_path("//"),
+ "/" = normalize_path("///"),
+ "foo" = normalize_path("foo"),
+ "/foo" = normalize_path("/foo"),
+ "/foo" = normalize_path("//foo"),
+ "/foo" = normalize_path("///foo"),
+ "foo/" = normalize_path("foo/"),
+ "foo/" = normalize_path("foo//"),
+ "foo/" = normalize_path("foo///"),
+ "foo/bar" = normalize_path("foo/bar"),
+ "foo/bar" = normalize_path("foo//bar"),
+ "foo/bar" = normalize_path("foo///bar"),
+ "foo/bar" = normalize_path("foo////bar"),
+ "/foo/bar" = normalize_path("/foo/bar"),
+ "/foo/bar" = normalize_path("/foo////bar"),
+ "/foo/bar" = normalize_path("////foo/bar"),
+ "/foo/bar" = normalize_path("////foo///bar"),
+ "/foo/bar" = normalize_path("////foo////bar"),
+ "/foo/bar/" = normalize_path("/foo/bar/"),
+ "/foo/bar/" = normalize_path("////foo/bar/"),
+ "/foo/bar/" = normalize_path("/foo////bar/"),
+ "/foo/bar/" = normalize_path("/foo/bar////"),
+ "/foo/bar/" = normalize_path("///foo////bar/"),
+ "/foo/bar/" = normalize_path("////foo/bar////"),
+ "/foo/bar/" = normalize_path("/foo///bar////"),
+ "/foo/bar/" = normalize_path("////foo///bar////"),
+ ok.
+
-endif.
diff --git a/src/mochiweb_websocket.erl b/src/mochiweb_websocket.erl
index 2768a3e..14b242a 100644
--- a/src/mochiweb_websocket.erl
+++ b/src/mochiweb_websocket.erl
@@ -28,11 +28,11 @@
-export([loop/5, upgrade_connection/2, request/5]).
-export([send/3]).
-ifdef(TEST).
--compile(export_all).
+-export([make_handshake/1, hixie_handshake/7, parse_hybi_frames/3, parse_hixie_frames/2]).
-endif.
loop(Socket, Body, State, WsVersion, ReplyChannel) ->
- ok = mochiweb_socket:setopts(Socket, [{packet, 0}, {active, once}]),
+ ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, 0}, {active, once}])),
proc_lib:hibernate(?MODULE, request,
[Socket, Body, State, WsVersion, ReplyChannel]).
@@ -206,7 +206,7 @@
_MaskKey:4/binary,
_/binary-unit:8>> = PartFrame,
Acc) ->
- ok = mochiweb_socket:setopts(Socket, [{packet, 0}, {active, once}]),
+ ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, 0}, {active, once}])),
receive
{tcp_closed, _} ->
mochiweb_socket:close(Socket),
diff --git a/src/reloader.erl b/src/reloader.erl
index 8266b33..8130f45 100644
--- a/src/reloader.erl
+++ b/src/reloader.erl
@@ -1,6 +1,24 @@
-%% @copyright 2007 Mochi Media, Inc.
%% @author Matthew Dempsky <matthew@mochimedia.com>
+%% @copyright 2007 Mochi Media, Inc.
%%
+%% Permission is hereby granted, free of charge, to any person obtaining a
+%% copy of this software and associated documentation files (the "Software"),
+%% to deal in the Software without restriction, including without limitation
+%% the rights to use, copy, modify, merge, publish, distribute, sublicense,
+%% and/or sell copies of the Software, and to permit persons to whom the
+%% Software is furnished to do so, subject to the following conditions:
+%%
+%% The above copyright notice and this permission notice shall be included in
+%% all copies or substantial portions of the Software.
+%%
+%% THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+%% IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+%% FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+%% THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+%% LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+%% FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+%% DEALINGS IN THE SOFTWARE.
+
%% @doc Erlang module for automatically reloading modified modules
%% during development.
diff --git a/support/templates/mochiwebapp.template b/support/templates/mochiwebapp.template
index 4942609..c56314c 100644
--- a/support/templates/mochiwebapp.template
+++ b/support/templates/mochiwebapp.template
@@ -13,6 +13,7 @@
{template, "mochiwebapp_skel/src/mochiapp_sup.erl", "{{dest}}/src/{{appid}}_sup.erl"}.
{template, "mochiwebapp_skel/src/mochiapp_web.erl", "{{dest}}/src/{{appid}}_web.erl"}.
{template, "mochiwebapp_skel/start-dev.sh", "{{dest}}/start-dev.sh"}.
+{template, "mochiwebapp_skel/bench.sh", "{{dest}}/bench.sh"}.
{template, "mochiwebapp_skel/priv/www/index.html", "{{dest}}/priv/www/index.html"}.
{file, "../../.gitignore", "{{dest}}/.gitignore"}.
{file, "../../Makefile", "{{dest}}/Makefile"}.
@@ -20,3 +21,4 @@
{file, "../../rebar", "{{dest}}/rebar"}.
{chmod, 8#755, "{{dest}}/rebar"}.
{chmod, 8#755, "{{dest}}/start-dev.sh"}.
+{chmod, 8#755, "{{dest}}/bench.sh"}.
diff --git a/support/templates/mochiwebapp_skel/bench.sh b/support/templates/mochiwebapp_skel/bench.sh
new file mode 100755
index 0000000..eb6e9c9
--- /dev/null
+++ b/support/templates/mochiwebapp_skel/bench.sh
@@ -0,0 +1,19 @@
+#!/bin/sh
+
+# workaround for rebar mustache template bug
+DEFAULT_PORT={{port}}
+HOST=${HOST:-127.0.0.1}
+PORT=${PORT:-${DEFAULT_PORT}}
+
+BENCH_RUN="siege -q -c400 -r100 -b http://$HOST:$PORT/hello_world"
+
+sleep 120
+
+echo ""
+echo ""
+for i in `seq 1 10`;
+do
+ echo "Running test #$i:"
+ $BENCH_RUN
+ sleep 90
+done
diff --git a/support/templates/mochiwebapp_skel/src/mochiapp_web.erl b/support/templates/mochiwebapp_skel/src/mochiapp_web.erl
index 5fe455a..8429a88 100644
--- a/support/templates/mochiwebapp_skel/src/mochiapp_web.erl
+++ b/support/templates/mochiwebapp_skel/src/mochiapp_web.erl
@@ -26,6 +26,9 @@
case Req:get(method) of
Method when Method =:= 'GET'; Method =:= 'HEAD' ->
case Path of
+ "hello_world" ->
+ Req:respond({200, [{"Content-Type", "text/plain"}],
+ "Hello world!\n"});
_ ->
Req:serve_file(Path, DocRoot)
end;
diff --git a/test/mochiweb_base64url_tests.erl b/test/mochiweb_base64url_tests.erl
index 69f276a..4f73666 100644
--- a/test/mochiweb_base64url_tests.erl
+++ b/test/mochiweb_base64url_tests.erl
@@ -9,10 +9,15 @@
X,
mochiweb_base64url:decode(
binary_to_list(mochiweb_base64url:encode(binary_to_list(X))))).
-
+-ifdef(rand_mod_unavailable).
random_binary(Short,Long) ->
<< <<(random:uniform(256) - 1)>>
|| _ <- lists:seq(1, Short + random:uniform(1 + Long - Short) - 1) >>.
+-else.
+random_binary(Short,Long) ->
+ << <<(rand:uniform(256) - 1)>>
+ || _ <- lists:seq(1, Short + rand:uniform(1 + Long - Short) - 1) >>.
+-endif.
empty_test() ->
id(<<>>).
diff --git a/test/mochiweb_html_tests.erl b/test/mochiweb_html_tests.erl
index 3d35400..5dceb9a 100644
--- a/test/mochiweb_html_tests.erl
+++ b/test/mochiweb_html_tests.erl
@@ -126,6 +126,12 @@
mochiweb_html:tokens(<<"not html < at all">>)),
ok.
+surrogate_test() ->
+ %% https://github.com/mochi/mochiweb/issues/164
+ ?assertEqual(
+ [{data,<<240,159,152,138>>,false}],
+ mochiweb_html:tokens(<<"��">>)).
+
parse_test() ->
D0 = <<"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">
<html>
@@ -422,6 +428,52 @@
{<<"div">>,[],[{<<"br">>, [], []}, {<<"br">>, [], []}, <<"z">>]},
mochiweb_html:parse("<div><br><br>z</br></br></div>")).
+empty_elements_test() ->
+ ?assertEqual(
+ {<<"div">>,[],[<<"a">>,{<<"area">>,[],[]},<<"z">>]},
+ mochiweb_html:parse("<div>a<area>z</div>")),
+ ?assertEqual(
+ {<<"div">>,[],[<<"a">>,{<<"base">>,[],[]},<<"z">>]},
+ mochiweb_html:parse("<div>a<base>z</div>")),
+ ?assertEqual(
+ {<<"div">>,[],[<<"a">>,{<<"br">>,[],[]},<<"z">>]},
+ mochiweb_html:parse("<div>a<br>z</div>")),
+ ?assertEqual(
+ {<<"div">>,[],[<<"a">>,{<<"col">>,[],[]},<<"z">>]},
+ mochiweb_html:parse("<div>a<col>z</div>")),
+ ?assertEqual(
+ {<<"div">>,[],[<<"a">>,{<<"embed">>,[],[]},<<"z">>]},
+ mochiweb_html:parse("<div>a<embed>z</div>")),
+ ?assertEqual(
+ {<<"div">>,[],[<<"a">>,{<<"hr">>,[],[]},<<"z">>]},
+ mochiweb_html:parse("<div>a<hr>z</div>")),
+ ?assertEqual(
+ {<<"div">>,[],[<<"a">>,{<<"img">>,[],[]},<<"z">>]},
+ mochiweb_html:parse("<div>a<img>z</div>")),
+ ?assertEqual(
+ {<<"div">>,[],[<<"a">>,{<<"input">>,[],[]},<<"z">>]},
+ mochiweb_html:parse("<div>a<input>z</div>")),
+ ?assertEqual(
+ {<<"div">>,[],[<<"a">>,{<<"keygen">>,[],[]},<<"z">>]},
+ mochiweb_html:parse("<div>a<keygen>z</div>")),
+ ?assertEqual(
+ {<<"div">>,[],[<<"a">>,{<<"link">>,[],[]},<<"z">>]},
+ mochiweb_html:parse("<div>a<link>z</div>")),
+ ?assertEqual(
+ {<<"div">>,[],[<<"a">>,{<<"meta">>,[],[]},<<"z">>]},
+ mochiweb_html:parse("<div>a<meta>z</div>")),
+ ?assertEqual(
+ {<<"div">>,[],[<<"a">>,{<<"param">>,[],[]},<<"z">>]},
+ mochiweb_html:parse("<div>a<param>z</div>")),
+ ?assertEqual(
+ {<<"div">>,[],[<<"a">>,{<<"source">>,[],[]},<<"z">>]},
+ mochiweb_html:parse("<div>a<source>z</div>")),
+ ?assertEqual(
+ {<<"div">>,[],[<<"a">>,{<<"track">>,[],[]},<<"z">>]},
+ mochiweb_html:parse("<div>a<track>z</div>")),
+ ?assertEqual(
+ {<<"div">>,[],[<<"a">>,{<<"wbr">>,[],[]},<<"z">>]},
+ mochiweb_html:parse("<div>a<wbr>z</div>")).
php_test() ->
%% http://code.google.com/p/mochiweb/issues/detail?id=71
@@ -556,7 +608,15 @@
?_assertEqual(
{<<"html">>,[],
[{<<"body">>,[],[<<"&">>]}]},
- mochiweb_html:parse("<html><body>&</body></html>"))].
+ mochiweb_html:parse("<html><body>&</body></html>")),
+ ?_assertEqual(
+ {<<"html">>,[],
+ [{<<"body">>,[],[<<"&;">>]}]},
+ mochiweb_html:parse("<html><body>&;</body></html>")),
+ ?_assertEqual(
+ {<<"html">>,[],
+ [{<<"body">>,[],[<<"&MISSING;">>]}]},
+ mochiweb_html:parse("<html><body>&MISSING;</body></html>"))].
parse_unescaped_lt_test() ->
D1 = <<"<div> < < <a href=\"/\">Back</a></div>">>,
@@ -587,3 +647,10 @@
[{<<"head">>, [], []},
{<<"body">>, [], []}]},
mochiweb_html:parse("<!doctype html><head></head><body></body>")).
+
+no_letter_no_tag_test() ->
+ ?assertEqual(
+ {<<"html">>,[],
+ [{<<"body">>,[],[<<"<3><!><*><<>>">>,{<<"body">>,[],[]}]}]},
+ mochiweb_html:parse(<<"<html><body><3><!><*><<>><body></html>">>)
+ ).
diff --git a/test/mochiweb_test_util.erl b/test/mochiweb_test_util.erl
index 2fbf14f..c4feaed 100644
--- a/test/mochiweb_test_util.erl
+++ b/test/mochiweb_test_util.erl
@@ -1,6 +1,6 @@
-module(mochiweb_test_util).
-export([with_server/3, client_request/4, sock_fun/2,
- read_server_headers/1, drain_reply/3]).
+ read_server_headers/1, drain_reply/3, ssl_client_opts/1]).
-include("mochiweb_test_util.hrl").
-include_lib("eunit/include/eunit.hrl").
@@ -25,6 +25,14 @@
mochiweb_http:stop(Server),
Res.
+-ifdef(sni_unavailable).
+ssl_client_opts(Opts) ->
+ [{ssl_imp, new} | Opts].
+-else.
+ssl_client_opts(Opts) ->
+ [{server_name_indication, disable} | Opts].
+-endif.
+
sock_fun(Transport, Port) ->
Opts = [binary, {active, false}, {packet, http}],
case Transport of
@@ -42,7 +50,7 @@
Socket
end;
ssl ->
- {ok, Socket} = ssl:connect("127.0.0.1", Port, [{ssl_imp, new} | Opts]),
+ {ok, Socket} = ssl:connect("127.0.0.1", Port, ssl_client_opts(Opts)),
fun (recv) ->
ssl:recv(Socket, 0);
({recv, Length}) ->
@@ -63,7 +71,7 @@
{the_end, {error, closed}} = {the_end, SockFun(recv)},
ok;
client_request(SockFun, Method,
- [#treq{path=Path, body=Body, xreply=ExReply} | Rest]) ->
+ [#treq{path=Path, body=Body, xreply=ExReply, xheaders=ExHeaders} | Rest]) ->
Request = [atom_to_list(Method), " ", Path, " HTTP/1.1\r\n",
client_headers(Body, Rest =:= []),
"\r\n",
@@ -83,6 +91,14 @@
?assert(mochiweb_headers:get_value("Date", Headers) =/= undefined),
?assert(mochiweb_headers:get_value("Content-Type", Headers) =/= undefined),
ContentLength = list_to_integer(mochiweb_headers:get_value("Content-Length", Headers)),
+ EHeaders = mochiweb_headers:make(ExHeaders),
+ lists:foreach(
+ fun (K) ->
+ ?assertEqual(mochiweb_headers:get_value(K, EHeaders),
+ mochiweb_headers:get_value(K, Headers))
+ end,
+ %% Assumes implementation details of the headers
+ gb_trees:keys(EHeaders)),
{payload, ExReply} = {payload, drain_reply(SockFun, ContentLength, <<>>)},
client_request(SockFun, Method, Rest).
diff --git a/test/mochiweb_test_util.hrl b/test/mochiweb_test_util.hrl
index 99fdc4e..503be98 100644
--- a/test/mochiweb_test_util.hrl
+++ b/test/mochiweb_test_util.hrl
@@ -1 +1 @@
--record(treq, {path, body= <<>>, xreply= <<>>}).
+-record(treq, {path, body= <<>>, xreply= <<>>, xheaders= []}).
diff --git a/test/mochiweb_tests.erl b/test/mochiweb_tests.erl
index 209971b..23f8312 100644
--- a/test/mochiweb_tests.erl
+++ b/test/mochiweb_tests.erl
@@ -6,7 +6,7 @@
mochiweb_test_util:with_server(Transport, ServerFun, ClientFun).
request_test() ->
- R = mochiweb_request:new(z, z, "/foo/bar/baz%20wibble+quux?qs=2", z, []),
+ R = mochiweb_request:new(z, z, "//foo///bar/baz%20wibble+quux?qs=2", z, []),
"/foo/bar/baz wibble quux" = R:get(path),
ok.
@@ -96,6 +96,23 @@
?_assertEqual(ok, with_server(Transport, ServerFun, ClientFun))}
|| Transport <- [ssl, plain]].
+
+cookie_header_test() ->
+ ReplyPrefix = "You requested: ",
+ ExHeaders = [{"Set-Cookie", "foo=bar"},
+ {"Set-Cookie", "foo=baz"}],
+ ServerFun = fun (Req) ->
+ Reply = ReplyPrefix ++ Req:get(path),
+ Req:ok({"text/plain", ExHeaders, Reply})
+ end,
+ Path = "cookie_header",
+ ExpectedReply = list_to_binary(ReplyPrefix ++ Path),
+ TestReqs = [#treq{path=Path, xreply=ExpectedReply, xheaders=ExHeaders}],
+ ClientFun = new_client_fun('GET', TestReqs),
+ ok = with_server(plain, ServerFun, ClientFun),
+ ok.
+
+
do_CONNECT(Transport, Times) ->
PathPrefix = "example.com:",
ReplyPrefix = "You requested: ",
@@ -138,7 +155,7 @@
end,
TestReqs = [begin
Path = "/stuff/" ++ integer_to_list(N),
- Body = crypto:rand_bytes(Size),
+ Body = crypto:strong_rand_bytes(Size),
#treq{path=Path, body=Body, xreply=Body}
end || N <- lists:seq(1, Times)],
ClientFun = new_client_fun('POST', TestReqs),