%% ``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.
%%
%% The Initial Developer of the Original Code is Mobile Arts AB
%% Portions created by Mobile Arts are Copyright 2002, Mobile Arts AB
%% All Rights Reserved.''
%%
%%% File : http_lib.erl
%%% Author : Johan Blom <[email protected]>
%%% Description : Generic, HTTP specific helper functions
%%% Created : 4 Mar 2002 by Johan Blom
%%% TODO
%%% - Check if I need to anything special when parsing
%%% "Content-Type:multipart/form-data"
-module(http_lib).
-author("[email protected]").
-include("http.hrl").
-include("jnets_httpd.hrl").
-export([connection_close/1,
accept/3,deliver/3,recv/4,recv0/3,
connect/1,send/3,close/2,controlling_process/3,setopts/3,
getParameterValue/2,
% get_var/2,
create_request_line/3]).
-export([read_client_headers/2,read_server_headers/2,
get_auth_data/1,create_header_list/1,
read_client_body/2,read_client_multipartrange_body/3,
read_server_body/2]).
%%% Server response:
%%% Check "Connection" header if server requests session to be closed.
%%% No 'close' means returns false
%%% Client Request:
%%% Check if 'close' in request headers
%%% Only care about HTTP 1.1 clients!
connection_close(Headers) when record(Headers,req_headers) ->
case Headers#req_headers.connection of
"close" ->
true;
"keep-alive" ->
false;
Value when list(Value) ->
true;
_ ->
false
end;
connection_close(Headers) when record(Headers,res_headers) ->
case Headers#res_headers.connection of
"close" ->
true;
"keep-alive" ->
false;
Value when list(Value) ->
true;
_ ->
false
end.
%% =============================================================================
%%% Debugging:
% format_time(TS) ->
% {_,_,MicroSecs}=TS,
% {{Y,Mon,D},{H,M,S}}=calendar:now_to_universal_time(TS),
% lists:flatten(io_lib:format("~4.4.0w-~2.2.0w-~2.2.0w,~2.2.0w:~2.2.0w:~6.3.0f",
% [Y,Mon,D,H,M,S+(MicroSecs/1000000)])).
%% Time in milli seconds
% t() ->
% {A,B,C} = erlang:now(),
% A*1000000000+B*1000+(C div 1000).
% sz(L) when list(L) ->
% length(L);
% sz(B) when binary(B) ->
% size(B);
% sz(O) ->
% {unknown_size,O}.
%% =============================================================================
getHeaderValue(_Attr,[]) ->
[];
getHeaderValue(Attr,[{Attr,Value}|_Rest]) ->
Value;
getHeaderValue(Attr,[_|Rest]) ->
getHeaderValue(Attr,Rest).
getParameterValue(_Attr,undefined) ->
undefined;
getParameterValue(Attr,List) ->
case lists:keysearch(Attr,1,List) of
{value,{Attr,Val}} ->
Val;
_ ->
undefined
end.
create_request_line(Method,Path,{Major,Minor}) ->
[atom_to_list(Method)," ",Path,
" HTTP/",integer_to_list(Major),".",integer_to_list(Minor)];
create_request_line(Method,Path,Minor) ->
[atom_to_list(Method)," ",Path," HTTP/1.",integer_to_list(Minor)].
%%% ============================================================================
read_client_headers(Info,Timeout) ->
Headers=read_response_h(Info#response.scheme,Info#response.socket,Timeout,
Info#response.headers),
Info#response{headers=Headers}.
read_server_headers(Info,Timeout) ->
Headers=read_request_h(Info#mod.socket_type,Info#mod.socket,Timeout,
Info#mod.headers),
Info#mod{headers=Headers}.
%% Parses the header of a HTTP request and returns a key,value tuple
%% list containing Name and Value of each header directive as of:
%%
%% Content-Type: multipart/mixed -> {"Content-Type", "multipart/mixed"}
%%
%% But in http/1.1 the field-names are case insencitive so now it must be
%% Content-Type: multipart/mixed -> {"content-type", "multipart/mixed"}
%% The standard furthermore says that leading and traling white space
%% is not a part of the fieldvalue and shall therefore be removed.
read_request_h(SType,S,Timeout,H) ->
case recv0(SType,S,Timeout) of
{ok,{http_header,_,'Connection',_,Value}} ->
read_request_h(SType,S,Timeout,H#req_headers{connection=Value});
{ok,{http_header,_,'Content-Type',_,Val}} ->
read_request_h(SType,S,Timeout,H#req_headers{content_type=Val});
{ok,{http_header,_,'Host',_,Value}} ->
read_request_h(SType,S,Timeout,H#req_headers{host=Value});
{ok,{http_header,_,'Content-Length',_,Value}} ->
read_request_h(SType,S,Timeout,H#req_headers{content_length=Value});
% {ok,{http_header,_,'Expect',_,Value}} -> % FIXME! Update inet_drv.c!!
% read_request_h(SType,S,Timeout,H#req_headers{expect=Value});
{ok,{http_header,_,'Transfer-Encoding',_,V}} ->
read_request_h(SType,S,Timeout,H#req_headers{transfer_encoding=V});
{ok,{http_header,_,'Authorization',_,Value}} ->
read_request_h(SType,S,Timeout,H#req_headers{authorization=Value});
{ok,{http_header,_,'User-Agent',_,Value}} ->
read_request_h(SType,S,Timeout,H#req_headers{user_agent=Value});
{ok,{http_header,_,'Range',_,Value}} ->
read_request_h(SType,S,Timeout,H#req_headers{range=Value});
{ok,{http_header,_,'If-Range',_,Value}} ->
read_request_h(SType,S,Timeout,H#req_headers{if_range=Value});
{ok,{http_header,_,'If-Match',_,Value}} ->
read_request_h(SType,S,Timeout,H#req_headers{if_match=Value});
{ok,{http_header,_,'If-None-Match',_,Value}} ->
read_request_h(SType,S,Timeout,H#req_headers{if_none_match=Value});
{ok,{http_header,_,'If-Modified-Since',_,V}} ->
read_request_h(SType,S,Timeout,H#req_headers{if_modified_since=V});
{ok,{http_header,_,'If-Unmodified-Since',_,V}} ->
read_request_h(SType,S,Timeout,H#req_headers{if_unmodified_since=V});
{ok,{http_header,_,K,_,V}} ->
read_request_h(SType,S,Timeout,
H#req_headers{other=H#req_headers.other++[{K,V}]});
{ok,http_eoh} ->
H;
{error, timeout} when SType==http ->
throw({error, session_local_timeout});
{error, etimedout} when SType==https ->
throw({error, session_local_timeout});
{error, Reason} when Reason==closed;Reason==enotconn ->
throw({error, session_remotely_closed});
{error, Reason} ->
throw({error,Reason})
end.
read_response_h(SType,S,Timeout,H) ->
case recv0(SType,S,Timeout) of
{ok,{http_header,_,'Connection',_,Val}} ->
read_response_h(SType,S,Timeout,H#res_headers{connection=Val});
{ok,{http_header,_,'Content-Length',_,Val}} ->
read_response_h(SType,S,Timeout,H#res_headers{content_length=Val});
{ok,{http_header,_,'Content-Type',_,Val}} ->
read_response_h(SType,S,Timeout,H#res_headers{content_type=Val});
{ok,{http_header,_,'Transfer-Encoding',_,V}} ->
read_response_h(SType,S,Timeout,H#res_headers{transfer_encoding=V});
{ok,{http_header,_,'Location',_,V}} ->
read_response_h(SType,S,Timeout,H#res_headers{location=V});
{ok,{http_header,_,'Retry-After',_,V}} ->
read_response_h(SType,S,Timeout,H#res_headers{retry_after=V});
{ok,{http_header,_,K,_,V}} ->
read_response_h(SType,S,Timeout,
H#res_headers{other=H#res_headers.other++[{K,V}]});
{ok,http_eoh} ->
H;
{error, timeout} when SType==http ->
throw({error, session_local_timeout});
{error, etimedout} when SType==https ->
throw({error, session_local_timeout});
{error, Reason} when Reason==closed;Reason==enotconn ->
throw({error, session_remotely_closed});
{error, Reason} ->
throw({error,Reason})
end.
%%% Got the headers, and maybe a part of the body, now read in the rest
%%% Note:
%%% - No need to check for Expect header if client
%%% - Currently no support for setting MaxHeaderSize in client, set to
%%% unlimited.
%%% - Move to raw packet mode as we are finished with HTTP parsing
read_client_body(Info,Timeout) ->
Headers=Info#response.headers,
case Headers#res_headers.transfer_encoding of
"chunked" ->
?DEBUG("read_entity_body2()->"
"Transfer-encoding:Chunked Data:",[]),
read_client_chunked_body(Info,Timeout,?MAXBODYSIZE);
Encoding when list(Encoding) ->
?DEBUG("read_entity_body2()->"
"Transfer-encoding:Unknown",[]),
throw({error,unknown_coding});
_ ->
ContLen=list_to_integer(Headers#res_headers.content_length),
if
ContLen>?MAXBODYSIZE ->
throw({error,body_too_big});
true ->
?DEBUG("read_entity_body2()->"
"Transfer-encoding:none ",[]),
Info#response{body=read_plain_body(Info#response.scheme,
Info#response.socket,
ContLen,
Info#response.body,
Timeout)}
end
end.
%%% ----------------------------------------------------------------------
read_server_body(Info,Timeout) ->
MaxBodySz=httpd_util:lookup(Info#mod.config_db,max_body_size,?MAXBODYSIZE),
ContLen=list_to_integer((Info#mod.headers)#req_headers.content_length),
%% ?vtrace("ContentLength: ~p", [ContLen]),
if
integer(ContLen),integer(MaxBodySz),ContLen>MaxBodySz ->
throw({error,body_too_big});
true ->
read_server_body2(Info,Timeout,ContLen,MaxBodySz)
end.
%%----------------------------------------------------------------------
%% Control if the body is transfer encoded, if so decode it.
%% Note:
%% - MaxBodySz has an integer value or 'nolimit'
%% - ContLen has an integer value or 'undefined'
%% All applications MUST be able to receive and decode the "chunked"
%% transfer-coding, see RFC 2616 Section 3.6.1
read_server_body2(Info,Timeout,ContLen,MaxBodySz) ->
?DEBUG("read_entity_body2()->Max: ~p ~nLength:~p ~nSocket: ~p ~n",
[MaxBodySz,ContLen,Info#mod.socket]),
case (Info#mod.headers)#req_headers.transfer_encoding of
"chunked" ->
?DEBUG("read_entity_body2()->"
"Transfer-encoding:Chunked Data:",[]),
read_server_chunked_body(Info,Timeout,MaxBodySz);
Encoding when list(Encoding) ->
?DEBUG("read_entity_body2()->"
"Transfer-encoding:Unknown",[]),
httpd_response:send_status(Info,501,"Unknown Transfer-Encoding"),
http_lib:close(Info#mod.socket_type,Info#mod.socket),
throw({error,{status_sent,"Unknown Transfer-Encoding "++Encoding}});
_ when integer(ContLen),integer(MaxBodySz),ContLen>MaxBodySz ->
throw({error,body_too_big});
_ when integer(ContLen) ->
?DEBUG("read_entity_body2()->"
"Transfer-encoding:none ",[]),
Info#mod{entity_body=read_plain_body(Info#mod.socket_type,
Info#mod.socket,
ContLen,Info#mod.entity_body,
Timeout)}
end.
%%% ----------------------------------------------------------------------------
%%% The body was plain, just read it from the socket.
read_plain_body(_SocketType,Socket,0,Cont,_Timeout) ->
Cont;
read_plain_body(SocketType,Socket,ContLen,Cont,Timeout) ->
Body=read_more_data(SocketType,Socket,ContLen,Timeout),
<<Cont/binary,Body/binary>>.
%%% ----------------------------------------------------------------------------
%%% The body was chunked, decode it.
%%% From RFC2616, Section 3.6.1
%% Chunked-Body = *chunk
%% last-chunk
%% trailer
%% CRLF
%%
%% chunk = chunk-size [ chunk-extension ] CRLF
%% chunk-data CRLF
%% chunk-size = 1*HEX
%% last-chunk = 1*("0") [ chunk-extension ] CRLF
%%
%% chunk-extension= *( ";" chunk-ext-name [ "=" chunk-ext-val ] )
%% chunk-ext-name = token
%% chunk-ext-val = token | quoted-string
%% chunk-data = chunk-size(OCTET)
%% trailer = *(entity-header CRLF)
%%
%%% "All applications MUST ignore chunk-extension extensions they do not
%%% understand.", see RFC 2616 Section 3.6.1
%%% We don't understand any extension...
read_client_chunked_body(Info,Timeout,MaxChunkSz) ->
case read_chunk(Info#response.scheme,Info#response.socket,
Timeout,0,MaxChunkSz) of
{last_chunk,_ExtensionList} -> % Ignore extension
TrailH=read_headers_old(Info#response.scheme,Info#response.socket,
Timeout),
H=Info#response.headers,
OtherHeaders=H#res_headers.other++TrailH,
Info#response{headers=H#res_headers{other=OtherHeaders}};
{Chunk,ChunkSize,_ExtensionList} -> % Ignore extension
Info1=Info#response{body= <<(Info#response.body)/binary,
Chunk/binary>>},
read_client_chunked_body(Info1,Timeout,MaxChunkSz-ChunkSize);
{error,Reason} ->
throw({error,Reason})
end.
read_server_chunked_body(Info,Timeout,MaxChunkSz) ->
case read_chunk(Info#mod.socket_type,Info#mod.socket,
Timeout,0,MaxChunkSz) of
{last_chunk,_ExtensionList} -> % Ignore extension
TrailH=read_headers_old(Info#mod.socket_type,Info#mod.socket,
Timeout),
H=Info#mod.headers,
OtherHeaders=H#req_headers.other++TrailH,
Info#mod{headers=H#req_headers{other=OtherHeaders}};
{Chunk,ChunkSize,_ExtensionList} -> % Ignore extension
Info1=Info#mod{entity_body= <<(Info#mod.entity_body)/binary,
Chunk/binary>>},
read_server_chunked_body(Info1,Timeout,MaxChunkSz-ChunkSize);
{error,Reason} ->
throw({error,Reason})
end.
read_chunk(Scheme,Socket,Timeout,Int,MaxChunkSz) when MaxChunkSz>Int ->
case read_more_data(Scheme,Socket,1,Timeout) of
<<C>> when $0=<C,C=<$9 ->
read_chunk(Scheme,Socket,Timeout,16*Int+(C-$0),MaxChunkSz);
<<C>> when $a=<C,C=<$f ->
read_chunk(Scheme,Socket,Timeout,16*Int+10+(C-$a),MaxChunkSz);
<<C>> when $A=<C,C=<$F ->
read_chunk(Scheme,Socket,Timeout,16*Int+10+(C-$A),MaxChunkSz);
<<$;>> when Int>0 ->
ExtensionList=read_chunk_ext_name(Scheme,Socket,Timeout,[],[]),
read_chunk_data(Scheme,Socket,Int+1,ExtensionList,Timeout);
<<$;>> when Int==0 ->
ExtensionList=read_chunk_ext_name(Scheme,Socket,Timeout,[],[]),
read_data_lf(Scheme,Socket,Timeout),
{last_chunk,ExtensionList};
<<?CR>> when Int>0 ->
read_chunk_data(Scheme,Socket,Int+1,[],Timeout);
<<?CR>> when Int==0 ->
read_data_lf(Scheme,Socket,Timeout),
{last_chunk,[]};
<<C>> when C==$ -> % Some servers (e.g., Apache 1.3.6) throw in
% additional whitespace...
read_chunk(Scheme,Socket,Timeout,Int,MaxChunkSz);
_Other ->
{error,unexpected_chunkdata}
end;
read_chunk(_Scheme,_Socket,_Timeout,_Int,_MaxChunkSz) ->
{error,body_too_big}.
%%% Note:
%%% - Got the initial ?CR already!
%%% - Bitsyntax does not allow matching of ?CR,?LF in the end of the first read
read_chunk_data(Scheme,Socket,Int,ExtensionList,Timeout) ->
case read_more_data(Scheme,Socket,Int,Timeout) of
<<?LF,Chunk/binary>> ->
case read_more_data(Scheme,Socket,2,Timeout) of
<<?CR,?LF>> ->
{Chunk,size(Chunk),ExtensionList};
_ ->
{error,bad_chunkdata}
end;
_ ->
{error,bad_chunkdata}
end.
read_chunk_ext_name(Scheme,Socket,Timeout,Name,Acc) ->
Len=length(Name),
case read_more_data(Scheme,Socket,1,Timeout) of
$= when Len>0 ->
read_chunk_ext_val(Scheme,Socket,Timeout,Name,[],Acc);
$; when Len>0 ->
read_chunk_ext_name(Scheme,Socket,Timeout,[],
[{lists:reverse(Name),""}|Acc]);
?CR when Len>0 ->
lists:reverse([{lists:reverse(Name,"")}|Acc]);
Token -> % FIXME Check that it is "token"
read_chunk_ext_name(Scheme,Socket,Timeout,[Token|Name],Acc);
_ ->
{error,bad_chunk_extension_name}
end.
read_chunk_ext_val(Scheme,Socket,Timeout,Name,Val,Acc) ->
Len=length(Val),
case read_more_data(Scheme,Socket,1,Timeout) of
$; when Len>0 ->
read_chunk_ext_name(Scheme,Socket,Timeout,[],
[{Name,lists:reverse(Val)}|Acc]);
?CR when Len>0 ->
lists:reverse([{Name,lists:reverse(Val)}|Acc]);
Token -> % FIXME Check that it is "token" or "quoted-string"
read_chunk_ext_val(Scheme,Socket,Timeout,Name,[Token|Val],Acc);
_ ->
{error,bad_chunk_extension_value}
end.
read_data_lf(Scheme,Socket,Timeout) ->
case read_more_data(Scheme,Socket,1,Timeout) of
?LF ->
ok;
_ ->
{error,bad_chunkdata}
end.
%%% ----------------------------------------------------------------------------
%%% The body was "multipart/byteranges", decode it.
%%% Example from RFC 2616, Appendix 19.2
%%% HTTP/1.1 206 Partial Content
%%% Date: Wed, 15 Nov 1995 06:25:24 GMT
%%% Last-Modified: Wed, 15 Nov 1995 04:58:08 GMT
%%% Content-type: multipart/byteranges; boundary=THIS_STRING_SEPARATES
%%%
%%% --THIS_STRING_SEPARATES
%%% Content-type: application/pdf
%%% Content-range: bytes 500-999/8000
%%%
%%% ...the first range...
%%% --THIS_STRING_SEPARATES
%%% Content-type: application/pdf
%%% Content-range: bytes 7000-7999/8000
%%%
%%% ...the second range
%%% --THIS_STRING_SEPARATES--
%%%
%%% Notes:
%%%
%%% 1) Additional CRLFs may precede the first boundary string in the
%%% entity.
%%% FIXME!!
read_client_multipartrange_body(Info,Parstr,Timeout) ->
Boundary=get_boundary(Parstr),
scan_boundary(Info,Boundary),
Info#response{body=read_multipart_body(Info,Boundary,Timeout)}.
read_multipart_body(Info,Boundary,Timeout) ->
Info.
% Headers=read_headers_old(Info#response.scheme,Info#response.socket,Timeout),
% H=Info#response.headers,
% OtherHeaders=H#res_headers.other++TrailH,
% Info#response{headers=H#res_headers{other=OtherHeaders}}.
scan_boundary(Info,Boundary) ->
Info.
get_boundary(Parstr) ->
case skip_lwsp(Parstr) of
[] ->
throw({error,missing_range_boundary_parameter});
Val ->
get_boundary2(string:tokens(Val, ";"))
end.
get_boundary2([]) ->
undefined;
get_boundary2([Param|Rest]) ->
case string:tokens(skip_lwsp(Param), "=") of
["boundary"++Attribute,Value] ->
Value;
_ ->
get_boundary2(Rest)
end.
%% skip space & tab
skip_lwsp([$ | Cs]) -> skip_lwsp(Cs);
skip_lwsp([$\t | Cs]) -> skip_lwsp(Cs);
skip_lwsp(Cs) -> Cs.
%%% ----------------------------------------------------------------------------
%%% Read the incoming data from the open socket.
read_more_data(http,Socket,Len,Timeout) ->
case gen_tcp:recv(Socket,Len,Timeout) of
{ok,Val} ->
Val;
{error, timeout} ->
throw({error, session_local_timeout});
{error, Reason} when Reason==closed;Reason==enotconn ->
throw({error, session_remotely_closed});
{error, Reason} ->
% httpd_response:send_status(Info,400,none),
throw({error, Reason})
end;
read_more_data(https,Socket,Len,Timeout) ->
case ssl:recv(Socket,Len,Timeout) of
{ok,Val} ->
Val;
{error, etimedout} ->
throw({error, session_local_timeout});
{error, Reason} when Reason==closed;Reason==enotconn ->
throw({error, session_remotely_closed});
{error, Reason} ->
% httpd_response:send_status(Info,400,none),
throw({error, Reason})
end.
%% =============================================================================
%%% Socket handling
accept(http,ListenSocket, Timeout) ->
gen_tcp:accept(ListenSocket, Timeout);
accept(https,ListenSocket, Timeout) ->
ssl:accept(ListenSocket, Timeout).
close(http,Socket) ->
gen_tcp:close(Socket);
close(https,Socket) ->
ssl:close(Socket).
connect(#request{scheme=http,settings=Settings,address=Addr}) ->
case proxyusage(Addr,Settings) of
{error,Reason} ->
{error,Reason};
{Host,Port} ->
Opts=[binary,{active,false},{reuseaddr,true}],
gen_tcp:connect(Host,Port,Opts)
end;
connect(#request{scheme=https,settings=Settings,address=Addr}) ->
case proxyusage(Addr,Settings) of
{error,Reason} ->
{error,Reason};
{Host,Port} ->
Opts=case Settings#client_settings.ssl of
false ->
[binary,{active,false}];
SSLSettings ->
[binary,{active,false}]++SSLSettings
end,
ssl:connect(Host,Port,Opts)
end.
%%% Check to see if the given {Host,Port} tuple is in the NoProxyList
%%% Returns an eventually updated {Host,Port} tuple, with the proxy address
proxyusage(HostPort,Settings) ->
case Settings#client_settings.useproxy of
true ->
case noProxy(HostPort,Settings#client_settings.noproxylist) of
true ->
HostPort;
_ ->
case Settings#client_settings.proxy of
undefined ->
{error,no_proxy_defined};
ProxyHostPort ->
ProxyHostPort
end
end;
_ ->
HostPort
end.
noProxy(_HostPort,[]) ->
false;
noProxy({Host,Port},[{Host,Port}|Rest]) ->
true;
noProxy(HostPort,[_|Rest]) ->
noProxy(HostPort,Rest).
controlling_process(http,Socket,Pid) ->
gen_tcp:controlling_process(Socket,Pid);
controlling_process(https,Socket,Pid) ->
ssl:controlling_process(Socket,Pid).
deliver(SocketType, Socket, Message) ->
case send(SocketType, Socket, Message) of
{error, einval} ->
close(SocketType, Socket),
socket_closed;
{error, _Reason} ->
% ?vlog("deliver(~p) failed for reason:"
% "~n Reason: ~p",[SocketType,_Reason]),
close(SocketType, Socket),
socket_closed;
_Other ->
ok
end.
recv0(http,Socket,Timeout) ->
gen_tcp:recv(Socket,0,Timeout);
recv0(https,Socket,Timeout) ->
ssl:recv(Socket,0,Timeout).
recv(http,Socket,Len,Timeout) ->
gen_tcp:recv(Socket,Len,Timeout);
recv(https,Socket,Len,Timeout) ->
ssl:recv(Socket,Len,Timeout).
setopts(http,Socket,Options) ->
inet:setopts(Socket,Options);
setopts(https,Socket,Options) ->
ssl:setopts(Socket,Options).
send(http,Socket,Message) ->
gen_tcp:send(Socket,Message);
send(https,Socket,Message) ->
ssl:send(Socket,Message).
%%% ============================================================================
%%% HTTP Server only
%%% Returns the Authenticating data in the HTTP request
get_auth_data("Basic "++EncodedString) ->
UnCodedString=httpd_util:decode_base64(EncodedString),
case catch string:tokens(UnCodedString,":") of
[User,PassWord] ->
{User,PassWord};
{error,Error}->
{error,Error}
end;
get_auth_data(BadCredentials) when list(BadCredentials) ->
{error,BadCredentials};
get_auth_data(_) ->
{error,nouser}.
create_header_list(H) ->
lookup(connection,H#req_headers.connection)++
lookup(host,H#req_headers.host)++
lookup(content_length,H#req_headers.content_length)++
lookup(transfer_encoding,H#req_headers.transfer_encoding)++
lookup(authorization,H#req_headers.authorization)++
lookup(user_agent,H#req_headers.user_agent)++
lookup(user_agent,H#req_headers.range)++
lookup(user_agent,H#req_headers.if_range)++
lookup(user_agent,H#req_headers.if_match)++
lookup(user_agent,H#req_headers.if_none_match)++
lookup(user_agent,H#req_headers.if_modified_since)++
lookup(user_agent,H#req_headers.if_unmodified_since)++
H#req_headers.other.
lookup(_Key,undefined) ->
[];
lookup(Key,Val) ->
[{Key,Val}].
%%% ============================================================================
%%% This code is for parsing trailer headers in chunked messages.
%%% Will be deprecated whenever I have found an alternative working solution!
%%% Note:
%%% - The header names are returned slightly different from what the what
%%% inet_drv returns
read_headers_old(Scheme,Socket,Timeout) ->
read_headers_old(<<>>,Scheme,Socket,Timeout,[],[]).
read_headers_old(<<>>,Scheme,Socket,Timeout,Acc,AccHdrs) ->
read_headers_old(read_more_data(Scheme,Socket,1,Timeout),
Scheme,Socket,Timeout,Acc,AccHdrs);
read_headers_old(<<$\r>>,Scheme,Socket,Timeout,Acc,AccHdrs) ->
read_headers_old(<<$\r,(read_more_data(Scheme,Socket,1,Timeout))/binary>>,
Scheme,Socket,Timeout,Acc,AccHdrs);
read_headers_old(<<$\r,$\n>>,Scheme,Socket,Timeout,Acc,AccHdrs) ->
if
Acc==[] -> % Done!
tagup_header(lists:reverse(AccHdrs));
true ->
read_headers_old(read_more_data(Scheme,Socket,1,Timeout),
Scheme,Socket,
Timeout,[],[lists:reverse(Acc)|AccHdrs])
end;
read_headers_old(<<C>>,Scheme,Socket,Timeout,Acc,AccHdrs) ->
read_headers_old(read_more_data(Scheme,Socket,1,Timeout),
Scheme,Socket,Timeout,[C|Acc],AccHdrs);
read_headers_old(Bin,_Scheme,_Socket,_Timeout,_Acc,_AccHdrs) ->
io:format("ERROR: Unexpected data from inet driver: ~p",[Bin]),
throw({error,this_is_a_bug}).
%% Parses the header of a HTTP request and returns a key,value tuple
%% list containing Name and Value of each header directive as of:
%%
%% Content-Type: multipart/mixed -> {"Content-Type", "multipart/mixed"}
%%
%% But in http/1.1 the field-names are case insencitive so now it must be
%% Content-Type: multipart/mixed -> {"content-type", "multipart/mixed"}
%% The standard furthermore says that leading and traling white space
%% is not a part of the fieldvalue and shall therefore be removed.
tagup_header([]) -> [];
tagup_header([Line|Rest]) -> [tag(Line, [])|tagup_header(Rest)].
tag([], Tag) ->
{httpd_util:to_lower(lists:reverse(Tag)), ""};
tag([$:|Rest], Tag) ->
{httpd_util:to_lower(lists:reverse(Tag)), httpd_util:strip(Rest)};
tag([Chr|Rest], Tag) ->
tag(Rest, [Chr|Tag]).