Skip to content

Commit

Permalink
Better total (body + attachments) size checking for documents
Browse files Browse the repository at this point in the history
Use the already computed (conservative) body size.

Switch multipart length calcuation to accept body and boundary sizes.

Issue apache#1200
Issue apache#1253
  • Loading branch information
nickva committed Jul 13, 2018
1 parent e6ae447 commit e6399a6
Show file tree
Hide file tree
Showing 2 changed files with 21 additions and 14 deletions.
20 changes: 13 additions & 7 deletions src/couch/src/couch_doc.erl
Original file line number Diff line number Diff line change
Expand Up @@ -133,10 +133,11 @@ from_json_obj_validate(EJson) ->
from_json_obj_validate(EJson, DbName) ->
MaxSize = config:get_integer("couchdb", "max_document_size", 4294967296),
Doc = from_json_obj(EJson, DbName),
case couch_ejson_size:encoded_size(Doc#doc.body) =< MaxSize of
BodySize = couch_ejson_size:encoded_size(Doc#doc.body),
case BodySize =< MaxSize of
true ->
validate_attachment_sizes(Doc#doc.atts),
validate_total_document_size(Doc),
validate_total_document_size(Doc, BodySize),
Doc;
false ->
throw({request_entity_too_large, Doc#doc.id})
Expand All @@ -145,12 +146,11 @@ from_json_obj_validate(EJson, DbName) ->

% sum up the json body size + attachment body size and
% make sure it is < max_http_request_size
validate_total_document_size(#doc{id=DocId, body=Body, atts=Atts0}=Doc) ->
validate_total_document_size(#doc{id=DocId, atts=Atts0}=Doc, BodySize) ->
MaxReqSize = config:get_integer("httpd", "max_http_request_size", 4294967296), % 4 GB
Boundary = <<"00000000000000000000000000000000">>,
Atts = lists:map(fun couch_att:to_tuple/1, Atts0),
{_, DocSum} = couch_httpd_multipart:length_multipart_stream(Boundary,
?JSON_ENCODE(Body), Atts),
{_, DocSum} = couch_httpd_multipart:length_multipart_stream(32, BodySize,
Atts),
case DocSum =< MaxReqSize of
true -> ok;
false -> throw({request_entity_too_large, DocId})
Expand Down Expand Up @@ -436,7 +436,13 @@ merge_stubs(#doc{id=Id,atts=MemBins}=StubsDoc, #doc{atts=DiskBins}) ->
len_doc_to_multi_part_stream(Boundary, JsonBytes, Atts, SendEncodedAtts) ->
AttsToInclude = lists:filter(fun(Att) -> not couch_att:is_stub(Att) end, Atts),
AttsDecoded = decode_attributes(AttsToInclude, SendEncodedAtts),
couch_httpd_multipart:length_multipart_stream(Boundary, JsonBytes, AttsDecoded).
case couch_httpd_multipart:length_multipart_stream(byte_size(Boundary),
iolist_size(JsonBytes), AttsDecoded) of
{json, Len} ->
{<<"application/json">>, Len};
{multipart, Len} ->
{<<"multipart/related; boundary=\"", Boundary/binary, "\"">>, Len}
end.


doc_to_multi_part_stream(Boundary, JsonBytes, Atts, WriteFun,
Expand Down
15 changes: 8 additions & 7 deletions src/couch/src/couch_httpd_multipart.erl
Original file line number Diff line number Diff line change
Expand Up @@ -262,14 +262,15 @@ atts_to_mp([{Att, Name, Len, Type, Encoding} | RestAtts], Boundary, WriteFun,
WriteFun(<<"\r\n--", Boundary/binary>>),
atts_to_mp(RestAtts, Boundary, WriteFun, AttFun).

length_multipart_stream(Boundary, JsonBytes, Atts) ->
length_multipart_stream(BoundarySize, JsonByteSize, Atts) when
is_integer(BoundarySize), is_integer(JsonByteSize) ->
AttsSize = lists:foldl(fun({_Att, Name, Len, Type, Encoding}, AccAttsSize) ->
AccAttsSize +
4 + % "\r\n\r\n"
length(integer_to_list(Len)) +
Len +
4 + % "\r\n--"
size(Boundary) +
BoundarySize +
% attachment headers
% (the length of the Content-Length has already been set)
size(Name) +
Expand All @@ -286,15 +287,15 @@ length_multipart_stream(Boundary, JsonBytes, Atts) ->
end
end, 0, Atts),
if AttsSize == 0 ->
{<<"application/json">>, iolist_size(JsonBytes)};
{json, JsonByteSize};
true ->
{<<"multipart/related; boundary=\"", Boundary/binary, "\"">>,
{multipart,
2 + % "--"
size(Boundary) +
BoundarySize +
36 + % "\r\ncontent-type: application/json\r\n\r\n"
iolist_size(JsonBytes) +
JsonByteSize +
4 + % "\r\n--"
size(Boundary) +
BoundarySize +
+ AttsSize +
2 % "--"
}
Expand Down

0 comments on commit e6399a6

Please sign in to comment.