diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/couchdb/couch_db.erl | 3 | ||||
-rw-r--r-- | src/couchdb/couch_httpd.erl | 2 | ||||
-rw-r--r-- | src/mochiweb/mochiweb_request.erl | 100 |
3 files changed, 66 insertions, 39 deletions
diff --git a/src/couchdb/couch_db.erl b/src/couchdb/couch_db.erl index e9612b47..ebc11bed 100644 --- a/src/couchdb/couch_db.erl +++ b/src/couchdb/couch_db.erl @@ -441,8 +441,7 @@ doc_flush_binaries(Doc, Fd) -> {ok, StreamPointer} = couch_stream:write(OutputStream, Bin), {Fd, StreamPointer, size(Bin)}; {StreamFun, undefined} when is_function(StreamFun) -> - % we will throw an error if the client - % sends a chunk larger than this size + % max_attachment_chunk_size control the max we buffer in memory MaxChunkSize = list_to_integer(couch_config:get("couchdb", "max_attachment_chunk_size","4294967296")), WriterFun = make_writer_fun(OutputStream), diff --git a/src/couchdb/couch_httpd.erl b/src/couchdb/couch_httpd.erl index 9969177e..5549e40a 100644 --- a/src/couchdb/couch_httpd.erl +++ b/src/couchdb/couch_httpd.erl @@ -264,7 +264,7 @@ recv_chunked(#httpd{mochi_req=MochiReq}, MaxChunkSize, ChunkFun, InitState) -> % Fun is called once with each chunk % Fun({Length, Binary}, State) % called with Length == 0 on the last time. - MochiReq:recv_body(MaxChunkSize, ChunkFun, InitState). + MochiReq:stream_body(MaxChunkSize, ChunkFun, InitState). body(#httpd{mochi_req=MochiReq}) -> % Maximum size of document PUT request body (4GB) diff --git a/src/mochiweb/mochiweb_request.erl b/src/mochiweb/mochiweb_request.erl index 7fc04f8b..f512555f 100644 --- a/src/mochiweb/mochiweb_request.erl +++ b/src/mochiweb/mochiweb_request.erl @@ -12,7 +12,7 @@ -define(READ_SIZE, 8192). -export([get_header_value/1, get_primary_header_value/1, get/1, dump/0]). --export([send/1, recv/1, recv/2, recv_body/0, recv_body/1, recv_body/3]). +-export([send/1, recv/1, recv/2, recv_body/0, recv_body/1, stream_body/3]). -export([start_response/1, start_response_length/1, start_raw_response/1]). -export([respond/1, ok/1]). -export([not_found/0, not_found/1]). @@ -171,39 +171,54 @@ recv_body() -> %% @doc Receive the body of the HTTP request (defined by Content-Length). %% Will receive up to MaxBody bytes. recv_body(MaxBody) -> - recv_body(MaxBody, nil, nil). + % we could use a sane constant for max chunk size + Body = stream_body(?MAX_RECV_BODY, fun + ({0, _ChunkedFooter}, {_LengthAcc, BinAcc}) -> + iolist_to_binary(lists:reverse(BinAcc)); + ({Length, Bin}, {LengthAcc, BinAcc}) -> + NewLength = Length + LengthAcc, + if NewLength > MaxBody -> + exit({body_too_large, chunked}); + true -> + {NewLength, [Bin | BinAcc]} + end + end, {0, []}, ?MAX_RECV_BODY), + put(?SAVE_BODY, Body), + Body. + +stream_body(MaxChunkSize, ChunkFun, FunState) -> + stream_body(MaxChunkSize, ChunkFun, FunState, undefined). + +stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength) -> -recv_body(MaxBody, ChunkFun, ChunkAcc) -> case get_header_value("expect") of "100-continue" -> start_raw_response({100, gb_trees:empty()}); _Else -> ok end, - Body = case body_length() of - undefined -> - undefined; - {unknown_transfer_encoding, Unknown} -> - exit({unknown_transfer_encoding, Unknown}); - chunked -> - case ChunkFun of - nil -> - read_chunked_body(MaxBody); - _StreamFun -> - % In this case the MaxBody is actually used to - % determine the maximum allowed size of a single - % chunk. - stream_chunked_body(MaxBody, ChunkFun, ChunkAcc) - end; - 0 -> - <<>>; - Length when is_integer(Length), Length =< MaxBody -> - recv(Length); - Length -> - exit({body_too_large, Length}) - end, - put(?SAVE_BODY, Body), - Body. + case body_length() of + undefined -> + undefined; + {unknown_transfer_encoding, Unknown} -> + exit({unknown_transfer_encoding, Unknown}); + chunked -> + % In this case the MaxBody is actually used to + % determine the maximum allowed size of a single + % chunk. + stream_chunked_body(MaxChunkSize, ChunkFun, FunState); + 0 -> + <<>>; + Length when is_integer(Length) -> + case MaxBodyLength of + MaxBodyLength when is_integer(MaxBodyLength), MaxBodyLength < Length -> + exit({body_too_large, content_length}); + _ -> + stream_unchunked_body(Length, MaxChunkSize, ChunkFun, FunState) + end; + Length -> + exit({length_not_integer, Length}) + end. %% @spec start_response({integer(), ioheaders()}) -> response() @@ -419,14 +434,6 @@ parse_post() -> Cached end. -read_chunked_body(MaxBufferSize) -> - stream_chunked_body(MaxBufferSize, fun - ({0, _}, Acc) -> - iolist_to_binary(lists:reverse(Acc)); - ({_Length, Bin}, Acc) -> - [Bin | Acc] - end, []). - %% @spec stream_chunked_body(integer(), fun(), term()) -> term() %% @doc The function is called for each chunk. %% Used internally by read_chunked_body. @@ -435,12 +442,25 @@ stream_chunked_body(MaxChunkSize, Fun, FunState) -> 0 -> Fun({0, read_chunk(0)}, FunState); Length when Length > MaxChunkSize -> - exit({body_too_large, chunked}); + NewState = read_sub_chunks(Length, MaxChunkSize, Fun, FunState), + stream_chunked_body(MaxChunkSize, Fun, NewState); Length -> NewState = Fun({Length, read_chunk(Length)}, FunState), stream_chunked_body(MaxChunkSize, Fun, NewState) end. +stream_unchunked_body(0, _MaxChunkSize, Fun, FunState) -> + Fun({0, <<>>}, FunState); +stream_unchunked_body(Length, MaxChunkSize, Fun, FunState) when Length > MaxChunkSize -> + Bin = recv(MaxChunkSize), + NewState = Fun({MaxChunkSize, Bin}, FunState), + stream_unchunked_body(Length - MaxChunkSize, MaxChunkSize, Fun, NewState); +stream_unchunked_body(Length, MaxChunkSize, Fun, FunState) -> + Bin = recv(Length), + NewState = Fun({Length, Bin}, FunState), + stream_unchunked_body(0, MaxChunkSize, Fun, NewState). + + %% @spec read_chunk_length() -> integer() %% @doc Read the length of the next HTTP chunk. read_chunk_length() -> @@ -483,6 +503,14 @@ read_chunk(Length) -> exit(normal) end. +read_sub_chunks(Length, MaxChunkSize, Fun, FunState) when Length > MaxChunkSize -> + Bin = recv(MaxChunkSize), + NewState = Fun({size(Bin), Bin}, FunState), + read_sub_chunks(Length - MaxChunkSize, MaxChunkSize, Fun, NewState); + +read_sub_chunks(Length, _MaxChunkSize, Fun, FunState) -> + Fun({Length, read_chunk(Length)}, FunState). + %% @spec serve_file(Path, DocRoot) -> Response %% @doc Serve a file relative to DocRoot. serve_file(Path, DocRoot) -> |