You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by to...@apache.org on 2016/04/22 08:02:17 UTC

[1/5] chttpd commit: updated refs/heads/2992-limit-doc-size to 1ac365d

Repository: couchdb-chttpd
Updated Branches:
  refs/heads/2992-limit-doc-size f7affd0d2 -> 1ac365dd8


Move max_document_size logic to update only

COUCHDB-2992


Project: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/commit/03fdcde5
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/tree/03fdcde5
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/diff/03fdcde5

Branch: refs/heads/2992-limit-doc-size
Commit: 03fdcde571585f17c2077a3a0313bc8703e32197
Parents: f7affd0
Author: Tony Sun <to...@cloudant.com>
Authored: Tue Apr 19 18:41:32 2016 -0700
Committer: Tony Sun <to...@cloudant.com>
Committed: Tue Apr 19 18:41:32 2016 -0700

----------------------------------------------------------------------
 include/chttpd.hrl               |  3 ++
 src/chttpd.erl                   |  9 ++---
 src/chttpd_db.erl                | 65 ++++++++++++++++++++---------------
 src/chttpd_external.erl          |  4 +--
 test/chttpd_db_doc_size_test.erl |  6 ++--
 5 files changed, 50 insertions(+), 37 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/blob/03fdcde5/include/chttpd.hrl
----------------------------------------------------------------------
diff --git a/include/chttpd.hrl b/include/chttpd.hrl
index a7f9aaa..d26a2ee 100644
--- a/include/chttpd.hrl
+++ b/include/chttpd.hrl
@@ -26,3 +26,6 @@
     (C >= $a andalso C =< $f) orelse
     (C >= $A andalso C =< $F)
 )).
+
+% For any PUT/POST request, max size will be 4 GB (semi-unlimited)
+-define(DEFAULT_RECV_BODY, 4294967296).

http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/blob/03fdcde5/src/chttpd.erl
----------------------------------------------------------------------
diff --git a/src/chttpd.erl b/src/chttpd.erl
index 1f8c160..ef1c140 100644
--- a/src/chttpd.erl
+++ b/src/chttpd.erl
@@ -599,12 +599,9 @@ body_length(#httpd{mochi_req=MochiReq}) ->
 body(#httpd{mochi_req=MochiReq, req_body=ReqBody}) ->
     case ReqBody of
         undefined ->
-            % Maximum size of document PUT request body (4GB)
-            MaxSize = list_to_integer(
-                config:get("couchdb", "max_document_size", "4294967296")),
             Begin = os:timestamp(),
             try
-                MochiReq:recv_body(MaxSize)
+                MochiReq:recv_body(?DEFAULT_RECV_BODY)
             after
                 T = timer:now_diff(os:timestamp(), Begin) div 1000,
                 put(body_time, T)
@@ -858,9 +855,9 @@ error_info({error, {database_name_too_long, DbName}}) ->
         <<"At least one path segment of `", DbName/binary, "` is too long.">>};
 error_info({missing_stub, Reason}) ->
     {412, <<"missing_stub">>, Reason};
-error_info({single_doc_too_large, MaxSize}) ->
+error_info({request_entity_too_large, MaxSize}) ->
     SizeBin = list_to_binary(integer_to_list(MaxSize)),
-    {413, <<"too_large">>, <<"Document exceeded single_max_doc_size:",
+    {413, <<"too_large">>, <<"Document exceeded max_document_size:",
         SizeBin/binary>>};
 error_info(request_entity_too_large) ->
     {413, <<"too_large">>, <<"the request entity is too large">>};

http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/blob/03fdcde5/src/chttpd_db.erl
----------------------------------------------------------------------
diff --git a/src/chttpd_db.erl b/src/chttpd_db.erl
index 2a3fe1b..fdfd03d 100644
--- a/src/chttpd_db.erl
+++ b/src/chttpd_db.erl
@@ -13,6 +13,7 @@
 -module(chttpd_db).
 -include_lib("couch/include/couch_db.hrl").
 -include_lib("couch_mrview/include/couch_mrview.hrl").
+-include_lib("chttpd/include/chttpd.hrl").
 
 -export([handle_request/1, handle_compact_req/2, handle_design_req/2,
     db_req/2, couch_doc_open/4,handle_changes_req/2,
@@ -309,7 +310,7 @@ db_req(#httpd{method='POST', path_parts=[DbName], user_ctx=Ctx}=Req, Db) ->
     Options = [{user_ctx,Ctx}, {w,W}],
 
     Body = chttpd:json_body(Req),
-    ok = verify_doc_size(Body),
+    ok = maybe_verify_body_size(Body),
 
     Doc = couch_doc:from_json_obj(Body),
     Doc2 = case Doc#doc.id of
@@ -377,7 +378,7 @@ db_req(#httpd{method='POST',path_parts=[_,<<"_bulk_docs">>], user_ctx=Ctx}=Req,
     DocsArray0 ->
         DocsArray0
     end,
-    {ExceedErrs, DocsArray1} = remove_exceed_docs(DocsArray),
+    {ExceedErrs, DocsArray1} = maybe_remove_exceed_docs(DocsArray),
     couch_stats:update_histogram([couchdb, httpd, bulk_docs], length(DocsArray)),
     W = case couch_util:get_value(<<"w">>, JsonProps) of
     Value when is_integer(Value) ->
@@ -769,7 +770,7 @@ db_doc_req(#httpd{method='PUT', user_ctx=Ctx}=Req, Db, DocId) ->
         "ok" ->
             % batch
             Body = chttpd:json_body(Req),
-            ok = verify_doc_size(Body),
+            ok = maybe_verify_body_size(Body),
             Doc = couch_doc_from_req(Req, DocId, Body),
 
             spawn(fun() ->
@@ -787,7 +788,7 @@ db_doc_req(#httpd{method='PUT', user_ctx=Ctx}=Req, Db, DocId) ->
         _Normal ->
             % normal
             Body = chttpd:json_body(Req),
-            ok = verify_doc_size(Body),
+            ok = maybe_verify_body_size(Body),
             Doc = couch_doc_from_req(Req, DocId, Body),
             send_updated_doc(Req, Db, DocId, Doc, RespHeaders, UpdateType)
         end
@@ -1634,33 +1635,43 @@ bulk_get_open_doc_revs1(Db, Props, _, {DocId, Revs, Options}) ->
             {DocId, Results, Options}
     end.
 
-remove_exceed_docs(DocArray0) ->
-    MaxSize = list_to_integer(config:get("couchdb",
-        "single_max_doc_size", "1048576")),
-    {ExceedDocs, DocsArray} = lists:splitwith(fun (Doc) ->
-        exceed_doc_size(Doc, MaxSize)
-    end, DocArray0),
-    ExceedErrs = lists:map (fun ({Doc}) ->
-        DocId = case couch_util:get_value(<<"_id">>, Doc) of
-            undefined -> couch_uuids:new();
-            Id0 -> Id0
-        end,
-        Reason = lists:concat(["Document exceeded single_max_doc_size:",
-            " of ", MaxSize, " bytes"]),
-        {[{id, DocId}, {error, <<"too_large">>},
-            {reason, ?l2b(Reason)}]}
-    end, ExceedDocs),
-    {ExceedErrs, DocsArray}.
+maybe_remove_exceed_docs(DocArray0) ->
+    case config:get_boolean("couchdb", "use_max_document_size", false) of
+        true ->
+            Max = config:get_integer("couchdb", "max_document_size",
+                16777216),
+            {ExceedDocs, DocsArray} = lists:splitwith(fun (Doc) ->
+                exceed_doc_size(Doc, Max)
+            end, DocArray0),
+            ExceedErrs = lists:map (fun ({Doc}) ->
+            DocId = case couch_util:get_value(<<"_id">>, Doc) of
+                undefined -> <<"no id generated since update failed">>;
+                Id0 -> Id0
+            end,
+            Reason = lists:concat(["Document exceeded max_document_size:",
+                " of ", Max, " bytes"]),
+            {[{id, DocId}, {error, <<"request_entity_too_large">>},
+                {reason, ?l2b(Reason)}]}
+            end, ExceedDocs),
+            {ExceedErrs, DocsArray};
+        false ->
+            {[], DocArray0}
+    end.
 
 exceed_doc_size(JsonBody, MaxSize) ->
     size(term_to_binary(JsonBody)) > MaxSize.
 
-verify_doc_size(JsonBody) ->
-    SMaxSize = list_to_integer(config:get("couchdb",
-        "single_max_doc_size", "1048576")),
-    case exceed_doc_size(JsonBody, SMaxSize) of
-        true -> throw({single_doc_too_large, SMaxSize});
-        false -> ok
+maybe_verify_body_size(JsonBody) ->
+    case config:get_boolean("couchdb", "use_max_document_size", false) of
+        true ->
+            Max = config:get_integer("couchdb", "max_document_size",
+                16777216),
+            case exceed_doc_size(JsonBody, Max) of
+                true -> throw({request_entity_too_large, Max});
+                false -> ok
+            end;
+        false ->
+            ok
     end.
 
 parse_field(<<"id">>, undefined) ->

http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/blob/03fdcde5/src/chttpd_external.erl
----------------------------------------------------------------------
diff --git a/src/chttpd_external.erl b/src/chttpd_external.erl
index f9bebed..e8efff2 100644
--- a/src/chttpd_external.erl
+++ b/src/chttpd_external.erl
@@ -20,6 +20,7 @@
 -import(chttpd,[send_error/4]).
 
 -include_lib("couch/include/couch_db.hrl").
+-include_lib("chttpd/include/chttpd.hrl").
 
 % handle_external_req/2
 % for the old type of config usage:
@@ -93,8 +94,7 @@ json_req_obj_field(<<"headers">>, #httpd{mochi_req=Req}, _Db, _DocId) ->
     Hlist = mochiweb_headers:to_list(Headers),
     to_json_terms(Hlist);
 json_req_obj_field(<<"body">>, #httpd{req_body=undefined, mochi_req=Req}, _Db, _DocId) ->
-    MaxSize = config:get_integer("couchdb", "max_document_size", 4294967296),
-    Req:recv_body(MaxSize);
+    Req:recv_body(?DEFAULT_RECV_BODY);
 json_req_obj_field(<<"body">>, #httpd{req_body=Body}, _Db, _DocId) ->
     Body;
 json_req_obj_field(<<"peer">>, #httpd{mochi_req=Req}, _Db, _DocId) ->

http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/blob/03fdcde5/test/chttpd_db_doc_size_test.erl
----------------------------------------------------------------------
diff --git a/test/chttpd_db_doc_size_test.erl b/test/chttpd_db_doc_size_test.erl
index 02ae3cd..ce29c17 100644
--- a/test/chttpd_db_doc_size_test.erl
+++ b/test/chttpd_db_doc_size_test.erl
@@ -22,7 +22,8 @@
 
 setup() ->
     ok = config:set("admins", ?USER, ?PASS, _Persist=false),
-    ok = config:set("couchdb", "single_max_doc_size", "50"),
+    ok = config:set("couchdb", "max_document_size", "50"),
+    ok = config:set("couchdb", "use_max_document_size", "true"),
     TmpDb = ?tempdb(),
     Addr = config:get("chttpd", "bind_address", "127.0.0.1"),
     Port = mochiweb_socket_server:get(chttpd, port),
@@ -33,7 +34,8 @@ setup() ->
 teardown(Url) ->
     delete_db(Url),
     ok = config:delete("admins", ?USER, _Persist=false),
-    ok = config:delete("couchdb", "single_max_doc_size").
+    ok = config:delete("couchdb", "max_document_size"),
+    ok = config:delete("couchdb", "use_max_document_size").
 
 create_db(Url) ->
     {ok, Status, _, _} = test_request:put(Url, [?CONTENT_JSON, ?AUTH], "{}"),


[3/5] chttpd commit: updated refs/heads/2992-limit-doc-size to 1ac365d

Posted by to...@apache.org.
Add in check for body size in multi_part requests

COUCHDB-2992


Project: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/commit/8ddb1ce7
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/tree/8ddb1ce7
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/diff/8ddb1ce7

Branch: refs/heads/2992-limit-doc-size
Commit: 8ddb1ce7c6bd01511daea3e0d05c5d2edf9610ba
Parents: b0e5493
Author: Tony Sun <to...@cloudant.com>
Authored: Thu Apr 21 18:42:51 2016 -0700
Committer: Tony Sun <to...@cloudant.com>
Committed: Thu Apr 21 18:42:51 2016 -0700

----------------------------------------------------------------------
 src/chttpd_db.erl | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/blob/8ddb1ce7/src/chttpd_db.erl
----------------------------------------------------------------------
diff --git a/src/chttpd_db.erl b/src/chttpd_db.erl
index d30a89b..2f6e0c0 100644
--- a/src/chttpd_db.erl
+++ b/src/chttpd_db.erl
@@ -698,7 +698,8 @@ db_doc_req(#httpd{method='POST', user_ctx=Ctx}=Req, Db, DocId) ->
     case proplists:is_defined("_doc", Form) of
     true ->
         Json = ?JSON_DECODE(couch_util:get_value("_doc", Form)),
-        Doc = couch_doc_from_req(Req, DocId, Json);
+        Doc = couch_doc_from_req(Req, DocId, Json),
+        ok = maybe_verify_body_size(Doc#doc.body);
     false ->
         Rev = couch_doc:parse_rev(list_to_binary(couch_util:get_value("_rev", Form))),
         {ok, [{ok, Doc}]} = fabric:open_revs(Db, DocId, [Rev], [])
@@ -754,6 +755,7 @@ db_doc_req(#httpd{method='PUT', user_ctx=Ctx}=Req, Db, DocId) ->
         {ok, Doc0, WaitFun, Parser} = couch_doc:doc_from_multi_part_stream(ContentType,
                 fun() -> receive_request_data(Req) end),
         Doc = couch_doc_from_req(Req, DocId, Doc0),
+        ok = maybe_verify_body_size(Doc#doc.body),
         try
             Result = send_updated_doc(Req, Db, DocId, Doc, RespHeaders, UpdateType),
             WaitFun(),


[4/5] chttpd commit: updated refs/heads/2992-limit-doc-size to 1ac365d

Posted by to...@apache.org.
Add check for _update handlers

COUCHDB-2992


Project: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/commit/0b0a3aca
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/tree/0b0a3aca
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/diff/0b0a3aca

Branch: refs/heads/2992-limit-doc-size
Commit: 0b0a3acaad204d00ea024f15c21dbdf2f01bae38
Parents: 8ddb1ce
Author: Tony Sun <to...@cloudant.com>
Authored: Thu Apr 21 18:46:07 2016 -0700
Committer: Tony Sun <to...@cloudant.com>
Committed: Thu Apr 21 18:46:07 2016 -0700

----------------------------------------------------------------------
 src/chttpd_db.erl   | 2 +-
 src/chttpd_show.erl | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/blob/0b0a3aca/src/chttpd_db.erl
----------------------------------------------------------------------
diff --git a/src/chttpd_db.erl b/src/chttpd_db.erl
index 2f6e0c0..03a0b65 100644
--- a/src/chttpd_db.erl
+++ b/src/chttpd_db.erl
@@ -19,7 +19,7 @@
     db_req/2, couch_doc_open/4,handle_changes_req/2,
     update_doc_result_to_json/1, update_doc_result_to_json/2,
     handle_design_info_req/3, handle_view_cleanup_req/2,
-    update_doc/4, http_code_from_status/1]).
+    update_doc/4, http_code_from_status/1, maybe_verify_body_size/1]).
 
 -import(chttpd,
     [send_json/2,send_json/3,send_json/4,send_method_not_allowed/2,

http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/blob/0b0a3aca/src/chttpd_show.erl
----------------------------------------------------------------------
diff --git a/src/chttpd_show.erl b/src/chttpd_show.erl
index 787df16..4bb9335 100644
--- a/src/chttpd_show.erl
+++ b/src/chttpd_show.erl
@@ -130,6 +130,7 @@ send_doc_update_response(Req, Db, DDoc, UpdateName, Doc, DocId) ->
                 Options = [{user_ctx, Req#httpd.user_ctx}, {w, W}]
             end,
             NewDoc = couch_doc:from_json_obj({NewJsonDoc}),
+            ok = chttpd_db:maybe_verify_body_size(NewDoc#doc.body),
             couch_doc:validate_docid(NewDoc#doc.id),
             {UpdateResult, NewRev} = fabric:update_doc(Db, NewDoc, Options),
             NewRevStr = couch_doc:rev_to_str(NewRev),


[2/5] chttpd commit: updated refs/heads/2992-limit-doc-size to 1ac365d

Posted by to...@apache.org.
Use doc body after parsing

We use the #doc.body because we only care about the json body. Inline
attachments are already stripped away

COUCHDB-2992


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

Branch: refs/heads/2992-limit-doc-size
Commit: b0e54930ad341b7385f48d8f06a9455372115e98
Parents: 03fdcde
Author: Tony Sun <to...@cloudant.com>
Authored: Wed Apr 20 15:38:49 2016 -0700
Committer: Tony Sun <to...@cloudant.com>
Committed: Wed Apr 20 15:38:49 2016 -0700

----------------------------------------------------------------------
 src/chttpd_db.erl | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/blob/b0e54930/src/chttpd_db.erl
----------------------------------------------------------------------
diff --git a/src/chttpd_db.erl b/src/chttpd_db.erl
index fdfd03d..d30a89b 100644
--- a/src/chttpd_db.erl
+++ b/src/chttpd_db.erl
@@ -309,10 +309,8 @@ db_req(#httpd{method='POST', path_parts=[DbName], user_ctx=Ctx}=Req, Db) ->
     W = chttpd:qs_value(Req, "w", integer_to_list(mem3:quorum(Db))),
     Options = [{user_ctx,Ctx}, {w,W}],
 
-    Body = chttpd:json_body(Req),
-    ok = maybe_verify_body_size(Body),
-
-    Doc = couch_doc:from_json_obj(Body),
+    Doc = couch_doc:from_json_obj(chttpd:json_body(Req)),
+    ok = maybe_verify_body_size(Doc#doc.body),
     Doc2 = case Doc#doc.id of
         <<"">> ->
             Doc#doc{id=couch_uuids:new(), revs={0, []}};
@@ -769,10 +767,8 @@ db_doc_req(#httpd{method='PUT', user_ctx=Ctx}=Req, Db, DocId) ->
         case chttpd:qs_value(Req, "batch") of
         "ok" ->
             % batch
-            Body = chttpd:json_body(Req),
-            ok = maybe_verify_body_size(Body),
-            Doc = couch_doc_from_req(Req, DocId, Body),
-
+            Doc = couch_doc_from_req(Req, DocId, chttpd:json_body(Req)),
+            ok = maybe_verify_body_size(Doc#doc.body),
             spawn(fun() ->
                     case catch(fabric:update_doc(Db, Doc, Options)) of
                     {ok, _} -> ok;
@@ -788,8 +784,8 @@ db_doc_req(#httpd{method='PUT', user_ctx=Ctx}=Req, Db, DocId) ->
         _Normal ->
             % normal
             Body = chttpd:json_body(Req),
-            ok = maybe_verify_body_size(Body),
             Doc = couch_doc_from_req(Req, DocId, Body),
+            ok = maybe_verify_body_size(Doc#doc.body),
             send_updated_doc(Req, Db, DocId, Doc, RespHeaders, UpdateType)
         end
     end;


[5/5] chttpd commit: updated refs/heads/2992-limit-doc-size to 1ac365d

Posted by to...@apache.org.
Add more tests

COUCHDB-2992


Project: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/repo
Commit: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/commit/1ac365dd
Tree: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/tree/1ac365dd
Diff: http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/diff/1ac365dd

Branch: refs/heads/2992-limit-doc-size
Commit: 1ac365dd88d5c953ea739b5b11ebb857038fc7d5
Parents: 0b0a3ac
Author: Tony Sun <to...@cloudant.com>
Authored: Thu Apr 21 23:03:01 2016 -0700
Committer: Tony Sun <to...@cloudant.com>
Committed: Thu Apr 21 23:03:01 2016 -0700

----------------------------------------------------------------------
 test/chttpd_db_doc_size_test.erl | 84 ++++++++++++++++++++++++++++++++++-
 1 file changed, 82 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/couchdb-chttpd/blob/1ac365dd/test/chttpd_db_doc_size_test.erl
----------------------------------------------------------------------
diff --git a/test/chttpd_db_doc_size_test.erl b/test/chttpd_db_doc_size_test.erl
index ce29c17..a0fa76e 100644
--- a/test/chttpd_db_doc_size_test.erl
+++ b/test/chttpd_db_doc_size_test.erl
@@ -19,6 +19,11 @@
 -define(PASS, "pass").
 -define(AUTH, {basic_auth, {?USER, ?PASS}}).
 -define(CONTENT_JSON, {"Content-Type", "application/json"}).
+-define(CONTENT_MULTI_RELATED, {"Content-Type",
+    "multipart/related;boundary=\"bound\""}).
+-define(CONTENT_MULTI_FORM, {"Content-Type",
+    "multipart/form-data;boundary=\"bound\""}).
+
 
 setup() ->
     ok = config:set("admins", ?USER, ?PASS, _Persist=false),
@@ -46,7 +51,7 @@ delete_db(Url) ->
 
 all_test_() ->
     {
-        "chttpd db single doc max size test",
+        "chttpd db max_document_size tests",
         {
             setup,
             fun chttpd_test_util:start_couch/0, fun chttpd_test_util:stop_couch/1,
@@ -56,7 +61,10 @@ all_test_() ->
                 [
                     fun post_single_doc/1,
                     fun put_single_doc/1,
-                    fun bulk_doc/1
+                    fun bulk_doc/1,
+                    fun put_post_doc_attach_inline/1,
+                    fun put_multi_part_related/1,
+                    fun post_multi_part_form/1
                 ]
             }
         }
@@ -96,3 +104,75 @@ bulk_doc(Url) ->
     Msg = couch_util:get_value(<<"error">>, InnerJson2),
     ?_assertEqual(<<"too_large">>, Error),
     ?_assertEqual(undefined, Msg).
+
+put_post_doc_attach_inline(Url) ->
+    Body1 = "{\"body\":\"This is a body.\",",
+    Body2 = lists:concat(["{\"body\":\"This is a body it should fail",
+        "because there are too many characters.\","]),
+    DocRest =  lists:concat(["\"_attachments\":{\"foo.txt\":{",
+        "\"content_type\":\"text/plain\",",
+        "\"data\": \"VGhpcyBpcyBhIGJhc2U2NCBlbmNvZGVkIHRleHQ=\"}}}"]),
+    Doc1 = lists:concat([Body1, DocRest]),
+    Doc2 = lists:concat([Body2, DocRest]),
+
+    {ok, _, _, ResultBody} = test_request:post(Url,
+        [?CONTENT_JSON, ?AUTH], Doc1),
+    {Msg} = ?JSON_DECODE(ResultBody),
+    ?_assertEqual({<<"ok">>, true}, lists:nth(1, Msg)),
+       {ok, _, _, ResultBody1} = test_request:post(Url,
+        [?CONTENT_JSON, ?AUTH], Doc2),
+    {Msg1} = ?JSON_DECODE(ResultBody1),
+    ?_assertEqual({<<"error">>, <<"too_large">>}, lists:nth(1, Msg1)),
+
+    {ok, _, _, ResultBody2} = test_request:put(Url ++ "/" ++ "accept",
+        [?CONTENT_JSON, ?AUTH], Doc1),
+    {Msg2} = ?JSON_DECODE(ResultBody2),
+    ?_assertEqual({<<"ok">>, true}, lists:nth(1, Msg2)),
+    {ok, _, _, ResultBody3} = test_request:put(Url ++ "/" ++ "fail",
+        [?CONTENT_JSON, ?AUTH], Doc2),
+    {Msg3} = ?JSON_DECODE(ResultBody3),
+    ?_assertEqual({<<"error">>, <<"too_large">>}, lists:nth(1, Msg3)).
+
+put_multi_part_related(Url) ->
+    Body1 = "{\"body\":\"This is a body.\",",
+    Body2 = lists:concat(["{\"body\":\"This is a body it should fail",
+        "because there are too many characters.\","]),
+    DocBeg = "--bound\r\nContent-Type: application/json\r\n\r\n",
+    DocRest =  lists:concat(["\"_attachments\":{\"foo.txt\":{\"follows\":true,",
+        "\"content_type\":\"text/plain\",\"length\":21},\"bar.txt\":",
+        "{\"follows\":true,\"content_type\":\"text/plain\",",
+        "\"length\":20}}}\r\n--bound\r\n\r\nthis is 21 chars long",
+        "\r\n--bound\r\n\r\nthis is 20 chars lon\r\n--bound--epilogue"]),
+    Doc1 = lists:concat([DocBeg, Body1, DocRest]),
+    Doc2 = lists:concat([DocBeg, Body2, DocRest]),
+    {ok, _, _, ResultBody} = test_request:put(Url ++ "/" ++ "accept",
+        [?CONTENT_MULTI_RELATED, ?AUTH], Doc1),
+    {Msg} = ?JSON_DECODE(ResultBody),
+    ?_assertEqual({<<"ok">>, true}, lists:nth(1, Msg)),
+       {ok, _, _, ResultBody1} = test_request:put(Url ++ "/" ++ "faildoc",
+        [?CONTENT_MULTI_RELATED, ?AUTH], Doc2),
+    {Msg1} = ?JSON_DECODE(ResultBody1),
+    ?_assertEqual({<<"error">>, <<"too_large">>}, lists:nth(1, Msg1)).
+
+post_multi_part_form(Url) ->
+    Port = mochiweb_socket_server:get(chttpd, port),
+    Host = lists:concat([ "http://127.0.0.1:", Port]),
+    Referer = {"Referer", Host},
+    Body1 = "{\"body\":\"This is a body.\"}",
+    Body2 = lists:concat(["{\"body\":\"This is a body it should fail",
+        "because there are too many characters.\"}"]),
+    DocBeg = "--bound\r\nContent-Disposition: form-data; name=\"_doc\"\r\n\r\n",
+    DocRest = lists:concat(["\r\n--bound\r\nContent-Disposition:",
+        "form-data; name=\"_attachments\"; filename=\"file.txt\"\r\n",
+        "Content-Type: text/plain\r\n\r\ncontents of file.txt\r\n\r\n",
+        "--bound--"]),
+    Doc1 = lists:concat([DocBeg, Body1, DocRest]),
+    Doc2 = lists:concat([DocBeg, Body2, DocRest]),
+    {ok, _, _, ResultBody} = test_request:post(Url ++ "/" ++ "accept",
+        [?CONTENT_MULTI_FORM, ?AUTH, Referer], Doc1),
+    {Msg} = ?JSON_DECODE(ResultBody),
+    ?_assertEqual({<<"ok">>, true}, lists:nth(1, Msg)),
+    {ok, _, _, ResultBody1} = test_request:post(Url ++ "/" ++ "fail",
+        [?CONTENT_MULTI_FORM, ?AUTH, Referer], Doc2),
+    {Msg1} = ?JSON_DECODE(ResultBody1),
+    ?_assertEqual({<<"error">>, <<"too_large">>}, lists:nth(1, Msg1)).