Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
Browse files
Allow limiting maximum document body size
Configuration is via the `couchdb.max_document_size`. In the past that
was implemented as a maximum http request body size and this finally implements
it by actually checking a document's body size.

COUCHDB-2992
  • Loading branch information
nickva committed Mar 16, 2017
1 parent 2d984b5 commit 7e48bda4459cc8e4dbb8bd86966792f533571d83
Showing 3 changed files with 34 additions and 9 deletions.
@@ -13,7 +13,7 @@
-module(couch_doc).

-export([to_doc_info/1,to_doc_info_path/1,parse_rev/1,parse_revs/1,rev_to_str/1,revs_to_strs/1]).
-export([from_json_obj/1,to_json_obj/2,has_stubs/1, merge_stubs/2]).
-export([from_json_obj/1, from_json_obj_validate/1, to_json_obj/2,has_stubs/1, merge_stubs/2]).
-export([validate_docid/1, get_validate_doc_fun/1]).
-export([doc_from_multi_part_stream/2, doc_from_multi_part_stream/3]).
-export([doc_to_multi_part_stream/5, len_doc_to_multi_part_stream/4]).
@@ -124,6 +124,16 @@ doc_to_json_obj(#doc{id=Id,deleted=Del,body=Body,revs={Start, RevIds},
++ to_json_attachments(Doc#doc.atts, Options)
}.

from_json_obj_validate(EJson) ->
MaxSize = config:get_integer("couchdb", "max_document_size", 4294967296),
Doc = from_json_obj(EJson),
case erlang:external_size(Doc#doc.body) =< MaxSize of
true ->
Doc;
false ->
throw({request_entity_too_large, Doc#doc.id})
end.

from_json_obj({Props}) ->
transfer_fields(Props, #doc{body=[]});

@@ -414,7 +424,7 @@ doc_from_multi_part_stream(ContentType, DataFun, Ref) ->
{{started_open_doc_revs, NewRef}, Parser, _ParserRef} ->
restart_open_doc_revs(Parser, Ref, NewRef);
{{doc_bytes, Ref, DocBytes}, Parser, ParserRef} ->
Doc = from_json_obj(?JSON_DECODE(DocBytes)),
Doc = from_json_obj_validate(?JSON_DECODE(DocBytes)),
erlang:put(mochiweb_request_recv, true),
% we'll send the Parser process ID to the remote nodes so they can
% retrieve their own copies of the attachment data
@@ -256,7 +256,7 @@ db_req(#httpd{method='GET',path_parts=[_DbName]}=Req, Db) ->

db_req(#httpd{method='POST',path_parts=[_DbName]}=Req, Db) ->
couch_httpd:validate_ctype(Req, "application/json"),
Doc = couch_doc:from_json_obj(couch_httpd:json_body(Req)),
Doc = couch_doc:from_json_obj_validate(couch_httpd:json_body(Req)),
validate_attachment_names(Doc),
Doc2 = case Doc#doc.id of
<<"">> ->
@@ -319,7 +319,7 @@ db_req(#httpd{method='POST',path_parts=[_,<<"_bulk_docs">>]}=Req, Db) ->
true ->
Docs = lists:map(
fun({ObjProps} = JsonObj) ->
Doc = couch_doc:from_json_obj(JsonObj),
Doc = couch_doc:from_json_obj_validate(JsonObj),
validate_attachment_names(Doc),
Id = case Doc#doc.id of
<<>> -> couch_uuids:new();
@@ -353,7 +353,7 @@ db_req(#httpd{method='POST',path_parts=[_,<<"_bulk_docs">>]}=Req, Db) ->
end;
false ->
Docs = lists:map(fun(JsonObj) ->
Doc = couch_doc:from_json_obj(JsonObj),
Doc = couch_doc:from_json_obj_validate(JsonObj),
validate_attachment_names(Doc),
Doc
end, DocsArray),
@@ -809,7 +809,7 @@ couch_doc_from_req(Req, DocId, #doc{revs=Revs}=Doc) ->
end,
Doc#doc{id=DocId, revs=Revs2};
couch_doc_from_req(Req, DocId, Json) ->
couch_doc_from_req(Req, DocId, couch_doc:from_json_obj(Json)).
couch_doc_from_req(Req, DocId, couch_doc:from_json_obj_validate(Json)).

% Useful for debugging
% couch_doc_open(Db, DocId) ->
@@ -38,6 +38,8 @@ mock(couch_log) ->
ok;
mock(config) ->
meck:new(config, [passthrough]),
meck:expect(config, get_integer,
fun("couchdb", "max_document_size", 4294967296) -> 1024 end),
meck:expect(config, get, fun(_, _) -> undefined end),
meck:expect(config, get, fun(_, _, Default) -> Default end),
ok.
@@ -165,7 +167,7 @@ from_json_success_cases() ->
],
lists:map(
fun({EJson, Expect, Msg}) ->
{Msg, ?_assertMatch(Expect, couch_doc:from_json_obj(EJson))}
{Msg, ?_assertMatch(Expect, couch_doc:from_json_obj_validate(EJson))}
end,
Cases).

@@ -230,16 +232,29 @@ from_json_error_cases() ->
{[{<<"_something">>, 5}]},
{doc_validation, <<"Bad special document member: _something">>},
"Underscore prefix fields are reserved."
},
{
fun() ->
{[
{<<"_id">>, <<"large_doc">>},
{<<"x">> , << <<"x">> || _ <- lists:seq(1,1025) >>}
]}
end,
{request_entity_too_large, <<"large_doc">>},
"Document too large."
}
],

lists:map(fun
({Fun, Expect, Msg}) when is_function(Fun, 0) ->
Error = (catch couch_doc:from_json_obj_validate(Fun())),
{Msg, ?_assertMatch(Expect, Error)};
({EJson, Expect, Msg}) ->
Error = (catch couch_doc:from_json_obj(EJson)),
Error = (catch couch_doc:from_json_obj_validate(EJson)),
{Msg, ?_assertMatch(Expect, Error)};
({EJson, Msg}) ->
try
couch_doc:from_json_obj(EJson),
couch_doc:from_json_obj_validate(EJson),
{"Conversion failed to raise an exception", ?_assert(false)}
catch
_:_ -> {Msg, ?_assert(true)}

0 comments on commit 7e48bda

Please sign in to comment.