You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by da...@apache.org on 2014/02/12 07:21:55 UTC

[23/50] [abbrv] mochiweb commit: updated refs/heads/import-master to 3a54dbf

upgrade mochiweb to r113, use hooks instead of forking mochijson2. COUCHDB-474

git-svn-id: https://svn.apache.org/repos/asf/couchdb/trunk@882941 13f79535-47bb-0310-9956-ffa450edef68


Project: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/commit/11513d3e
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/tree/11513d3e
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/diff/11513d3e

Branch: refs/heads/import-master
Commit: 11513d3ece1f2b4e51457e64f6b802f6507bd80a
Parents: 9b22305
Author: Adam Kocoloski <ko...@apache.org>
Authored: Sat Nov 21 17:01:50 2009 +0000
Committer: Adam Kocoloski <ko...@apache.org>
Committed: Sat Nov 21 17:01:50 2009 +0000

----------------------------------------------------------------------
 Makefile.am                |   2 +-
 mochijson.erl              |  17 +++--
 mochijson2.erl             | 102 +++++++++++++++++--------
 mochiweb.app.in            |   2 +-
 mochiweb_html.erl          |  15 +++-
 mochiweb_http.erl          |  26 +++++--
 mochiweb_multipart.erl     | 165 ++++++++++++++++++++++++++++++++--------
 mochiweb_request.erl       | 119 +++++++++++++++++++----------
 mochiweb_skel.erl          |   6 +-
 mochiweb_socket_server.erl |   2 +-
 mochiweb_util.erl          |   6 +-
 reloader.erl               |   3 +-
 12 files changed, 336 insertions(+), 129 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/11513d3e/Makefile.am
----------------------------------------------------------------------
diff --git a/Makefile.am b/Makefile.am
index 608d4dc..c191abf 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -10,7 +10,7 @@
 ## License for the specific language governing permissions and limitations under
 ## the License.
 
-mochiwebebindir = $(localerlanglibdir)/mochiweb-r97/ebin
+mochiwebebindir = $(localerlanglibdir)/mochiweb-r113/ebin
 
 mochiweb_file_collection = \
     mochifmt.erl \

http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/11513d3e/mochijson.erl
----------------------------------------------------------------------
diff --git a/mochijson.erl b/mochijson.erl
index 029642a..74695a7 100644
--- a/mochijson.erl
+++ b/mochijson.erl
@@ -186,7 +186,8 @@ json_encode_string_utf8_1([C | Cs]) when C >= 0, C =< 16#7f ->
            end,
     [NewC | json_encode_string_utf8_1(Cs)];
 json_encode_string_utf8_1(All=[C | _]) when C >= 16#80, C =< 16#10FFFF ->
-    json_encode_string_unicode(xmerl_ucs:from_utf8(All));
+    [?Q | Rest] = json_encode_string_unicode(xmerl_ucs:from_utf8(All)),
+    Rest;
 json_encode_string_utf8_1([]) ->
     "\"".
 
@@ -459,16 +460,18 @@ equiv_object(Props1, Props2) ->
 equiv_list([], []) ->
     true;
 equiv_list([V1 | L1], [V2 | L2]) ->
-    case equiv(V1, V2) of
-        true ->
-            equiv_list(L1, L2);
-        false ->
-            false
-    end.
+    equiv(V1, V2) andalso equiv_list(L1, L2).
 
 test_all() ->
+    test_issue33(),
     test_one(e2j_test_vec(utf8), 1).
 
+test_issue33() ->
+    %% http://code.google.com/p/mochiweb/issues/detail?id=33
+    Js = {struct, [{"key", [194, 163]}]},
+    Encoder = encoder([{input_encoding, utf8}]),
+    "{\"key\":\"\\u00a3\"}" = lists:flatten(Encoder(Js)).
+
 test_one([], _N) ->
     %% io:format("~p tests passed~n", [N-1]),
     ok;

http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/11513d3e/mochijson2.erl
----------------------------------------------------------------------
diff --git a/mochijson2.erl b/mochijson2.erl
index ee19458..66f68bf 100644
--- a/mochijson2.erl
+++ b/mochijson2.erl
@@ -42,7 +42,8 @@
 %% @type json_term() = json_string() | json_number() | json_array() |
 %%                     json_object()
 
--record(encoder, {handler=null}).
+-record(encoder, {handler=null,
+                  utf8=false}).
 
 -record(decoder, {object_hook=null,
                   offset=0,
@@ -52,6 +53,8 @@
 
 %% @spec encoder([encoder_option()]) -> function()
 %% @doc Create an encoder/1 with the given options.
+%% @type encoder_option() = handler_option() | utf8_option()
+%% @type utf8_option() = boolean(). Emit unicode as utf8 (default - false)
 encoder(Options) ->
     State = parse_encoder_options(Options, #encoder{}),
     fun (O) -> json_encode(O, State) end.
@@ -70,10 +73,7 @@ decoder(Options) ->
 %% @spec decode(iolist()) -> json_term()
 %% @doc Decode the given iolist to Erlang terms.
 decode(S) ->
-    try json_decode(S, #decoder{})
-    catch
-        _:_ -> throw({invalid_json, S})
-    end.
+    json_decode(S, #decoder{}).
 
 test() ->
     test_all().
@@ -83,7 +83,9 @@ test() ->
 parse_encoder_options([], State) ->
     State;
 parse_encoder_options([{handler, Handler} | Rest], State) ->
-    parse_encoder_options(Rest, State#encoder{handler=Handler}).
+    parse_encoder_options(Rest, State#encoder{handler=Handler});
+parse_encoder_options([{utf8, Switch} | Rest], State) ->
+    parse_encoder_options(Rest, State#encoder{utf8=Switch}).
 
 parse_decoder_options([], State) ->
     State;
@@ -96,15 +98,18 @@ json_encode(false, _State) ->
     <<"false">>;
 json_encode(null, _State) ->
     <<"null">>;
-json_encode(I, _State) when is_integer(I) ->
+json_encode(I, _State) when is_integer(I) andalso I >= -2147483648 andalso I =< 2147483647 ->
+    %% Anything outside of 32-bit integers should be encoded as a float
     integer_to_list(I);
+json_encode(I, _State) when is_integer(I) ->
+    mochinum:digits(float(I));
 json_encode(F, _State) when is_float(F) ->
     mochinum:digits(F);
 json_encode(S, State) when is_binary(S); is_atom(S) ->
     json_encode_string(S, State);
 json_encode(Array, State) when is_list(Array) ->
     json_encode_array(Array, State);
-json_encode({Props}, State) when is_list(Props) ->
+json_encode({struct, Props}, State) when is_list(Props) ->
     json_encode_proplist(Props, State);
 json_encode(Bad, #encoder{handler=null}) ->
     exit({json_encode, {bad_term, Bad}});
@@ -131,29 +136,29 @@ json_encode_proplist(Props, State) ->
     [$, | Acc1] = lists:foldl(F, "{", Props),
     lists:reverse([$\} | Acc1]).
 
-json_encode_string(A, _State) when is_atom(A) ->
+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), [?Q])
+            json_encode_string_unicode(xmerl_ucs:from_utf8(L), State, [?Q])
     end;
-json_encode_string(B, _State) when is_binary(B) ->
+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), [?Q])
+            json_encode_string_unicode(xmerl_ucs:from_utf8(B), State, [?Q])
     end;
 json_encode_string(I, _State) when is_integer(I) ->
     [?Q, integer_to_list(I), ?Q];
-json_encode_string(L, _State) when is_list(L) ->
+json_encode_string(L, State) when is_list(L) ->
     case json_string_is_safe(L) of
         true ->
             [?Q, L, ?Q];
         false ->
-            json_encode_string_unicode(L, [?Q])
+            json_encode_string_unicode(L, State, [?Q])
     end.
 
 json_string_is_safe([]) ->
@@ -208,9 +213,9 @@ json_bin_is_safe(<<C, Rest/binary>>) ->
             false
     end.
 
-json_encode_string_unicode([], Acc) ->
+json_encode_string_unicode([], _State, Acc) ->
     lists:reverse([$\" | Acc]);
-json_encode_string_unicode([C | Cs], Acc) ->
+json_encode_string_unicode([C | Cs], State, Acc) ->
     Acc1 = case C of
                ?Q ->
                    [?Q, $\\ | Acc];
@@ -236,14 +241,18 @@ json_encode_string_unicode([C | Cs], Acc) ->
                    [$r, $\\ | Acc];
                $\t ->
                    [$t, $\\ | Acc];
-               C when C >= 0, C < $\s; C >= 16#7f, C =< 16#10FFFF ->
+               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];
+               C when  C >= 16#7f, C =< 16#10FFFF, not State#encoder.utf8 ->
                    [unihex(C) | Acc];
                C when C < 16#7f ->
                    [C | Acc];
                _ ->
                    exit({json_encode, {bad_char, C}})
            end,
-    json_encode_string_unicode(Cs, Acc1).
+    json_encode_string_unicode(Cs, State, Acc1).
 
 hexdigit(C) when C >= 0, C =< 9 ->
     C + $0;
@@ -288,7 +297,7 @@ decode_object(B, S) ->
 decode_object(B, S=#decoder{state=key}, Acc) ->
     case tokenize(B, S) of
         {end_object, S1} ->
-            V = make_object({lists:reverse(Acc)}, S1),
+            V = make_object({struct, lists:reverse(Acc)}, S1),
             {V, S1#decoder{state=null}};
         {{const, K}, S1} ->
             {colon, S2} = tokenize(B, S1),
@@ -298,7 +307,7 @@ decode_object(B, S=#decoder{state=key}, Acc) ->
 decode_object(B, S=#decoder{state=comma}, Acc) ->
     case tokenize(B, S) of
         {end_object, S1} ->
-            V = make_object({lists:reverse(Acc)}, S1),
+            V = make_object({struct, lists:reverse(Acc)}, S1),
             {V, S1#decoder{state=null}};
         {comma, S1} ->
             decode_object(B, S1#decoder{state=key}, Acc)
@@ -507,9 +516,9 @@ tokenize(B, S=#decoder{offset=O}) ->
 %% Create an object from a list of Key/Value pairs.
 
 obj_new() ->
-    {[]}.
+    {struct, []}.
 
-is_obj({Props}) ->
+is_obj({struct, Props}) ->
     F = fun ({K, _}) when is_binary(K) ->
                 true;
             (_) ->
@@ -518,7 +527,7 @@ is_obj({Props}) ->
     lists:all(F, Props).
 
 obj_from_list(Props) ->
-    Obj = {Props},
+    Obj = {struct, Props},
     case is_obj(Obj) of
         true -> Obj;
         false -> exit({json_bad_object, Obj})
@@ -529,7 +538,7 @@ obj_from_list(Props) ->
 %% compare unequal as erlang terms, so we need to carefully recurse
 %% through aggregates (tuples and objects).
 
-equiv({Props1}, {Props2}) ->
+equiv({struct, Props1}, {struct, Props2}) ->
     equiv_object(Props1, Props2);
 equiv(L1, L2) when is_list(L1), is_list(L2) ->
     equiv_list(L1, L2);
@@ -555,16 +564,13 @@ equiv_object(Props1, Props2) ->
 equiv_list([], []) ->
     true;
 equiv_list([V1 | L1], [V2 | L2]) ->
-    case equiv(V1, V2) of
-        true ->
-            equiv_list(L1, L2);
-        false ->
-            false
-    end.
+    equiv(V1, V2) andalso equiv_list(L1, L2).
 
 test_all() ->
     [1199344435545.0, 1] = decode(<<"[1199344435545.0,1]">>),
     <<16#F0,16#9D,16#9C,16#95>> = decode([34,"\\ud835","\\udf15",34]),
+    test_encoder_utf8(),
+    test_input_validation(),
     test_one(e2j_test_vec(utf8), 1).
 
 test_one([], _N) ->
@@ -619,3 +625,39 @@ e2j_test_vec(utf8) ->
      {[-123, <<"foo">>, obj_from_list([{<<"bar">>, []}]), null],
       "[-123,\"foo\",{\"bar\":[]},null]"}
     ].
+
+%% test utf8 encoding
+test_encoder_utf8() ->
+    %% safe conversion case (default)
+    [34,"\\u0001","\\u0442","\\u0435","\\u0441","\\u0442",34] =
+        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">>).
+
+test_input_validation() ->
+    Good = [
+        {16#00A3, <<?Q, 16#C2, 16#A3, ?Q>>}, % pound
+        {16#20AC, <<?Q, 16#E2, 16#82, 16#AC, ?Q>>}, % euro
+        {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 = decode(UTF8)
+    end, Good),
+
+    Bad = [
+        % 2nd, 3rd, or 4th byte of a multi-byte sequence w/o leading byte
+        <<?Q, 16#80, ?Q>>,
+        % missing continuations, last byte in each should be 80-BF
+        <<?Q, 16#C2, 16#7F, ?Q>>,
+        <<?Q, 16#E0, 16#80,16#7F, ?Q>>,
+        <<?Q, 16#F0, 16#80, 16#80, 16#7F, ?Q>>,
+        % we don't support code points > 10FFFF per RFC 3629
+        <<?Q, 16#F5, 16#80, 16#80, 16#80, ?Q>>
+    ],
+    lists:foreach(fun(X) ->
+        ok = try decode(X) catch invalid_utf8 -> ok end
+    end, Bad).

http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/11513d3e/mochiweb.app.in
----------------------------------------------------------------------
diff --git a/mochiweb.app.in b/mochiweb.app.in
index cd8dbb2..b0f9014 100644
--- a/mochiweb.app.in
+++ b/mochiweb.app.in
@@ -1,6 +1,6 @@
 {application, mochiweb,
  [{description, "MochiMedia Web Server"},
-  {vsn, "0.01"},
+  {vsn, "113"},
   {modules, [
         mochihex,
         mochijson,

http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/11513d3e/mochiweb_html.erl
----------------------------------------------------------------------
diff --git a/mochiweb_html.erl b/mochiweb_html.erl
index 0e030c1..77100d5 100644
--- a/mochiweb_html.erl
+++ b/mochiweb_html.erl
@@ -305,6 +305,18 @@ test_tokens() ->
      {data, <<" A= B <= C ">>, false},
      {end_tag, <<"script">>}] =
         tokens(<<"<script type=\"text/javascript\"> A= B <= C </script>">>),
+    [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
+     {data, <<" A= B <= C ">>, false},
+     {end_tag, <<"script">>}] =
+        tokens(<<"<script type =\"text/javascript\"> A= B <= C </script>">>),
+    [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
+     {data, <<" A= B <= C ">>, false},
+     {end_tag, <<"script">>}] =
+        tokens(<<"<script type = \"text/javascript\"> A= B <= C </script>">>),
+    [{start_tag, <<"script">>, [{<<"type">>, <<"text/javascript">>}], false},
+     {data, <<" A= B <= C ">>, false},
+     {end_tag, <<"script">>}] =
+        tokens(<<"<script type= \"text/javascript\"> A= B <= C </script>">>),
     [{start_tag, <<"textarea">>, [], false},
      {data, <<"<html></body>">>, false},
      {end_tag, <<"textarea">>}] =
@@ -672,7 +684,8 @@ tokenize_attr_value(Attr, B, S) ->
     O = S1#decoder.offset,
     case B of
         <<_:O/binary, "=", _/binary>> ->
-            tokenize_word_or_literal(B, ?INC_COL(S1));
+            S2 = skip_whitespace(B, ?INC_COL(S1)),
+            tokenize_word_or_literal(B, S2);
         _ ->
             {Attr, S1}
     end.

http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/11513d3e/mochiweb_http.erl
----------------------------------------------------------------------
diff --git a/mochiweb_http.erl b/mochiweb_http.erl
index 14a3657..f1821f4 100644
--- a/mochiweb_http.erl
+++ b/mochiweb_http.erl
@@ -7,6 +7,7 @@
 -author('bob@mochimedia.com').
 -export([start/0, start/1, stop/0, stop/1]).
 -export([loop/2, default_body/1]).
+-export([after_response/2, reentry/1]).
 
 -define(IDLE_TIMEOUT, 30000).
 
@@ -110,6 +111,11 @@ request(Socket, Body) ->
             exit(normal)
     end.
 
+reentry(Body) ->
+    fun (Req) ->
+            ?MODULE:after_response(Body, Req)
+    end.
+
 headers(Socket, Request, Headers, _Body, ?MAX_HEADERS) ->
     %% Too many headers sent, bad request.
     inet:setopts(Socket, [{packet, raw}]),
@@ -125,14 +131,7 @@ headers(Socket, Request, Headers, Body, HeaderCount) ->
             Req = mochiweb:new_request({Socket, Request,
                                         lists:reverse(Headers)}),
             Body(Req),
-            case Req:should_close() of
-                true ->
-                    gen_tcp:close(Socket),
-                    exit(normal);
-                false ->
-                    Req:cleanup(),
-                    ?MODULE:loop(Socket, Body)
-            end;
+            ?MODULE:after_response(Body, Req);
         {ok, {http_header, _, Name, _, Value}} ->
             headers(Socket, Request, [{Name, Value} | Headers], Body,
                     1 + HeaderCount);
@@ -140,3 +139,14 @@ headers(Socket, Request, Headers, Body, HeaderCount) ->
             gen_tcp:close(Socket),
             exit(normal)
     end.
+
+after_response(Body, Req) ->
+    Socket = Req:get(socket),
+    case Req:should_close() of
+        true ->
+            gen_tcp:close(Socket),
+            exit(normal);
+        false ->
+            Req:cleanup(),
+            ?MODULE:loop(Socket, Body)
+    end.

http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/11513d3e/mochiweb_multipart.erl
----------------------------------------------------------------------
diff --git a/mochiweb_multipart.erl b/mochiweb_multipart.erl
index b963161..0368a9a 100644
--- a/mochiweb_multipart.erl
+++ b/mochiweb_multipart.erl
@@ -76,15 +76,15 @@ parse_multipart_request(Req, Callback) ->
     Boundary = iolist_to_binary(
                  get_boundary(Req:get_header_value("content-type"))),
     Prefix = <<"\r\n--", Boundary/binary>>,
-    BS = size(Boundary),
+    BS = byte_size(Boundary),
     Chunk = read_chunk(Req, Length),
-    Length1 = Length - size(Chunk),
+    Length1 = Length - byte_size(Chunk),
     <<"--", Boundary:BS/binary, "\r\n", Rest/binary>> = Chunk,
-    feed_mp(headers, #mp{boundary=Prefix,
-                         length=Length1,
-                         buffer=Rest,
-                         callback=Callback,
-                         req=Req}).
+    feed_mp(headers, flash_multipart_hack(#mp{boundary=Prefix,
+                                              length=Length1,
+                                              buffer=Rest,
+                                              callback=Callback,
+                                              req=Req})).
 
 parse_headers(<<>>) ->
     [];
@@ -117,8 +117,27 @@ read_chunk(Req, Length) when Length > 0 ->
 read_more(State=#mp{length=Length, buffer=Buffer, req=Req}) ->
     Data = read_chunk(Req, Length),
     Buffer1 = <<Buffer/binary, Data/binary>>,
-    State#mp{length=Length - size(Data),
-             buffer=Buffer1}.
+    flash_multipart_hack(State#mp{length=Length - byte_size(Data),
+                                  buffer=Buffer1}).
+
+flash_multipart_hack(State=#mp{length=0, buffer=Buffer, boundary=Prefix}) ->
+    %% http://code.google.com/p/mochiweb/issues/detail?id=22
+    %% Flash doesn't terminate multipart with \r\n properly so we fix it up here
+    PrefixSize = size(Prefix),
+    case size(Buffer) - (2 + PrefixSize) of
+        Seek when Seek >= 0 ->
+            case Buffer of
+                <<_:Seek/binary, Prefix:PrefixSize/binary, "--">> ->
+                    Buffer1 = <<Buffer/binary, "\r\n">>,
+                    State#mp{buffer=Buffer1};
+                _ ->
+                    State
+            end;
+        _ ->
+            State
+    end;
+flash_multipart_hack(State) ->
+    State.
 
 feed_mp(headers, State=#mp{buffer=Buffer, callback=Callback}) ->
     {State1, P} = case find_in_binary(<<"\r\n\r\n">>, Buffer) of
@@ -136,7 +155,8 @@ feed_mp(headers, State=#mp{buffer=Buffer, callback=Callback}) ->
     feed_mp(body, State1#mp{buffer=Rest,
                             callback=NextCallback});
 feed_mp(body, State=#mp{boundary=Prefix, buffer=Buffer, callback=Callback}) ->
-    case find_boundary(Prefix, Buffer) of
+    Boundary = find_boundary(Prefix, Buffer),
+    case Boundary of
         {end_boundary, Start, Skip} ->
             <<Data:Start/binary, _:Skip/binary, Rest/binary>> = Buffer,
             C1 = Callback({body, Data}),
@@ -158,7 +178,7 @@ feed_mp(body, State=#mp{boundary=Prefix, buffer=Buffer, callback=Callback}) ->
     end.
 
 get_boundary(ContentType) ->
-    {"multipart/" ++ _, Opts} = mochiweb_util:parse_header(ContentType),
+    {"multipart/form-data", Opts} = mochiweb_util:parse_header(ContentType),
     case proplists:get_value("boundary", Opts) of
         S when is_list(S) ->
             S
@@ -242,7 +262,11 @@ test_callback(Expect, [Expect | Rest]) ->
             ok;
         _ ->
             fun (Next) -> test_callback(Next, Rest) end
-    end.
+    end;
+test_callback({body, Got}, [{body, Expect} | Rest]) ->
+    GotSize = size(Got),
+    <<Got:GotSize/binary, Expect1/binary>> = Expect,
+    fun (Next) -> test_callback(Next, [{body, Expect1} | Rest]) end.
 
 test_parse3() ->
     ContentType = "multipart/form-data; boundary=---------------------------7386909285754635891697677882",
@@ -261,14 +285,12 @@ test_parse3() ->
               eof],
     TestCallback = fun (Next) -> test_callback(Next, Expect) end,
     ServerFun = fun (Socket) ->
-                        case gen_tcp:send(Socket, BinContent) of
-                            ok ->
-                                exit(normal)
-                        end
+                        ok = gen_tcp:send(Socket, BinContent),
+                        exit(normal)
                 end,
     ClientFun = fun (Socket) ->
                         Req = fake_request(Socket, ContentType,
-                                           size(BinContent)),
+                                           byte_size(BinContent)),
                         Res = parse_multipart_request(Req, TestCallback),
                         {0, <<>>, ok} = Res,
                         ok
@@ -294,14 +316,12 @@ test_parse2() ->
               eof],
     TestCallback = fun (Next) -> test_callback(Next, Expect) end,
     ServerFun = fun (Socket) ->
-                        case gen_tcp:send(Socket, BinContent) of
-                            ok ->
-                                exit(normal)
-                        end
+                        ok = gen_tcp:send(Socket, BinContent),
+                        exit(normal)
                 end,
     ClientFun = fun (Socket) ->
                         Req = fake_request(Socket, ContentType,
-                                           size(BinContent)),
+                                           byte_size(BinContent)),
                         Res = parse_multipart_request(Req, TestCallback),
                         {0, <<>>, ok} = Res,
                         ok
@@ -327,14 +347,12 @@ test_parse_form() ->
                  ""], "\r\n"),
     BinContent = iolist_to_binary(Content),
     ServerFun = fun (Socket) ->
-                        case gen_tcp:send(Socket, BinContent) of
-                            ok ->
-                                exit(normal)
-                        end
+                        ok = gen_tcp:send(Socket, BinContent),
+                        exit(normal)
                 end,
     ClientFun = fun (Socket) ->
                         Req = fake_request(Socket, ContentType,
-                                           size(BinContent)),
+                                           byte_size(BinContent)),
                         Res = parse_form(Req),
                         [{"submit-name", "Larry"},
                          {"files", {"file1.txt", {"text/plain",[]},
@@ -376,14 +394,12 @@ test_parse() ->
               eof],
     TestCallback = fun (Next) -> test_callback(Next, Expect) end,
     ServerFun = fun (Socket) ->
-                        case gen_tcp:send(Socket, BinContent) of
-                            ok ->
-                                exit(normal)
-                        end
+                        ok = gen_tcp:send(Socket, BinContent),
+                        exit(normal)
                 end,
     ClientFun = fun (Socket) ->
                         Req = fake_request(Socket, ContentType,
-                                           size(BinContent)),
+                                           byte_size(BinContent)),
                         Res = parse_multipart_request(Req, TestCallback),
                         {0, <<>>, ok} = Res,
                         ok
@@ -419,6 +435,89 @@ test_find_in_binary() ->
     {partial, 1, 3} = find_in_binary(<<"foobar">>, <<"afoo">>),
     ok.
 
+test_flash_parse() ->
+    ContentType = "multipart/form-data; boundary=----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5",
+    "----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5" = get_boundary(ContentType),
+    BinContent = <<"------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Filename\"\r\n\r\nhello.txt\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"success_action_status\"\r\n\r\n201\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"file\"; filename=\"hello.txt\"\r\nContent-Type: application/octet-stream\r\n\r\nhello\n\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Upload\"\r\n\r\nSubmit Query\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5--">>,
+    Expect = [{headers,
+               [{"content-disposition",
+                 {"form-data", [{"name", "Filename"}]}}]},
+              {body, <<"hello.txt">>},
+              body_end,
+              {headers,
+               [{"content-disposition",
+                 {"form-data", [{"name", "success_action_status"}]}}]},
+              {body, <<"201">>},
+              body_end,
+              {headers,
+               [{"content-disposition",
+                 {"form-data", [{"name", "file"}, {"filename", "hello.txt"}]}},
+                {"content-type", {"application/octet-stream", []}}]},
+              {body, <<"hello\n">>},
+              body_end,
+              {headers,
+               [{"content-disposition",
+                 {"form-data", [{"name", "Upload"}]}}]},
+              {body, <<"Submit Query">>},
+              body_end,
+              eof],
+    TestCallback = fun (Next) -> test_callback(Next, Expect) end,
+    ServerFun = fun (Socket) ->
+                        ok = gen_tcp:send(Socket, BinContent),
+                        exit(normal)
+                end,
+    ClientFun = fun (Socket) ->
+                        Req = fake_request(Socket, ContentType,
+                                           byte_size(BinContent)),
+                        Res = parse_multipart_request(Req, TestCallback),
+                        {0, <<>>, ok} = Res,
+                        ok
+                end,
+    ok = with_socket_server(ServerFun, ClientFun),
+    ok.
+
+test_flash_parse2() ->
+    ContentType = "multipart/form-data; boundary=----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5",
+    "----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5" = get_boundary(ContentType),
+    Chunk = iolist_to_binary(string:copies("%", 4096)),
+    BinContent = <<"------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Filename\"\r\n\r\nhello.txt\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"success_action_status\"\r\n\r\n201\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"file\"; filename=\"hello.txt\"\r\nContent-Type: application/octet-stream\r\n\r\n", Chunk/binary, "\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Upload\"\r\n\r\nSubmit Query\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5--">>,
+    Expect = [{headers,
+               [{"content-disposition",
+                 {"form-data", [{"name", "Filename"}]}}]},
+              {body, <<"hello.txt">>},
+              body_end,
+              {headers,
+               [{"content-disposition",
+                 {"form-data", [{"name", "success_action_status"}]}}]},
+              {body, <<"201">>},
+              body_end,
+              {headers,
+               [{"content-disposition",
+                 {"form-data", [{"name", "file"}, {"filename", "hello.txt"}]}},
+                {"content-type", {"application/octet-stream", []}}]},
+              {body, Chunk},
+              body_end,
+              {headers,
+               [{"content-disposition",
+                 {"form-data", [{"name", "Upload"}]}}]},
+              {body, <<"Submit Query">>},
+              body_end,
+              eof],
+    TestCallback = fun (Next) -> test_callback(Next, Expect) end,
+    ServerFun = fun (Socket) ->
+                        ok = gen_tcp:send(Socket, BinContent),
+                        exit(normal)
+                end,
+    ClientFun = fun (Socket) ->
+                        Req = fake_request(Socket, ContentType,
+                                           byte_size(BinContent)),
+                        Res = parse_multipart_request(Req, TestCallback),
+                        {0, <<>>, ok} = Res,
+                        ok
+                end,
+    ok = with_socket_server(ServerFun, ClientFun),
+    ok.
+
 test() ->
     test_find_in_binary(),
     test_find_boundary(),
@@ -426,4 +525,6 @@ test() ->
     test_parse2(),
     test_parse3(),
     test_parse_form(),
+    test_flash_parse(),
+    test_flash_parse2(),
     ok.

http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/11513d3e/mochiweb_request.erl
----------------------------------------------------------------------
diff --git a/mochiweb_request.erl b/mochiweb_request.erl
index e8f0a67..fc296f4 100644
--- a/mochiweb_request.erl
+++ b/mochiweb_request.erl
@@ -190,8 +190,13 @@ stream_body(MaxChunkSize, ChunkFun, FunState) ->
     stream_body(MaxChunkSize, ChunkFun, FunState, undefined).
 
 stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength) ->
-
-    case get_header_value("expect") of
+    Expect = case get_header_value("expect") of
+                 undefined ->
+                     undefined;
+                 Value when is_list(Value) ->
+                     string:to_lower(Value)
+             end,
+    case Expect of
         "100-continue" ->
             start_raw_response({100, gb_trees:empty()});
         _Else ->
@@ -214,7 +219,7 @@ stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength) ->
             MaxBodyLength when is_integer(MaxBodyLength), MaxBodyLength < Length ->
                 exit({body_too_large, content_length});
             _ ->
-                stream_unchunked_body(Length, MaxChunkSize, ChunkFun, FunState)
+                stream_unchunked_body(Length, ChunkFun, FunState)
             end;
         Length ->
             exit({length_not_integer, Length})
@@ -449,13 +454,20 @@ stream_chunked_body(MaxChunkSize, Fun, FunState) ->
             stream_chunked_body(MaxChunkSize, Fun, NewState)
     end.
 
-stream_unchunked_body(0, _MaxChunkSize, Fun, FunState) ->
+stream_unchunked_body(0, Fun, FunState) ->
     Fun({0, <<>>}, FunState);
-stream_unchunked_body(Length, _, Fun, FunState) when Length > 0 ->
+stream_unchunked_body(Length, Fun, FunState) when Length > 0 ->
     Bin = recv(0),
-    BinSize = size(Bin),
-    NewState = Fun({BinSize, Bin}, FunState),
-    stream_unchunked_body(Length - BinSize, 0, Fun, NewState).
+    BinSize = byte_size(Bin),
+    if BinSize > Length ->
+        <<OurBody:Length/binary, Extra/binary>> = Bin,
+        gen_tcp:unrecv(Socket, Extra),
+        NewState = Fun({Length, OurBody}, FunState),
+        stream_unchunked_body(0, Fun, NewState);
+    true ->
+        NewState = Fun({BinSize, Bin}, FunState),
+        stream_unchunked_body(Length - BinSize, Fun, NewState)
+    end.
 
 
 %% @spec read_chunk_length() -> integer()
@@ -521,40 +533,69 @@ serve_file(Path, DocRoot, ExtraHeaders) ->
             not_found(ExtraHeaders);
         RelPath ->
             FullPath = filename:join([DocRoot, RelPath]),
-            File = case filelib:is_dir(FullPath) of
-                       true ->
-                           filename:join([FullPath, "index.html"]);
-                       false ->
-                           FullPath
-                   end,
-            case file:read_file_info(File) of
-                {ok, FileInfo} ->
-                    LastModified = httpd_util:rfc1123_date(FileInfo#file_info.mtime),
-                    case get_header_value("if-modified-since") of
-                        LastModified ->
-                            respond({304, ExtraHeaders, ""});
-                        _ ->
-                            case file:open(File, [raw, binary]) of
-                                {ok, IoDevice} ->
-                                    ContentType = mochiweb_util:guess_mime(File),
-                                    Res = ok({ContentType,
-                                              [{"last-modified", LastModified}
-                                               | ExtraHeaders],
-                                              {file, IoDevice}}),
-                                    file:close(IoDevice),
-                                    Res;
-                                _ ->
-                                    not_found(ExtraHeaders)
-                            end
-                    end;
-                {error, _} ->
-                    not_found(ExtraHeaders)
+            case filelib:is_dir(FullPath) of
+                true ->
+                    maybe_redirect(RelPath, FullPath, ExtraHeaders);
+                false ->
+                    maybe_serve_file(FullPath, ExtraHeaders)
             end
     end.
 
-
 %% Internal API
 
+%% This has the same effect as the DirectoryIndex directive in httpd
+directory_index(FullPath) ->
+    filename:join([FullPath, "index.html"]).
+
+maybe_redirect([], FullPath, ExtraHeaders) ->
+    maybe_serve_file(directory_index(FullPath), ExtraHeaders);
+
+maybe_redirect(RelPath, FullPath, ExtraHeaders) ->
+    case string:right(RelPath, 1) of
+        "/" ->
+            maybe_serve_file(directory_index(FullPath), ExtraHeaders);
+        _   ->
+            Host = mochiweb_headers:get_value("host", Headers),
+            Location = "http://" ++ Host  ++ "/" ++ RelPath ++ "/",
+            LocationBin = list_to_binary(Location),
+            MoreHeaders = [{"Location", Location},
+                           {"Content-Type", "text/html"} | ExtraHeaders],
+            Top = <<"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">"
+            "<html><head>"
+            "<title>301 Moved Permanently</title>"
+            "</head><body>"
+            "<h1>Moved Permanently</h1>"
+            "<p>The document has moved <a href=\"">>,
+            Bottom = <<">here</a>.</p></body></html>\n">>,
+            Body = <<Top/binary, LocationBin/binary, Bottom/binary>>,
+            respond({301, MoreHeaders, Body})
+    end.
+
+maybe_serve_file(File, ExtraHeaders) ->
+    case file:read_file_info(File) of
+        {ok, FileInfo} ->
+            LastModified = httpd_util:rfc1123_date(FileInfo#file_info.mtime),
+            case get_header_value("if-modified-since") of
+                LastModified ->
+                    respond({304, ExtraHeaders, ""});
+                _ ->
+                    case file:open(File, [raw, binary]) of
+                        {ok, IoDevice} ->
+                            ContentType = mochiweb_util:guess_mime(File),
+                            Res = ok({ContentType,
+                                      [{"last-modified", LastModified}
+                                       | ExtraHeaders],
+                                      {file, IoDevice}}),
+                            file:close(IoDevice),
+                            Res;
+                        _ ->
+                            not_found(ExtraHeaders)
+                    end
+            end;
+        {error, _} ->
+            not_found(ExtraHeaders)
+    end.
+
 server_headers() ->
     [{"Server", "MochiWeb/1.0 (" ++ ?QUIP ++ ")"},
      {"Date", httpd_util:rfc1123_date()}].
@@ -639,7 +680,6 @@ range_parts({file, IoDevice}, Ranges) ->
                            end,
                            LocNums, Data),
     {Bodies, Size};
-
 range_parts(Body0, Ranges) ->
     Body = iolist_to_binary(Body0),
     Size = size(Body),
@@ -706,7 +746,6 @@ test_range() ->
     [{none, 20}] = parse_range_request("bytes=-20"),
     io:format(".. ok ~n"),
 
-
     %% invalid, single ranges
     io:format("Testing parse_range_request with invalid ranges~n"),
     io:format("1"),
@@ -734,7 +773,7 @@ test_range() ->
     io:format(".. ok~n"),
 
     Body = <<"012345678901234567890123456789012345678901234567890123456789">>,
-    BodySize = size(Body), %% 60
+    BodySize = byte_size(Body), %% 60
     BodySize = 60,
 
     %% these values assume BodySize =:= 60

http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/11513d3e/mochiweb_skel.erl
----------------------------------------------------------------------
diff --git a/mochiweb_skel.erl b/mochiweb_skel.erl
index 098951b..36b48be 100644
--- a/mochiweb_skel.erl
+++ b/mochiweb_skel.erl
@@ -29,7 +29,8 @@ skel() ->
     "skel".
 
 skelcopy(Src, DestDir, Name, LDst) ->
-    {ok, Dest, _} = regexp:gsub(filename:basename(Src), skel(), Name),
+    Dest = re:replace(filename:basename(Src), skel(), Name,
+                      [global, {return, list}]),
     case file:read_file_info(Src) of
         {ok, #file_info{type=directory, mode=Mode}} ->
             Dir = DestDir ++ "/" ++ Dest,
@@ -50,7 +51,8 @@ skelcopy(Src, DestDir, Name, LDst) ->
         {ok, #file_info{type=regular, mode=Mode}} ->
             OutFile = filename:join(DestDir, Dest),
             {ok, B} = file:read_file(Src),
-            {ok, S, _} = regexp:gsub(binary_to_list(B), skel(), Name),
+            S = re:replace(binary_to_list(B), skel(), Name,
+                           [{return, list}, global]),
             ok = file:write_file(OutFile, list_to_binary(S)),
             ok = file:write_file_info(OutFile, #file_info{mode=Mode}),
             io:format("    ~s~n", [filename:basename(Src)]),

http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/11513d3e/mochiweb_socket_server.erl
----------------------------------------------------------------------
diff --git a/mochiweb_socket_server.erl b/mochiweb_socket_server.erl
index a483c3d..7aafe29 100644
--- a/mochiweb_socket_server.erl
+++ b/mochiweb_socket_server.erl
@@ -22,7 +22,7 @@
          ip=any,
          listen=null,
          acceptor=null,
-         backlog=30}).
+         backlog=128}).
 
 start(State=#mochiweb_socket_server{}) ->
     start_server(State);

http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/11513d3e/mochiweb_util.erl
----------------------------------------------------------------------
diff --git a/mochiweb_util.erl b/mochiweb_util.erl
index 7bf18d1..73cacea 100644
--- a/mochiweb_util.erl
+++ b/mochiweb_util.erl
@@ -78,7 +78,7 @@ safe_relative_path("", Acc) ->
         [] ->
             "";
         _ ->
-            join(lists:reverse(Acc), "/")
+            string:join(lists:reverse(Acc), "/")
     end;
 safe_relative_path(P, Acc) ->
     case partition(P, "/") of
@@ -423,9 +423,7 @@ record_to_proplist(Record, Fields) ->
 %%      Fields should be obtained by calling record_info(fields, record_type)
 %%      where record_type is the record type of Record
 record_to_proplist(Record, Fields, TypeKey)
-  when is_tuple(Record),
-       is_list(Fields),
-       size(Record) - 1 =:= length(Fields) ->
+  when tuple_size(Record) - 1 =:= length(Fields) ->
     lists:zip([TypeKey | Fields], tuple_to_list(Record)).
 
 

http://git-wip-us.apache.org/repos/asf/couchdb-mochiweb/blob/11513d3e/reloader.erl
----------------------------------------------------------------------
diff --git a/reloader.erl b/reloader.erl
index 2ff154b..6835f8f 100644
--- a/reloader.erl
+++ b/reloader.erl
@@ -78,8 +78,7 @@ code_change(_Vsn, State, _Extra) ->
 
 doit(From, To) ->
     [case file:read_file_info(Filename) of
-         {ok, FileInfo} when FileInfo#file_info.mtime >= From,
-                             FileInfo#file_info.mtime < To ->
+         {ok, #file_info{mtime = Mtime}} when Mtime >= From, Mtime < To ->
              reload(Module);
          {ok, _} ->
              unmodified;