1
%% ``The contents of this file are subject to the Erlang Public License,
2
%% Version 1.1, (the "License"); you may not use this file except in
3
%% compliance with the License. You should have received a copy of the
4
%% Erlang Public License along with this software. If not, it can be
5
%% retrieved via the world wide web at http://www.erlang.org/.
7
%% Software distributed under the License is distributed on an "AS IS"
8
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
9
%% the License for the specific language governing rights and limitations
12
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
13
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
14
%% AB. All Rights Reserved.''
17
%% Description: Implements chunked transfer encoding see RFC2616 section
21
-include("http_internal.hrl").
24
-export([decode/3, decode/4, encode/1, encode_last/0, handle_headers/2]).
25
%% Callback API - used for example if the chunkedbody is received a
26
%% little at a time on a socket.
27
-export([decode_size/1, ignore_extensions/1, decode_data/1, decode_trailer/1]).
29
%%%=========================================================================
31
%%%=========================================================================
32
%%-------------------------------------------------------------------------
33
%% decode(ChunkedBody, MaxBodySize, MaxHeaderSize, <Stream>) ->
34
%% {ok, {Headers, Body}} | {Module, Function, Args}
36
%% Headers = ["Header:Value"]
37
%% ChunkedBody = binary()
38
%% MaxBodySize = integer()
39
%% MaxHeaderSize = integer()
40
%% Stream = {Code, Request} - if Request#request.stream =/= none
41
%% and Code == 200 the side effect of sending each decode chunk to the
42
%% client/file before the whole body is received will take place.
44
%% Note: decode/4 should only be used from httpc_handler module.
45
%% Otherwhise use the side effect free decode/3.
47
%% Description: Decodes a body encoded by the chunked transfer
48
%% encoding. If the ChunkedBody is not compleate it returns {Module,
49
%% Function, Args} so that decoding can be continued when more of the
50
%% data has been received by calling Module:Function([NewData | Args]).
52
%% Note: In the case of pipelining a call to decode might contain data
53
%% that belongs to the next request/response and will be returned as
54
%% part of the body, hence functions calling http_chunk:decode must
55
%% look at the returned content-length header to make sure that they
56
%% split the actual body and data that possible should be passed along to
57
%% the next pass in the loop.
58
%%-------------------------------------------------------------------------
59
decode(ChunkedBody, MaxBodySize, MaxHeaderSize) ->
60
decode(ChunkedBody, MaxBodySize, MaxHeaderSize, false).
62
decode(ChunkedBody, MaxBodySize, MaxHeaderSize, Stream) ->
63
%% Note decode_size will call decode_data.
64
decode_size([ChunkedBody, <<>>, [],
65
{MaxBodySize, <<>>, 0, MaxHeaderSize, Stream}]).
67
%%-------------------------------------------------------------------------
68
%% encode(Chunk) -> EncodedChunk
71
%% EncodedChunk = binary()
73
%% Description: Encodes a body part with the chunked transfer encoding.
74
%% Chunks are returned as lists or binaries depending on the
75
%% input format. When sending the data on the both formats
77
%%-------------------------------------------------------------------------
78
encode(Chunk) when is_binary(Chunk)->
79
HEXSize = list_to_binary(http_util:integer_to_hexlist(size(Chunk))),
80
<<HEXSize/binary, ?CR, ?LF, Chunk/binary, ?CR, ?LF>>;
82
encode(Chunk) when is_list(Chunk)->
83
HEXSize = http_util:integer_to_hexlist(length(Chunk)),
84
[HEXSize, ?CR, ?LF, Chunk, ?CR, ?LF].
87
<<$0, ?CR, ?LF, ?CR, ?LF >>.
89
%%-------------------------------------------------------------------------
90
%% handle_headers(HeaderRecord, ChunkedHeaders) -> NewHeaderRecord
92
%% HeaderRecord = NewHeaderRecord = #http_request_h{} | #http_response_h{}
93
%% ChunkedHeaders = ["Header:Value"] as returnde by http_chunk:decode/3
95
%% Description: Removes chunked from the header as we now have decode
96
%% the body and adds a content-length header and any other headers
97
%% found in the chunked trail.
98
%%-------------------------------------------------------------------------
99
handle_headers(RequestHeaderRecord = #http_request_h{}, ChunkedHeaders) ->
100
NewHeaders = http_request:headers(ChunkedHeaders, RequestHeaderRecord),
102
case NewHeaders#http_request_h.'transfer-encoding' -- "chunked" of
108
NewHeaders#http_request_h{'transfer-encoding' = TransferEncoding};
110
handle_headers(ResponseHeaderRecord = #http_response_h{}, ChunkedHeaders) ->
111
NewHeaders = http_response:headers(ChunkedHeaders, ResponseHeaderRecord),
113
case NewHeaders#http_response_h.'transfer-encoding' -- "chunked" of
119
NewHeaders#http_response_h{'transfer-encoding' = TransferEncoding}.
121
%% Functions that may be returned during the decoding process
122
%% if the input data is incompleate.
123
decode_size([Bin, Rest, HexList, Info]) ->
124
decode_size(<<Rest/binary, Bin/binary>>, HexList, Info).
126
ignore_extensions([Bin, Rest, NextFunction]) ->
127
ignore_extensions(<<Rest/binary, Bin/binary>>, NextFunction).
129
decode_data([Bin, ChunkSize, TotalChunk, Info]) ->
130
decode_data(ChunkSize, <<TotalChunk/binary, Bin/binary>>, Info).
132
decode_trailer([Bin, Rest, Header, Headers, MaxHeaderSize, Body,
134
decode_trailer(<<Rest/binary, Bin/binary>>,
135
Header, Headers, MaxHeaderSize, Body, BodyLength).
137
%%%========================================================================
138
%%% Internal functions
139
%%%========================================================================
140
decode_size(<<>>, HexList, Info) ->
141
{?MODULE, decode_size, [<<>>, HexList, Info]};
142
decode_size(Data = <<?CR, ?LF, ChunkRest/binary>>, HexList,
145
MaxHeaderSize, Stream}) ->
146
ChunkSize = http_util:hexlist_to_integer(lists:reverse(HexList)),
148
0 -> % Last chunk, there was no data
149
ignore_extensions(Data, {?MODULE, decode_trailer,
150
[<<>>, [],[], MaxHeaderSize,
152
integer_to_list(AccLength)]});
154
%% Note decode_data may call decode_size again if there
155
%% is more than one chunk, hence here is where the last parameter
156
%% to this function comes in.
157
decode_data(ChunkSize, ChunkRest, {MaxBodySize, Body,
158
ChunkSize + AccLength ,
159
MaxHeaderSize, Stream})
161
decode_size(<<";", Rest/binary>>, HexList, Info) ->
162
%% Note ignore_extensions will call decode_size/1 again when
163
%% it ignored all extensions.
164
ignore_extensions(Rest, {?MODULE, decode_size, [<<>>, HexList, Info]});
165
decode_size(<<?CR>> = Data, HexList, Info) ->
166
{?MODULE, decode_size, [Data, HexList, Info]};
167
decode_size(<<Octet, Rest/binary>>, HexList, Info) ->
168
decode_size(Rest, [Octet | HexList], Info).
170
%% "All applications MUST ignore chunk-extension extensions they
171
%% do not understand.", see RFC 2616 Section 3.6.1 We don't
172
%% understand any extension...
173
ignore_extensions(<<>>, NextFunction) ->
174
{?MODULE, ignore_extensions, [<<>>, NextFunction]};
175
ignore_extensions(Data = <<?CR, ?LF, _ChunkRest/binary>>,
176
{Module, Function, Args}) ->
177
Module:Function([Data | Args]);
178
ignore_extensions(<<?CR>> = Data, NextFunction) ->
179
{?MODULE, ignore_extensions, [Data, NextFunction]};
180
ignore_extensions(<<_Octet, Rest/binary>>, NextFunction) ->
181
ignore_extensions(Rest, NextFunction).
183
decode_data(ChunkSize, TotalChunk,
184
Info = {MaxBodySize, BodySoFar, AccLength, MaxHeaderSize, Stream})
185
when ChunkSize =< size(TotalChunk) ->
187
%% Potential last chunk
188
<<_:ChunkSize/binary, ?CR, ?LF, "0">> ->
189
{?MODULE, decode_data, [ChunkSize, TotalChunk, Info]};
190
<<_:ChunkSize/binary, ?CR, ?LF, "0", ?CR>> ->
191
{?MODULE, decode_data, [ChunkSize, TotalChunk, Info]};
192
<<_:ChunkSize/binary, ?CR, ?LF>> ->
193
{?MODULE, decode_data, [ChunkSize, TotalChunk, Info]};
195
<<Data:ChunkSize/binary, ?CR, ?LF, "0", ";">> ->
196
%% Note ignore_extensions will call decode_trailer/1
197
%% once it ignored all extensions.
199
stream(<<BodySoFar/binary, Data/binary>>, Stream),
200
{?MODULE, ignore_extensions,
202
{?MODULE, decode_trailer, [<<>>, [],[], MaxHeaderSize,
204
integer_to_list(AccLength)]}]};
205
<<Data:ChunkSize/binary, ?CR, ?LF, "0", ";", Rest/binary>> ->
206
%% Note ignore_extensions will call decode_trailer/1
207
%% once it ignored all extensions.
208
{NewBody, _} = stream(<<BodySoFar/binary, Data/binary>>, Stream),
209
ignore_extensions(Rest, {?MODULE, decode_trailer,
210
[<<>>, [],[], MaxHeaderSize,
212
integer_to_list(AccLength)]});
213
<<Data:ChunkSize/binary, ?CR, ?LF, "0", ?CR, ?LF>> ->
214
{NewBody, _} = stream(<<BodySoFar/binary, Data/binary>>, Stream),
215
{?MODULE, decode_trailer, [<<?CR, ?LF>>, [],[], MaxHeaderSize,
217
integer_to_list(AccLength)]};
218
<<Data:ChunkSize/binary, ?CR, ?LF, "0", ?CR, ?LF, Rest/binary>> ->
219
{NewBody,_}= stream(<<BodySoFar/binary, Data/binary>>, Stream),
220
decode_trailer(<<?CR, ?LF, Rest/binary>>, [],[], MaxHeaderSize,
222
integer_to_list(AccLength));
223
%% There are more chunks, so here we go agin...
224
<<Data:ChunkSize/binary, ?CR, ?LF, Rest/binary>>
225
when (AccLength < MaxBodySize) or (MaxBodySize == nolimit) ->
226
{NewBody, NewStream} =
227
stream(<<BodySoFar/binary, Data/binary>>, Stream),
228
decode_size(Rest, [],
229
{MaxBodySize, NewBody,
230
AccLength, MaxHeaderSize, NewStream});
231
<<_:ChunkSize/binary, ?CR, ?LF, _/binary>> ->
232
throw({error, body_too_big});
234
{?MODULE, decode_data, [ChunkSize, TotalChunk, Info]}
236
decode_data(ChunkSize, TotalChunk, Info) ->
237
{?MODULE, decode_data, [ChunkSize, TotalChunk, Info]}.
239
decode_trailer(<<>>, Header, Headers, MaxHeaderSize, Body, BodyLength) ->
240
{?MODULE, decode_trailer, [<<>>, Header, Headers, MaxHeaderSize, Body,
243
%% Note: If Bin is not empty it is part of a pipelined request/response.
244
decode_trailer(<<?CR,?LF,?CR,?LF, Bin/binary>>, [], [], _, Body, BodyLength) ->
245
{ok, {["content-length:" ++ BodyLength], <<Body/binary, Bin/binary>>}};
246
decode_trailer(<<?CR,?LF,?CR,?LF, Bin/binary>>,
247
Header, Headers, MaxHeaderSize, Body, BodyLength) ->
248
NewHeaders = case Header of
252
[lists:reverse(Header) | Headers]
254
Length = length(NewHeaders),
255
case Length > MaxHeaderSize of
257
throw({error, {header_too_long, MaxHeaderSize,
258
MaxHeaderSize-Length}});
260
{ok, {["content-length:" ++ BodyLength | NewHeaders],
261
<<Body/binary, Bin/binary>>}}
263
decode_trailer(<<?CR,?LF,?CR>> = Data, Header, Headers, MaxHeaderSize,
265
{?MODULE, decode_trailer, [Data, Header, Headers, MaxHeaderSize, Body,
267
decode_trailer(<<?CR,?LF>> = Data, Header, Headers, MaxHeaderSize,
269
{?MODULE, decode_trailer, [Data, Header, Headers, MaxHeaderSize, Body,
271
decode_trailer(<<?CR>> = Data, Header, Headers, MaxHeaderSize,
273
{?MODULE, decode_trailer, [Data, Header, Headers, MaxHeaderSize, Body,
275
decode_trailer(<<?CR, ?LF, Rest/binary>>, Header, Headers,
276
MaxHeaderSize, Body, BodyLength) ->
277
decode_trailer(Rest, [], [lists:reverse(Header) | Headers],
278
MaxHeaderSize, Body, BodyLength);
280
decode_trailer(<<Octet, Rest/binary>>, Header, Headers, MaxHeaderSize, Body,
282
decode_trailer(Rest, [Octet | Header], Headers, MaxHeaderSize,
285
stream(BodyPart, false) ->
287
stream(BodyPart, {Code, Request}) ->
288
{NewBody, NewRequest} = httpc_handler:stream(BodyPart, Request, Code),
289
{NewBody, {Code, NewRequest}}.