%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2004-2016. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. %% You may obtain a copy of the License at %% %% http://www.apache.org/licenses/LICENSE-2.0 %% %% Unless required by applicable law or agreed to in writing, software %% distributed under the License is distributed on an "AS IS" BASIS, %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %% See the License for the specific language governing permissions and %% limitations under the License. %% %% %CopyrightEnd% %% %% Description: Implements chunked transfer encoding see RFC2616 section %% 3.6.1 -module(http_chunk). -include("http_internal.hrl"). %% API -export([decode/3, encode/1, encode_last/0, encode_last/1, handle_headers/2]). %% Callback API - used for example if the chunkedbody is received a %% little at a time on a socket. -export([decode_size/1, ignore_extensions/1, decode_data/1, decode_trailer/1]). %%%========================================================================= %%% API %%%========================================================================= %%------------------------------------------------------------------------- %% decode(ChunkedBody, MaxBodySize, MaxHeaderSize) -> %% {ok, {Headers, Body}} | {Module, Function, Args} %% %% Headers = ["Header:Value"] %% ChunkedBody = binary() %% MaxBodySize = integer() %% MaxHeaderSize = integer() %% %% Description: Decodes a body encoded by the chunked transfer %% encoding. If the ChunkedBody is not compleate it returns {Module, %% Function, Args} so that decoding can be continued when more of the %% data has been received by calling Module:Function([NewData | Args]). %% %% Note: In the case of pipelining a call to decode might contain data %% that belongs to the next request/response and will be returned as %% part of the body, hence functions calling http_chunk:decode must %% look at the returned content-length header to make sure that they %% split the actual body and data that possible should be passed along to %% the next pass in the loop. %%------------------------------------------------------------------------- decode(ChunkedBody, MaxBodySize, MaxHeaderSize) -> %% Note decode_size will call decode_data. decode_size([ChunkedBody, <<>>, [], 0, {MaxBodySize, <<>>, 0, MaxHeaderSize}]). %%------------------------------------------------------------------------- %% encode(Chunk) -> EncodedChunk %% %% Chunked = binary() %% EncodedChunk = binary() %% %% Description: Encodes a body part with the chunked transfer encoding. %% Chunks are returned as lists or binaries depending on the %% input format. When sending the data on the both formats %% are accepted. %%------------------------------------------------------------------------- encode(Chunk) when is_binary(Chunk)-> HEXSize = list_to_binary(http_util:integer_to_hexlist(size(Chunk))), <<HEXSize/binary, ?CR, ?LF, Chunk/binary, ?CR, ?LF>>; encode([<<>>]) -> []; encode(Chunk) when is_list(Chunk)-> HEXSize = http_util:integer_to_hexlist(erlang:iolist_size(Chunk)), [HEXSize, ?CR, ?LF, Chunk, ?CR, ?LF]. encode_last() -> <<$0, ?CR, ?LF, ?CR, ?LF >>. encode_last([]) -> encode_last(); encode_last(Trailers0) -> Trailers = list_to_binary(encode_trailers(Trailers0)), <<$0, ?CR, ?LF, Trailers/binary>>. %%------------------------------------------------------------------------- %% handle_headers(HeaderRecord, ChunkedHeaders) -> NewHeaderRecord %% %% HeaderRecord = NewHeaderRecord = #http_request_h{} | #http_response_h{} %% ChunkedHeaders = ["Header:Value"] as returnde by http_chunk:decode/3 %% %% Description: Removes chunked from the header as we now have decode %% the body and adds a content-length header and any other headers %% found in the chunked trail. %%------------------------------------------------------------------------- handle_headers(RequestHeaderRecord = #http_request_h{}, ChunkedHeaders) -> NewHeaders = http_request:headers(ChunkedHeaders, RequestHeaderRecord), TransferEncoding = case NewHeaders#http_request_h.'transfer-encoding' -- "chunked" of "" -> undefined; Other -> Other end, NewHeaders#http_request_h{'transfer-encoding' = TransferEncoding}; handle_headers(ResponseHeaderRecord = #http_response_h{}, ChunkedHeaders) -> NewHeaders = http_response:headers(ChunkedHeaders, ResponseHeaderRecord), TransferEncoding = case NewHeaders#http_response_h.'transfer-encoding' -- "chunked" of "" -> undefined; Other -> Other end, NewHeaders#http_response_h{'transfer-encoding' = TransferEncoding}. %% Functions that may be returned during the decoding process %% if the input data is incompleate. decode_size([Bin, Rest, HexList, AccSize, Info]) -> decode_size(<<Rest/binary, Bin/binary>>, HexList, AccSize, Info). ignore_extensions([Bin, Rest, RemainingSize, TotalMaxHeaderSize, NextFunction]) -> ignore_extensions(<<Rest/binary, Bin/binary>>, RemainingSize, TotalMaxHeaderSize, NextFunction). decode_data([Bin, ChunkSize, TotalChunk, Info]) -> decode_data(ChunkSize, <<TotalChunk/binary, Bin/binary>>, Info). decode_trailer([Bin, Rest, Header, Headers, Body, BodyLength, RemainingSize, TotalMaxHeaderSize]) -> decode_trailer(<<Rest/binary, Bin/binary>>, Header, Headers, Body, BodyLength, RemainingSize, TotalMaxHeaderSize). %%%======================================================================== %%% Internal functions %%%======================================================================== decode_size(_, _, AccHeaderSize, {_,_,_, MaxHeaderSize}) when AccHeaderSize > MaxHeaderSize -> throw({error, {header_too_long, {max, MaxHeaderSize}}}); decode_size(<<>>, HexList, AccHeaderSize, Info) -> {?MODULE, decode_size, [<<>>, HexList, AccHeaderSize, Info]}; decode_size(Data = <<?CR, ?LF, ChunkRest/binary>>, HexList, AccHeaderSize, {MaxBodySize, Body, AccLength, MaxHeaderSize}) -> try http_util:hexlist_to_integer(lists:reverse(string:strip(HexList, left))) of 0 -> % Last chunk, there was no data ignore_extensions(Data, remaing_size(MaxHeaderSize, AccHeaderSize), MaxHeaderSize, {?MODULE, decode_trailer, [<<>>, [],[], Body, integer_to_list(AccLength)]}); ChunkSize -> %% Note decode_data may call decode_size again if there %% is more than one chunk, hence here is where the last parameter %% to this function comes in. decode_data(ChunkSize, ChunkRest, {MaxBodySize, Body, ChunkSize + AccLength, MaxHeaderSize}) catch _:_ -> throw({error, {chunk_size, lists:reverse(HexList)}}) end; decode_size(<<";", Rest/binary>>, HexList, AccHeaderSize, {_,_,_, MaxHeaderSize} = Info) -> %% Note ignore_extensions will call decode_size/1 again when %% it ignored all extensions. ignore_extensions(Rest, remaing_size(MaxHeaderSize, AccHeaderSize), MaxHeaderSize, {?MODULE, decode_size, [<<>>, HexList, AccHeaderSize, Info]}); decode_size(<<?CR>> = Data, HexList, AccHeaderSize, Info) -> {?MODULE, decode_size, [Data, HexList, AccHeaderSize, Info]}; decode_size(<<Octet, Rest/binary>>, HexList, AccHeaderSize, Info) -> decode_size(Rest, [Octet | HexList], AccHeaderSize + 1, Info). %% "All applications MUST ignore chunk-extension extensions they %% do not understand.", see RFC 2616 Section 3.6.1 We don't %% understand any extension... ignore_extensions(_, 0, TotalMaxHeaderSize, _) -> throw({error, {header_too_long, {max, TotalMaxHeaderSize}}}); ignore_extensions(<<>>, RemainingSize, TotalMaxHeaderSize, NextFunction) -> {?MODULE, ignore_extensions, [<<>>, RemainingSize, TotalMaxHeaderSize, NextFunction]}; ignore_extensions(Data = <<?CR, ?LF, _ChunkRest/binary>>, RemainingSize, TotalMaxHeaderSize, {Module, Function, Args}) -> case Function of decode_trailer -> Module:Function([Data | Args ++ [RemainingSize, TotalMaxHeaderSize]]); _ -> Module:Function([Data | Args]) end; ignore_extensions(<<?CR>> = Data, RemainingSize, TotalMaxHeaderSize, NextFunction) -> {?MODULE, ignore_extensions, [Data, RemainingSize, TotalMaxHeaderSize, NextFunction]}; ignore_extensions(<<_Octet, Rest/binary>>, RemainingSize, TotalMaxHeaderSize, NextFunction) -> ignore_extensions(Rest, remaing_size(RemainingSize, 1), TotalMaxHeaderSize, NextFunction). decode_data(ChunkSize, TotalChunk, Info = {MaxBodySize, BodySoFar, AccLength, MaxHeaderSize}) when ChunkSize =< size(TotalChunk) -> case TotalChunk of %% Last chunk <<Data:ChunkSize/binary, ?CR, ?LF, "0", ";">> -> %% Note ignore_extensions will call decode_trailer/1 %% once it ignored all extensions. {?MODULE, ignore_extensions, [<<>>, {?MODULE, decode_trailer, [<<>>, [],[], <<BodySoFar/binary, Data/binary>>, integer_to_list(AccLength)]}]}; <<Data:ChunkSize/binary, ?CR, ?LF, "0", ";", Rest/binary>> -> %% Note ignore_extensions will call decode_trailer/1 %% once it ignored all extensions. ignore_extensions(Rest, MaxHeaderSize, MaxHeaderSize, {?MODULE, decode_trailer, [<<>>, [],[], <<BodySoFar/binary, Data/binary>>, integer_to_list(AccLength)]}); <<Data:ChunkSize/binary, ?CR, ?LF, "0", ?CR, ?LF>> -> {?MODULE, decode_trailer, [<<?CR, ?LF>>, [],[], <<BodySoFar/binary, Data/binary>>, integer_to_list(AccLength), MaxHeaderSize, MaxHeaderSize]}; <<Data:ChunkSize/binary, ?CR, ?LF, "0", ?CR, ?LF, Rest/binary>> -> decode_trailer(<<?CR, ?LF, Rest/binary>>, [],[], <<BodySoFar/binary, Data/binary>>, integer_to_list(AccLength), MaxHeaderSize, MaxHeaderSize); %% There are more chunks, so here we go again... <<Data:ChunkSize/binary, ?CR, ?LF>> -> NewBody = <<BodySoFar/binary, Data/binary>>, {?MODULE, decode_size, [<<>>, [], 0, {MaxBodySize, NewBody, AccLength, MaxHeaderSize}]}; <<Data:ChunkSize/binary, ?CR, ?LF, Rest/binary>> when (AccLength < MaxBodySize) or (MaxBodySize == nolimit) -> decode_size(Rest, [], 0, {MaxBodySize, <<BodySoFar/binary, Data/binary>>, AccLength, MaxHeaderSize}); <<_:ChunkSize/binary, ?CR, ?LF, _/binary>> -> throw({error, {body_too_big, {max, MaxBodySize}}}); _ -> {?MODULE, decode_data, [ChunkSize, TotalChunk, Info]} end; decode_data(ChunkSize, TotalChunk, Info) -> {?MODULE, decode_data, [ChunkSize, TotalChunk, Info]}. decode_trailer(_,_,_,_,_, 0, TotalMaxHeaderSize) -> throw({error, {header_too_long, {max, TotalMaxHeaderSize}}}); decode_trailer(<<>>, Header, Headers, Body, BodyLength, RemainingSize, TotalMaxHeaderSize) -> {?MODULE, decode_trailer, [<<>>, Header, Headers, Body, BodyLength, RemainingSize, TotalMaxHeaderSize]}; %% Note: If Bin is not empty it is part of a pipelined request/response. decode_trailer(<<?CR,?LF,?CR,?LF, Bin/binary>>, [], [], Body, BodyLength, _, _) -> {ok, {["content-length:" ++ BodyLength], <<Body/binary, Bin/binary>>}}; decode_trailer(<<?CR,?LF,?CR,?LF, Bin/binary>>, Header, Headers, Body, BodyLength, _, _) -> NewHeaders = case Header of [] -> Headers; _ -> [lists:reverse(Header) | Headers] end, {ok, {["content-length:" ++ BodyLength | NewHeaders], <<Body/binary, Bin/binary>>}}; decode_trailer(<<?CR,?LF,?CR>> = Data, Header, Headers, Body, BodyLength, RemainingSize, TotalMaxHeaderSize) -> {?MODULE, decode_trailer, [Data, Header, Headers, Body, BodyLength, RemainingSize, TotalMaxHeaderSize]}; decode_trailer(<<?CR,?LF>> = Data, Header, Headers, Body, BodyLength, RemainingSize, TotalMaxHeaderSize) -> {?MODULE, decode_trailer, [Data, Header, Headers, Body, BodyLength, RemainingSize, TotalMaxHeaderSize]}; decode_trailer(<<?CR>> = Data, Header, Headers, Body, BodyLength, RemainingSize, TotalMaxHeaderSize) -> {?MODULE, decode_trailer, [Data, Header, Headers, Body, BodyLength, RemainingSize, TotalMaxHeaderSize]}; decode_trailer(<<?CR, ?LF, Rest/binary>>, Header, Headers, Body, BodyLength, RemainingSize, TotalMaxHeaderSize) -> decode_trailer(Rest, [], [lists:reverse(Header) | Headers], Body, BodyLength, RemainingSize, TotalMaxHeaderSize); decode_trailer(<<Octet, Rest/binary>>, Header, Headers, Body, BodyLength, RemainingSize, TotalMaxHeaderSize) -> decode_trailer(Rest, [Octet | Header], Headers, Body, BodyLength, remaing_size(RemainingSize, 1), TotalMaxHeaderSize). remaing_size(nolimit, _) -> nolimit; remaing_size(Total, Consumed) -> Total - Consumed. encode_trailers(Trailers) -> encode_trailers(Trailers, ""). encode_trailers([], Acc) -> Acc ++ ?CRLF ++ ?CRLF; encode_trailers([{Header, Value} | Rest], Acc) -> encode_trailers(Rest, Header ++ ":" ++ Value ++ ?CRLF ++ Acc).