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 Mobile Arts AB
13
%% Portions created by Mobile Arts are Copyright 2002, Mobile Arts AB
14
%% All Rights Reserved.''
17
%%% File : http_lib.erl
18
%%% Author : Johan Blom <johan.blom@mobilearts.se>
19
%%% Description : Generic, HTTP specific helper functions
20
%%% Created : 4 Mar 2002 by Johan Blom
23
%%% - Check if I need to anything special when parsing
24
%%% "Content-Type:multipart/form-data"
27
-author("johan.blom@mobilearts.se").
30
-include("jnets_httpd.hrl").
32
-export([connection_close/1,
33
accept/3,deliver/3,recv/4,recv0/3,
34
connect/1,send/3,close/2,controlling_process/3,setopts/3,
37
create_request_line/3]).
39
-export([read_client_headers/2,read_server_headers/2,
40
get_auth_data/1,create_header_list/1,
41
read_client_body/2,read_client_multipartrange_body/3,
46
%%% Check "Connection" header if server requests session to be closed.
47
%%% No 'close' means returns false
49
%%% Check if 'close' in request headers
50
%%% Only care about HTTP 1.1 clients!
51
connection_close(Headers) when record(Headers,req_headers) ->
52
case Headers#req_headers.connection of
57
Value when list(Value) ->
62
connection_close(Headers) when record(Headers,res_headers) ->
63
case Headers#res_headers.connection of
68
Value when list(Value) ->
75
%% =============================================================================
80
% {{Y,Mon,D},{H,M,S}}=calendar:now_to_universal_time(TS),
81
% lists:flatten(io_lib:format("~4.4.0w-~2.2.0w-~2.2.0w,~2.2.0w:~2.2.0w:~6.3.0f",
82
% [Y,Mon,D,H,M,S+(MicroSecs/1000000)])).
84
%% Time in milli seconds
86
% {A,B,C} = erlang:now(),
87
% A*1000000000+B*1000+(C div 1000).
89
% sz(L) when list(L) ->
91
% sz(B) when binary(B) ->
97
%% =============================================================================
99
getHeaderValue(_Attr,[]) ->
101
getHeaderValue(Attr,[{Attr,Value}|_Rest]) ->
103
getHeaderValue(Attr,[_|Rest]) ->
104
getHeaderValue(Attr,Rest).
106
getParameterValue(_Attr,undefined) ->
108
getParameterValue(Attr,List) ->
109
case lists:keysearch(Attr,1,List) of
110
{value,{Attr,Val}} ->
116
create_request_line(Method,Path,{Major,Minor}) ->
117
[atom_to_list(Method)," ",Path,
118
" HTTP/",integer_to_list(Major),".",integer_to_list(Minor)];
119
create_request_line(Method,Path,Minor) ->
120
[atom_to_list(Method)," ",Path," HTTP/1.",integer_to_list(Minor)].
123
%%% ============================================================================
124
read_client_headers(Info,Timeout) ->
125
Headers=read_response_h(Info#response.scheme,Info#response.socket,Timeout,
126
Info#response.headers),
127
Info#response{headers=Headers}.
129
read_server_headers(Info,Timeout) ->
130
Headers=read_request_h(Info#mod.socket_type,Info#mod.socket,Timeout,
132
Info#mod{headers=Headers}.
135
%% Parses the header of a HTTP request and returns a key,value tuple
136
%% list containing Name and Value of each header directive as of:
138
%% Content-Type: multipart/mixed -> {"Content-Type", "multipart/mixed"}
140
%% But in http/1.1 the field-names are case insencitive so now it must be
141
%% Content-Type: multipart/mixed -> {"content-type", "multipart/mixed"}
142
%% The standard furthermore says that leading and traling white space
143
%% is not a part of the fieldvalue and shall therefore be removed.
144
read_request_h(SType,S,Timeout,H) ->
145
case recv0(SType,S,Timeout) of
146
{ok,{http_header,_,'Connection',_,Value}} ->
147
read_request_h(SType,S,Timeout,H#req_headers{connection=Value});
148
{ok,{http_header,_,'Content-Type',_,Val}} ->
149
read_request_h(SType,S,Timeout,H#req_headers{content_type=Val});
150
{ok,{http_header,_,'Host',_,Value}} ->
151
read_request_h(SType,S,Timeout,H#req_headers{host=Value});
152
{ok,{http_header,_,'Content-Length',_,Value}} ->
153
read_request_h(SType,S,Timeout,H#req_headers{content_length=Value});
154
% {ok,{http_header,_,'Expect',_,Value}} -> % FIXME! Update inet_drv.c!!
155
% read_request_h(SType,S,Timeout,H#req_headers{expect=Value});
156
{ok,{http_header,_,'Transfer-Encoding',_,V}} ->
157
read_request_h(SType,S,Timeout,H#req_headers{transfer_encoding=V});
158
{ok,{http_header,_,'Authorization',_,Value}} ->
159
read_request_h(SType,S,Timeout,H#req_headers{authorization=Value});
160
{ok,{http_header,_,'User-Agent',_,Value}} ->
161
read_request_h(SType,S,Timeout,H#req_headers{user_agent=Value});
162
{ok,{http_header,_,'Range',_,Value}} ->
163
read_request_h(SType,S,Timeout,H#req_headers{range=Value});
164
{ok,{http_header,_,'If-Range',_,Value}} ->
165
read_request_h(SType,S,Timeout,H#req_headers{if_range=Value});
166
{ok,{http_header,_,'If-Match',_,Value}} ->
167
read_request_h(SType,S,Timeout,H#req_headers{if_match=Value});
168
{ok,{http_header,_,'If-None-Match',_,Value}} ->
169
read_request_h(SType,S,Timeout,H#req_headers{if_none_match=Value});
170
{ok,{http_header,_,'If-Modified-Since',_,V}} ->
171
read_request_h(SType,S,Timeout,H#req_headers{if_modified_since=V});
172
{ok,{http_header,_,'If-Unmodified-Since',_,V}} ->
173
read_request_h(SType,S,Timeout,H#req_headers{if_unmodified_since=V});
174
{ok,{http_header,_,K,_,V}} ->
175
read_request_h(SType,S,Timeout,
176
H#req_headers{other=H#req_headers.other++[{K,V}]});
179
{error, timeout} when SType==http ->
180
throw({error, session_local_timeout});
181
{error, etimedout} when SType==https ->
182
throw({error, session_local_timeout});
183
{error, Reason} when Reason==closed;Reason==enotconn ->
184
throw({error, session_remotely_closed});
186
throw({error,Reason})
190
read_response_h(SType,S,Timeout,H) ->
191
case recv0(SType,S,Timeout) of
192
{ok,{http_header,_,'Connection',_,Val}} ->
193
read_response_h(SType,S,Timeout,H#res_headers{connection=Val});
194
{ok,{http_header,_,'Content-Length',_,Val}} ->
195
read_response_h(SType,S,Timeout,H#res_headers{content_length=Val});
196
{ok,{http_header,_,'Content-Type',_,Val}} ->
197
read_response_h(SType,S,Timeout,H#res_headers{content_type=Val});
198
{ok,{http_header,_,'Transfer-Encoding',_,V}} ->
199
read_response_h(SType,S,Timeout,H#res_headers{transfer_encoding=V});
200
{ok,{http_header,_,'Location',_,V}} ->
201
read_response_h(SType,S,Timeout,H#res_headers{location=V});
202
{ok,{http_header,_,'Retry-After',_,V}} ->
203
read_response_h(SType,S,Timeout,H#res_headers{retry_after=V});
204
{ok,{http_header,_,K,_,V}} ->
205
read_response_h(SType,S,Timeout,
206
H#res_headers{other=H#res_headers.other++[{K,V}]});
209
{error, timeout} when SType==http ->
210
throw({error, session_local_timeout});
211
{error, etimedout} when SType==https ->
212
throw({error, session_local_timeout});
213
{error, Reason} when Reason==closed;Reason==enotconn ->
214
throw({error, session_remotely_closed});
216
throw({error,Reason})
220
%%% Got the headers, and maybe a part of the body, now read in the rest
222
%%% - No need to check for Expect header if client
223
%%% - Currently no support for setting MaxHeaderSize in client, set to
225
%%% - Move to raw packet mode as we are finished with HTTP parsing
226
read_client_body(Info,Timeout) ->
227
Headers=Info#response.headers,
228
case Headers#res_headers.transfer_encoding of
230
?DEBUG("read_entity_body2()->"
231
"Transfer-encoding:Chunked Data:",[]),
232
read_client_chunked_body(Info,Timeout,?MAXBODYSIZE);
233
Encoding when list(Encoding) ->
234
?DEBUG("read_entity_body2()->"
235
"Transfer-encoding:Unknown",[]),
236
throw({error,unknown_coding});
238
ContLen=list_to_integer(Headers#res_headers.content_length),
240
ContLen>?MAXBODYSIZE ->
241
throw({error,body_too_big});
243
?DEBUG("read_entity_body2()->"
244
"Transfer-encoding:none ",[]),
245
Info#response{body=read_plain_body(Info#response.scheme,
246
Info#response.socket,
254
%%% ----------------------------------------------------------------------
255
read_server_body(Info,Timeout) ->
256
MaxBodySz=httpd_util:lookup(Info#mod.config_db,max_body_size,?MAXBODYSIZE),
257
ContLen=list_to_integer((Info#mod.headers)#req_headers.content_length),
258
%% ?vtrace("ContentLength: ~p", [ContLen]),
260
integer(ContLen),integer(MaxBodySz),ContLen>MaxBodySz ->
261
throw({error,body_too_big});
263
read_server_body2(Info,Timeout,ContLen,MaxBodySz)
267
%%----------------------------------------------------------------------
268
%% Control if the body is transfer encoded, if so decode it.
270
%% - MaxBodySz has an integer value or 'nolimit'
271
%% - ContLen has an integer value or 'undefined'
272
%% All applications MUST be able to receive and decode the "chunked"
273
%% transfer-coding, see RFC 2616 Section 3.6.1
274
read_server_body2(Info,Timeout,ContLen,MaxBodySz) ->
275
?DEBUG("read_entity_body2()->Max: ~p ~nLength:~p ~nSocket: ~p ~n",
276
[MaxBodySz,ContLen,Info#mod.socket]),
277
case (Info#mod.headers)#req_headers.transfer_encoding of
279
?DEBUG("read_entity_body2()->"
280
"Transfer-encoding:Chunked Data:",[]),
281
read_server_chunked_body(Info,Timeout,MaxBodySz);
282
Encoding when list(Encoding) ->
283
?DEBUG("read_entity_body2()->"
284
"Transfer-encoding:Unknown",[]),
285
httpd_response:send_status(Info,501,"Unknown Transfer-Encoding"),
286
http_lib:close(Info#mod.socket_type,Info#mod.socket),
287
throw({error,{status_sent,"Unknown Transfer-Encoding "++Encoding}});
288
_ when integer(ContLen),integer(MaxBodySz),ContLen>MaxBodySz ->
289
throw({error,body_too_big});
290
_ when integer(ContLen) ->
291
?DEBUG("read_entity_body2()->"
292
"Transfer-encoding:none ",[]),
293
Info#mod{entity_body=read_plain_body(Info#mod.socket_type,
295
ContLen,Info#mod.entity_body,
300
%%% ----------------------------------------------------------------------------
301
%%% The body was plain, just read it from the socket.
302
read_plain_body(_SocketType,Socket,0,Cont,_Timeout) ->
304
read_plain_body(SocketType,Socket,ContLen,Cont,Timeout) ->
305
Body=read_more_data(SocketType,Socket,ContLen,Timeout),
306
<<Cont/binary,Body/binary>>.
308
%%% ----------------------------------------------------------------------------
309
%%% The body was chunked, decode it.
310
%%% From RFC2616, Section 3.6.1
311
%% Chunked-Body = *chunk
316
%% chunk = chunk-size [ chunk-extension ] CRLF
318
%% chunk-size = 1*HEX
319
%% last-chunk = 1*("0") [ chunk-extension ] CRLF
321
%% chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
322
%% chunk-ext-name = token
323
%% chunk-ext-val = token | quoted-string
324
%% chunk-data = chunk-size(OCTET)
325
%% trailer = *(entity-header CRLF)
327
%%% "All applications MUST ignore chunk-extension extensions they do not
328
%%% understand.", see RFC 2616 Section 3.6.1
329
%%% We don't understand any extension...
330
read_client_chunked_body(Info,Timeout,MaxChunkSz) ->
331
case read_chunk(Info#response.scheme,Info#response.socket,
332
Timeout,0,MaxChunkSz) of
333
{last_chunk,_ExtensionList} -> % Ignore extension
334
TrailH=read_headers_old(Info#response.scheme,Info#response.socket,
336
H=Info#response.headers,
337
OtherHeaders=H#res_headers.other++TrailH,
338
Info#response{headers=H#res_headers{other=OtherHeaders}};
339
{Chunk,ChunkSize,_ExtensionList} -> % Ignore extension
340
Info1=Info#response{body= <<(Info#response.body)/binary,
342
read_client_chunked_body(Info1,Timeout,MaxChunkSz-ChunkSize);
344
throw({error,Reason})
348
read_server_chunked_body(Info,Timeout,MaxChunkSz) ->
349
case read_chunk(Info#mod.socket_type,Info#mod.socket,
350
Timeout,0,MaxChunkSz) of
351
{last_chunk,_ExtensionList} -> % Ignore extension
352
TrailH=read_headers_old(Info#mod.socket_type,Info#mod.socket,
355
OtherHeaders=H#req_headers.other++TrailH,
356
Info#mod{headers=H#req_headers{other=OtherHeaders}};
357
{Chunk,ChunkSize,_ExtensionList} -> % Ignore extension
358
Info1=Info#mod{entity_body= <<(Info#mod.entity_body)/binary,
360
read_server_chunked_body(Info1,Timeout,MaxChunkSz-ChunkSize);
362
throw({error,Reason})
366
read_chunk(Scheme,Socket,Timeout,Int,MaxChunkSz) when MaxChunkSz>Int ->
367
case read_more_data(Scheme,Socket,1,Timeout) of
368
<<C>> when $0=<C,C=<$9 ->
369
read_chunk(Scheme,Socket,Timeout,16*Int+(C-$0),MaxChunkSz);
370
<<C>> when $a=<C,C=<$f ->
371
read_chunk(Scheme,Socket,Timeout,16*Int+10+(C-$a),MaxChunkSz);
372
<<C>> when $A=<C,C=<$F ->
373
read_chunk(Scheme,Socket,Timeout,16*Int+10+(C-$A),MaxChunkSz);
375
ExtensionList=read_chunk_ext_name(Scheme,Socket,Timeout,[],[]),
376
read_chunk_data(Scheme,Socket,Int+1,ExtensionList,Timeout);
377
<<$;>> when Int==0 ->
378
ExtensionList=read_chunk_ext_name(Scheme,Socket,Timeout,[],[]),
379
read_data_lf(Scheme,Socket,Timeout),
380
{last_chunk,ExtensionList};
381
<<?CR>> when Int>0 ->
382
read_chunk_data(Scheme,Socket,Int+1,[],Timeout);
383
<<?CR>> when Int==0 ->
384
read_data_lf(Scheme,Socket,Timeout),
386
<<C>> when C==$ -> % Some servers (e.g., Apache 1.3.6) throw in
387
% additional whitespace...
388
read_chunk(Scheme,Socket,Timeout,Int,MaxChunkSz);
390
{error,unexpected_chunkdata}
392
read_chunk(_Scheme,_Socket,_Timeout,_Int,_MaxChunkSz) ->
393
{error,body_too_big}.
397
%%% - Got the initial ?CR already!
398
%%% - Bitsyntax does not allow matching of ?CR,?LF in the end of the first read
399
read_chunk_data(Scheme,Socket,Int,ExtensionList,Timeout) ->
400
case read_more_data(Scheme,Socket,Int,Timeout) of
401
<<?LF,Chunk/binary>> ->
402
case read_more_data(Scheme,Socket,2,Timeout) of
404
{Chunk,size(Chunk),ExtensionList};
406
{error,bad_chunkdata}
409
{error,bad_chunkdata}
412
read_chunk_ext_name(Scheme,Socket,Timeout,Name,Acc) ->
414
case read_more_data(Scheme,Socket,1,Timeout) of
416
read_chunk_ext_val(Scheme,Socket,Timeout,Name,[],Acc);
418
read_chunk_ext_name(Scheme,Socket,Timeout,[],
419
[{lists:reverse(Name),""}|Acc]);
421
lists:reverse([{lists:reverse(Name,"")}|Acc]);
422
Token -> % FIXME Check that it is "token"
423
read_chunk_ext_name(Scheme,Socket,Timeout,[Token|Name],Acc);
425
{error,bad_chunk_extension_name}
428
read_chunk_ext_val(Scheme,Socket,Timeout,Name,Val,Acc) ->
430
case read_more_data(Scheme,Socket,1,Timeout) of
432
read_chunk_ext_name(Scheme,Socket,Timeout,[],
433
[{Name,lists:reverse(Val)}|Acc]);
435
lists:reverse([{Name,lists:reverse(Val)}|Acc]);
436
Token -> % FIXME Check that it is "token" or "quoted-string"
437
read_chunk_ext_val(Scheme,Socket,Timeout,Name,[Token|Val],Acc);
439
{error,bad_chunk_extension_value}
442
read_data_lf(Scheme,Socket,Timeout) ->
443
case read_more_data(Scheme,Socket,1,Timeout) of
447
{error,bad_chunkdata}
450
%%% ----------------------------------------------------------------------------
451
%%% The body was "multipart/byteranges", decode it.
452
%%% Example from RFC 2616, Appendix 19.2
453
%%% HTTP/1.1 206 Partial Content
454
%%% Date: Wed, 15 Nov 1995 06:25:24 GMT
455
%%% Last-Modified: Wed, 15 Nov 1995 04:58:08 GMT
456
%%% Content-type: multipart/byteranges; boundary=THIS_STRING_SEPARATES
458
%%% --THIS_STRING_SEPARATES
459
%%% Content-type: application/pdf
460
%%% Content-range: bytes 500-999/8000
462
%%% ...the first range...
463
%%% --THIS_STRING_SEPARATES
464
%%% Content-type: application/pdf
465
%%% Content-range: bytes 7000-7999/8000
467
%%% ...the second range
468
%%% --THIS_STRING_SEPARATES--
472
%%% 1) Additional CRLFs may precede the first boundary string in the
475
read_client_multipartrange_body(Info,Parstr,Timeout) ->
476
Boundary=get_boundary(Parstr),
477
scan_boundary(Info,Boundary),
478
Info#response{body=read_multipart_body(Info,Boundary,Timeout)}.
480
read_multipart_body(Info,Boundary,Timeout) ->
483
% Headers=read_headers_old(Info#response.scheme,Info#response.socket,Timeout),
484
% H=Info#response.headers,
485
% OtherHeaders=H#res_headers.other++TrailH,
486
% Info#response{headers=H#res_headers{other=OtherHeaders}}.
489
scan_boundary(Info,Boundary) ->
493
get_boundary(Parstr) ->
494
case skip_lwsp(Parstr) of
496
throw({error,missing_range_boundary_parameter});
498
get_boundary2(string:tokens(Val, ";"))
503
get_boundary2([Param|Rest]) ->
504
case string:tokens(skip_lwsp(Param), "=") of
505
["boundary"++Attribute,Value] ->
513
skip_lwsp([$ | Cs]) -> skip_lwsp(Cs);
514
skip_lwsp([$\t | Cs]) -> skip_lwsp(Cs);
517
%%% ----------------------------------------------------------------------------
519
%%% Read the incoming data from the open socket.
520
read_more_data(http,Socket,Len,Timeout) ->
521
case gen_tcp:recv(Socket,Len,Timeout) of
525
throw({error, session_local_timeout});
526
{error, Reason} when Reason==closed;Reason==enotconn ->
527
throw({error, session_remotely_closed});
529
% httpd_response:send_status(Info,400,none),
530
throw({error, Reason})
532
read_more_data(https,Socket,Len,Timeout) ->
533
case ssl:recv(Socket,Len,Timeout) of
536
{error, etimedout} ->
537
throw({error, session_local_timeout});
538
{error, Reason} when Reason==closed;Reason==enotconn ->
539
throw({error, session_remotely_closed});
541
% httpd_response:send_status(Info,400,none),
542
throw({error, Reason})
546
%% =============================================================================
549
accept(http,ListenSocket, Timeout) ->
550
gen_tcp:accept(ListenSocket, Timeout);
551
accept(https,ListenSocket, Timeout) ->
552
ssl:accept(ListenSocket, Timeout).
555
close(http,Socket) ->
556
gen_tcp:close(Socket);
557
close(https,Socket) ->
561
connect(#request{scheme=http,settings=Settings,address=Addr}) ->
562
case proxyusage(Addr,Settings) of
566
Opts=[binary,{active,false},{reuseaddr,true}],
567
gen_tcp:connect(Host,Port,Opts)
569
connect(#request{scheme=https,settings=Settings,address=Addr}) ->
570
case proxyusage(Addr,Settings) of
574
Opts=case Settings#client_settings.ssl of
576
[binary,{active,false}];
578
[binary,{active,false}]++SSLSettings
580
ssl:connect(Host,Port,Opts)
584
%%% Check to see if the given {Host,Port} tuple is in the NoProxyList
585
%%% Returns an eventually updated {Host,Port} tuple, with the proxy address
586
proxyusage(HostPort,Settings) ->
587
case Settings#client_settings.useproxy of
589
case noProxy(HostPort,Settings#client_settings.noproxylist) of
593
case Settings#client_settings.proxy of
595
{error,no_proxy_defined};
604
noProxy(_HostPort,[]) ->
606
noProxy({Host,Port},[{Host,Port}|Rest]) ->
608
noProxy(HostPort,[_|Rest]) ->
609
noProxy(HostPort,Rest).
612
controlling_process(http,Socket,Pid) ->
613
gen_tcp:controlling_process(Socket,Pid);
614
controlling_process(https,Socket,Pid) ->
615
ssl:controlling_process(Socket,Pid).
618
deliver(SocketType, Socket, Message) ->
619
case send(SocketType, Socket, Message) of
621
close(SocketType, Socket),
624
% ?vlog("deliver(~p) failed for reason:"
625
% "~n Reason: ~p",[SocketType,_Reason]),
626
close(SocketType, Socket),
633
recv0(http,Socket,Timeout) ->
634
gen_tcp:recv(Socket,0,Timeout);
635
recv0(https,Socket,Timeout) ->
636
ssl:recv(Socket,0,Timeout).
638
recv(http,Socket,Len,Timeout) ->
639
gen_tcp:recv(Socket,Len,Timeout);
640
recv(https,Socket,Len,Timeout) ->
641
ssl:recv(Socket,Len,Timeout).
644
setopts(http,Socket,Options) ->
645
inet:setopts(Socket,Options);
646
setopts(https,Socket,Options) ->
647
ssl:setopts(Socket,Options).
650
send(http,Socket,Message) ->
651
gen_tcp:send(Socket,Message);
652
send(https,Socket,Message) ->
653
ssl:send(Socket,Message).
656
%%% ============================================================================
659
%%% Returns the Authenticating data in the HTTP request
660
get_auth_data("Basic "++EncodedString) ->
661
UnCodedString=httpd_util:decode_base64(EncodedString),
662
case catch string:tokens(UnCodedString,":") of
668
get_auth_data(BadCredentials) when list(BadCredentials) ->
669
{error,BadCredentials};
674
create_header_list(H) ->
675
lookup(connection,H#req_headers.connection)++
676
lookup(host,H#req_headers.host)++
677
lookup(content_length,H#req_headers.content_length)++
678
lookup(transfer_encoding,H#req_headers.transfer_encoding)++
679
lookup(authorization,H#req_headers.authorization)++
680
lookup(user_agent,H#req_headers.user_agent)++
681
lookup(user_agent,H#req_headers.range)++
682
lookup(user_agent,H#req_headers.if_range)++
683
lookup(user_agent,H#req_headers.if_match)++
684
lookup(user_agent,H#req_headers.if_none_match)++
685
lookup(user_agent,H#req_headers.if_modified_since)++
686
lookup(user_agent,H#req_headers.if_unmodified_since)++
689
lookup(_Key,undefined) ->
696
%%% ============================================================================
697
%%% This code is for parsing trailer headers in chunked messages.
698
%%% Will be deprecated whenever I have found an alternative working solution!
700
%%% - The header names are returned slighly different from what the what
702
read_headers_old(Scheme,Socket,Timeout) ->
703
read_headers_old(<<>>,Scheme,Socket,Timeout,[],[]).
705
read_headers_old(<<>>,Scheme,Socket,Timeout,Acc,AccHdrs) ->
706
read_headers_old(read_more_data(Scheme,Socket,1,Timeout),
707
Scheme,Socket,Timeout,Acc,AccHdrs);
708
read_headers_old(<<$\r>>,Scheme,Socket,Timeout,Acc,AccHdrs) ->
709
read_headers_old(<<$\r,(read_more_data(Scheme,Socket,1,Timeout))/binary>>,
710
Scheme,Socket,Timeout,Acc,AccHdrs);
711
read_headers_old(<<$\r,$\n>>,Scheme,Socket,Timeout,Acc,AccHdrs) ->
714
tagup_header(lists:reverse(AccHdrs));
716
read_headers_old(read_more_data(Scheme,Socket,1,Timeout),
718
Timeout,[],[lists:reverse(Acc)|AccHdrs])
720
read_headers_old(<<C>>,Scheme,Socket,Timeout,Acc,AccHdrs) ->
721
read_headers_old(read_more_data(Scheme,Socket,1,Timeout),
722
Scheme,Socket,Timeout,[C|Acc],AccHdrs);
723
read_headers_old(Bin,_Scheme,_Socket,_Timeout,_Acc,_AccHdrs) ->
724
io:format("ERROR: Unexpected data from inet driver: ~p",[Bin]),
725
throw({error,this_is_a_bug}).
728
%% Parses the header of a HTTP request and returns a key,value tuple
729
%% list containing Name and Value of each header directive as of:
731
%% Content-Type: multipart/mixed -> {"Content-Type", "multipart/mixed"}
733
%% But in http/1.1 the field-names are case insencitive so now it must be
734
%% Content-Type: multipart/mixed -> {"content-type", "multipart/mixed"}
735
%% The standard furthermore says that leading and traling white space
736
%% is not a part of the fieldvalue and shall therefore be removed.
737
tagup_header([]) -> [];
738
tagup_header([Line|Rest]) -> [tag(Line, [])|tagup_header(Rest)].
741
{httpd_util:to_lower(lists:reverse(Tag)), ""};
742
tag([$:|Rest], Tag) ->
743
{httpd_util:to_lower(lists:reverse(Tag)), httpd_util:strip(Rest)};
744
tag([Chr|Rest], Tag) ->
745
tag(Rest, [Chr|Tag]).