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(<<"&#55357;&#56842;">>)).
+
 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),