~ubuntu-branches/ubuntu/jaunty/couchdb/jaunty

« back to all changes in this revision

Viewing changes to src/couch_inets/http_chunk.erl

  • Committer: Bazaar Package Importer
  • Author(s): Noah Slater
  • Date: 2008-05-24 16:30:21 UTC
  • mfrom: (1.1.1 upstream)
  • Revision ID: james.westby@ubuntu.com-20080524163021-bpkh6s1090i37xy1
Tags: 0.7.3~svn650270-2
* Added release partitioning to database and log directories.
* Corrected postrm maintainer script to not remove logs.

Show diffs side-by-side

added added

removed removed

Lines of Context:
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/.
6
 
%% 
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
10
 
%% under the License.
11
 
%% 
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.''
15
 
%% 
16
 
%%     $Id$
17
 
%% Description: Implements chunked transfer encoding see RFC2616 section
18
 
%% 3.6.1
19
 
-module(http_chunk).
20
 
 
21
 
-include("http_internal.hrl").
22
 
 
23
 
%% API
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]).
28
 
 
29
 
%%%=========================================================================
30
 
%%%  API
31
 
%%%=========================================================================
32
 
%%-------------------------------------------------------------------------
33
 
%% decode(ChunkedBody, MaxBodySize, MaxHeaderSize, <Stream>) -> 
34
 
%%       {ok, {Headers, Body}} | {Module, Function, Args}
35
 
%%
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.
43
 
%%
44
 
%% Note: decode/4 should only be used from httpc_handler module.
45
 
%% Otherwhise use the side effect free decode/3.
46
 
%%                                   
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]).
51
 
%%
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).
61
 
 
62
 
decode(ChunkedBody, MaxBodySize, MaxHeaderSize, Stream) ->
63
 
     %% Note decode_size will call decode_data.
64
 
    decode_size([ChunkedBody, <<>>, [], 
65
 
                 {MaxBodySize, <<>>, 0, MaxHeaderSize, Stream}]).
66
 
 
67
 
%%-------------------------------------------------------------------------
68
 
%% encode(Chunk) -> EncodedChunk
69
 
%%     
70
 
%%      Chunked = binary()
71
 
%%      EncodedChunk = binary()
72
 
%%                                    
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 
76
 
%%              are accepted.
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>>;
81
 
 
82
 
encode(Chunk) when is_list(Chunk)->
83
 
    HEXSize = http_util:integer_to_hexlist(length(Chunk)),
84
 
    [HEXSize,  ?CR, ?LF, Chunk, ?CR, ?LF].
85
 
 
86
 
encode_last() ->
87
 
    <<$0, ?CR, ?LF, ?CR, ?LF >>.
88
 
 
89
 
%%-------------------------------------------------------------------------
90
 
%% handle_headers(HeaderRecord, ChunkedHeaders) -> NewHeaderRecord
91
 
%%
92
 
%%      HeaderRecord = NewHeaderRecord = #http_request_h{} | #http_response_h{}
93
 
%%      ChunkedHeaders = ["Header:Value"] as returnde by http_chunk:decode/3
94
 
%%                                    
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),
101
 
    TransferEncoding = 
102
 
        case NewHeaders#http_request_h.'transfer-encoding' -- "chunked" of
103
 
            ""  ->
104
 
                undefined;
105
 
            Other ->
106
 
                Other
107
 
        end,
108
 
    NewHeaders#http_request_h{'transfer-encoding' = TransferEncoding};
109
 
 
110
 
handle_headers(ResponseHeaderRecord = #http_response_h{},  ChunkedHeaders) ->
111
 
    NewHeaders = http_response:headers(ChunkedHeaders, ResponseHeaderRecord),
112
 
    TransferEncoding = 
113
 
        case NewHeaders#http_response_h.'transfer-encoding' -- "chunked" of
114
 
            ""  ->
115
 
                undefined;
116
 
            Other ->
117
 
                Other
118
 
        end,
119
 
    NewHeaders#http_response_h{'transfer-encoding' = TransferEncoding}.
120
 
 
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).
125
 
 
126
 
ignore_extensions([Bin, Rest, NextFunction]) ->
127
 
    ignore_extensions(<<Rest/binary, Bin/binary>>, NextFunction).
128
 
 
129
 
decode_data([Bin, ChunkSize, TotalChunk, Info]) ->
130
 
    decode_data(ChunkSize, <<TotalChunk/binary, Bin/binary>>, Info).
131
 
 
132
 
decode_trailer([Bin, Rest, Header, Headers, MaxHeaderSize, Body, 
133
 
                BodyLength]) ->
134
 
    decode_trailer(<<Rest/binary, Bin/binary>>, 
135
 
                   Header, Headers, MaxHeaderSize, Body, BodyLength).
136
 
 
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, 
143
 
            {MaxBodySize, Body, 
144
 
             AccLength,
145
 
             MaxHeaderSize, Stream}) ->
146
 
    ChunkSize =  http_util:hexlist_to_integer(lists:reverse(HexList)),
147
 
     case ChunkSize of
148
 
        0 -> % Last chunk, there was no data
149
 
            ignore_extensions(Data, {?MODULE, decode_trailer, 
150
 
                                      [<<>>, [],[], MaxHeaderSize,
151
 
                                       Body,
152
 
                                       integer_to_list(AccLength)]});  
153
 
        _ ->
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})
160
 
    end;
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).
169
 
 
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).
182
 
 
183
 
decode_data(ChunkSize, TotalChunk,
184
 
            Info = {MaxBodySize, BodySoFar, AccLength, MaxHeaderSize, Stream}) 
185
 
  when ChunkSize =< size(TotalChunk) ->
186
 
    case TotalChunk of
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]};
194
 
        %% Last chunk
195
 
        <<Data:ChunkSize/binary, ?CR, ?LF, "0", ";">> ->
196
 
            %% Note ignore_extensions will call decode_trailer/1
197
 
            %% once it ignored all extensions.
198
 
            {NewBody, _} = 
199
 
                stream(<<BodySoFar/binary, Data/binary>>, Stream),
200
 
            {?MODULE, ignore_extensions, 
201
 
             [<<>>, 
202
 
              {?MODULE, decode_trailer, [<<>>, [],[], MaxHeaderSize,
203
 
                                         NewBody,
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,
211
 
                                      NewBody,
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,
216
 
                                       NewBody,
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,
221
 
                           NewBody,
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});
233
 
        _ ->
234
 
            {?MODULE, decode_data, [ChunkSize, TotalChunk, Info]}
235
 
    end;        
236
 
decode_data(ChunkSize, TotalChunk, Info) ->
237
 
    {?MODULE, decode_data, [ChunkSize, TotalChunk, Info]}.
238
 
 
239
 
decode_trailer(<<>>, Header, Headers, MaxHeaderSize, Body, BodyLength) ->
240
 
    {?MODULE, decode_trailer, [<<>>, Header, Headers, MaxHeaderSize, Body, 
241
 
                               BodyLength]};
242
 
 
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
249
 
                     [] ->
250
 
                         Headers;
251
 
                     _ ->
252
 
                         [lists:reverse(Header) | Headers]
253
 
                 end,
254
 
    Length =  length(NewHeaders), 
255
 
    case Length > MaxHeaderSize of
256
 
        true ->
257
 
            throw({error, {header_too_long, MaxHeaderSize, 
258
 
                           MaxHeaderSize-Length}});
259
 
        false ->
260
 
            {ok, {["content-length:" ++ BodyLength | NewHeaders], 
261
 
                  <<Body/binary, Bin/binary>>}}
262
 
    end;
263
 
decode_trailer(<<?CR,?LF,?CR>> = Data, Header, Headers, MaxHeaderSize, 
264
 
               Body, BodyLength) ->
265
 
    {?MODULE, decode_trailer, [Data, Header, Headers, MaxHeaderSize, Body, 
266
 
                               BodyLength]};
267
 
decode_trailer(<<?CR,?LF>> = Data, Header, Headers, MaxHeaderSize, 
268
 
               Body, BodyLength) ->
269
 
    {?MODULE, decode_trailer, [Data, Header, Headers, MaxHeaderSize, Body, 
270
 
                               BodyLength]};
271
 
decode_trailer(<<?CR>> = Data, Header, Headers, MaxHeaderSize, 
272
 
               Body, BodyLength) ->
273
 
    {?MODULE, decode_trailer, [Data, Header, Headers, MaxHeaderSize, Body, 
274
 
                               BodyLength]};
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);
279
 
 
280
 
decode_trailer(<<Octet, Rest/binary>>, Header, Headers, MaxHeaderSize, Body,
281
 
               BodyLength) ->
282
 
    decode_trailer(Rest, [Octet | Header], Headers, MaxHeaderSize, 
283
 
                   Body, BodyLength).
284
 
 
285
 
stream(BodyPart, false) ->
286
 
    {BodyPart, false};
287
 
stream(BodyPart, {Code, Request}) ->
288
 
    {NewBody, NewRequest} = httpc_handler:stream(BodyPart, Request, Code),
289
 
    {NewBody, {Code, NewRequest}}.