%% ``The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved via the world wide web at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% The Initial Developer of the Original Code is Ericsson Utvecklings AB.
%% Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
%% AB. All Rights Reserved.''
%%
%% $Id: httpd_request_handler.erl,v 1.1 2008/12/17 09:53:34 mikpe Exp $
%%
-module(httpd_request_handler).
%% app internal api
-export([start_link/2, synchronize/3]).
%% module internal api
-export([connection/2, do_next_connection/6, read_header/7]).
-export([parse_trailers/1, newline/1]).
-include("httpd.hrl").
-include("httpd_verbosity.hrl").
%% start_link
start_link(Manager, ConfigDB) ->
Pid = proc_lib:spawn(?MODULE, connection, [Manager, ConfigDB]),
{ok, Pid}.
%% synchronize
synchronize(Pid, SocketType, Socket) ->
Pid ! {synchronize, SocketType, Socket}.
% connection
connection(Manager, ConfigDB) ->
{SocketType, Socket, {Status, Verbosity}} = await_synchronize(Manager),
put(sname,self()),
put(verbosity,?vvalidate(Verbosity)),
connection1(Status, Manager, ConfigDB, SocketType, Socket).
connection1({reject, busy}, Manager, ConfigDB, SocketType, Socket) ->
handle_busy(Manager, ConfigDB, SocketType, Socket);
connection1({reject, blocked}, Manager, ConfigDB, SocketType, Socket) ->
handle_blocked(Manager, ConfigDB, SocketType, Socket);
connection1(accept, Manager, ConfigDB, SocketType, Socket) ->
handle_connection(Manager, ConfigDB, SocketType, Socket).
%% await_synchronize
await_synchronize(Manager) ->
receive
{synchronize, SocketType, Socket} ->
?vlog("received syncronize: "
"~n SocketType: ~p"
"~n Socket: ~p", [SocketType, Socket]),
{SocketType, Socket, httpd_manager:new_connection(Manager)}
after 5000 ->
exit(synchronize_timeout)
end.
% handle_busy
handle_busy(Manager, ConfigDB, SocketType, Socket) ->
?vlog("handle busy: ~p", [Socket]),
MaxClients = httpd_util:lookup(ConfigDB, max_clients, 150),
String = io_lib:format("heavy load (>~w processes)", [MaxClients]),
reject_connection(Manager, ConfigDB, SocketType, Socket, String).
% handle_blocked
handle_blocked(Manager, ConfigDB, SocketType, Socket) ->
?vlog("handle blocked: ~p", [Socket]),
String = "Server maintenance performed, try again later",
reject_connection(Manager, ConfigDB, SocketType, Socket, String).
% reject_connection
reject_connection(Manager, ConfigDB, SocketType, Socket, Info) ->
String = lists:flatten(Info),
?vtrace("send status (503) message", []),
httpd_response:send_status(SocketType, Socket, 503, String, ConfigDB),
%% This ugly thing is to make ssl deliver the message, before the close...
close_sleep(SocketType, 1000),
?vtrace("close the socket", []),
close(SocketType, Socket, ConfigDB).
% handle_connection
handle_connection(Manager, ConfigDB, SocketType, Socket) ->
?vlog("handle connection: ~p", [Socket]),
Resolve = httpd_socket:resolve(SocketType),
Peername = httpd_socket:peername(SocketType, Socket),
InitData = #init_data{peername=Peername, resolve=Resolve},
TimeOut = httpd_util:lookup(ConfigDB, keep_alive_timeout, 150000),
NrOfRequest = httpd_util:lookup(ConfigDB, max_keep_alive_request, forever),
?MODULE:do_next_connection(ConfigDB, InitData,
SocketType, Socket,NrOfRequest,TimeOut),
?vlog("handle connection: done", []),
httpd_manager:done_connection(Manager),
?vlog("handle connection: close socket", []),
close(SocketType, Socket, ConfigDB).
% do_next_connection
do_next_connection(_ConfigDB, _InitData, _SocketType, _Socket, NrOfRequests,
_Timeout) when NrOfRequests < 1 ->
?vtrace("do_next_connection: done", []),
ok;
do_next_connection(ConfigDB, InitData, SocketType, Socket, NrOfRequests,
Timeout) ->
Peername = InitData#init_data.peername,
case (catch read(ConfigDB, SocketType, Socket, InitData, Timeout)) of
{'EXIT', Reason} ->
?vlog("exit reading from socket: ~p",[Reason]),
error_logger:error_report({'EXIT',Reason}),
String =
lists:flatten(
io_lib:format("exit reading from socket: ~p => ~n~p~n",
[Socket, Reason])),
error_log(mod_log,
SocketType, Socket, ConfigDB, Peername, String),
error_log(mod_disk_log,
SocketType, Socket, ConfigDB, Peername, String);
{error, Reason} ->
handle_read_error(Reason,SocketType,Socket,ConfigDB,Peername);
Info when record(Info, mod) ->
case Info#mod.connection of
true ->
ReqTimeout = httpd_util:lookup(ConfigDB,
keep_alive_timeout, 150000),
?MODULE:do_next_connection(ConfigDB, InitData,
SocketType, Socket,
dec(NrOfRequests), ReqTimeout);
_ ->
ok
end;
_ ->
ok
end.
%% read
read(ConfigDB, SocketType, Socket, InitData, Timeout) ->
?vdebug("read from socket ~p with Timeout ~p",[Socket, Timeout]),
MaxHdrSz = httpd_util:lookup(ConfigDB, max_header_size, 10240),
case ?MODULE:read_header(SocketType, Socket, Timeout, MaxHdrSz,
ConfigDB, InitData, []) of
{socket_closed, Reason} ->
?vlog("Socket closed while reading request header: "
"~n ~p", [Reason]),
socket_close;
{error, Error} ->
{error, Error};
{ok, Info, EntityBodyPart} ->
read1(SocketType, Socket, ConfigDB, InitData, Timeout, Info,
EntityBodyPart)
end.
%% Got the head and maybe a part of the body: read in the rest
read1(SocketType, Socket, ConfigDB, InitData, Timeout, Info, BodyPart)->
MaxBodySz = httpd_util:lookup(ConfigDB, max_body_size, nolimit),
ContentLength = content_length(Info),
?vtrace("ContentLength: ~p", [ContentLength]),
case read_entity_body(SocketType, Socket, Timeout, MaxBodySz,
ContentLength, BodyPart, Info, ConfigDB) of
{socket_closed, Reason} ->
?vlog("Socket closed while reading request body: "
"~n ~p", [Reason]),
socket_close;
{ok, EntityBody} ->
finish_request(EntityBody, [], Info);
{ok, ExtraHeader, EntityBody} ->
finish_request(EntityBody, ExtraHeader, Info);
Response ->
httpd_socket:close(SocketType, Socket),
socket_closed
%% Catch up all bad return values
end.
%% The request is read in send it forward to the module that
%% generates the response
finish_request(EntityBody, ExtraHeader,
#mod{parsed_header = ParsedHeader} = Info)->
?DEBUG("finish_request -> ~n"
" EntityBody: ~p~n"
" ExtraHeader: ~p~n"
" ParsedHeader: ~p~n",
[EntityBody, ExtraHeader, ParsedHeader]),
httpd_response:send(Info#mod{parsed_header = ParsedHeader ++ ExtraHeader,
entity_body = EntityBody}).
%% read_header
%% This algorithm rely on the buffer size of the inet driver together
%% with the {active, once} socket option. Atmost one message of this
%% size will be received at a given time. When a full header has been
%% read, the body is read with the recv function (the body size is known).
%%
read_header(SocketType, Socket, Timeout, MaxHdrSz, ConfigDB,
InitData, SoFar0) ->
T = t(),
%% remove any newlines at the begining, they might be crap from ?
SoFar = remove_newline(SoFar0),
case terminated_header(MaxHdrSz, SoFar) of
{true, Header, EntityBodyPart} ->
?vdebug("read_header -> done reading header: "
"~n length(Header): ~p"
"~n length(EntityBodyPart): ~p",
[length(Header), length(EntityBodyPart)]),
transform_header(SocketType, Socket, Header, ConfigDB, InitData,
EntityBodyPart);
false ->
?vtrace("read_header -> "
"~n set active = 'once' and "
"await a chunk of the header", []),
case httpd_socket:active_once(SocketType, Socket) of
ok ->
receive
%%
%% TCP
%%
{tcp, Socket, Data} ->
?vtrace("read_header(ip) -> got some data: ~p",
[sz(Data)]),
?MODULE:read_header(SocketType, Socket,
Timeout - (t()-T),
MaxHdrSz, ConfigDB,
InitData, SoFar ++ Data);
{tcp_closed, Socket} ->
?vtrace("read_header(ip) -> socket closed",[]),
{socket_closed,normal};
{tcp_error, Socket, Reason} ->
?vtrace("read_header(ip) -> socket error: ~p",
[Reason]),
{socket_closed, Reason};
%%
%% SSL
%%
{ssl, Socket, Data} ->
?vtrace("read_header(ssl) -> got some data: ~p",
[sz(Data)]),
?MODULE:read_header(SocketType, Socket,
Timeout - (t()-T),
MaxHdrSz, ConfigDB,
InitData, SoFar ++ Data);
{ssl_closed, Socket} ->
?vtrace("read_header(ssl) -> socket closed", []),
{socket_closed, normal};
{ssl_error, Socket, Reason} ->
?vtrace("read_header(ssl) -> socket error: ~p",
[Reason]),
{socket_closed, Reason}
after Timeout ->
?vlog("read_header -> timeout", []),
{socket_closed, timeout}
end;
Error ->
httpd_response:send_status(SocketType, Socket,
500, none, ConfigDB),
Error
end
end.
terminated_header(MaxHdrSz, Data) ->
D1 = lists:flatten(Data),
?vtrace("terminated_header -> Data size: ~p",[sz(D1)]),
case hsplit(MaxHdrSz,[],D1) of
not_terminated ->
false;
[Header, EntityBodyPart] ->
{true, Header++"\r\n\r\n",EntityBodyPart}
end.
transform_header(SocketType, Socket, Request, ConfigDB, InitData, BodyPart) ->
case httpd_parse:request_header(Request) of
{not_implemented, RequestLine, Method, RequestURI, ParsedHeader,
HTTPVersion} ->
httpd_response:send_status(SocketType, Socket, 501,
{Method, RequestURI, HTTPVersion},
ConfigDB),
{error,"Not Implemented"};
{bad_request, {forbidden, URI}} ->
httpd_response:send_status(SocketType, Socket, 403, URI, ConfigDB),
{error,"Forbidden Request"};
{bad_request, Reason} ->
httpd_response:send_status(SocketType, Socket, 400, none,
ConfigDB),
{error,"Malformed request"};
{ok,[Method, RequestURI, HTTPVersion, RequestLine, ParsedHeader]} ->
?DEBUG("send -> ~n"
" Method: ~p~n"
" RequestURI: ~p~n"
" HTTPVersion: ~p~n"
" RequestLine: ~p~n",
[Method, RequestURI, HTTPVersion, RequestLine]),
{ok, Info} =
httpd_parse:get_request_record(Socket, SocketType, ConfigDB,
Method, RequestURI, HTTPVersion,
RequestLine, ParsedHeader,
[], InitData),
%% Control that the Host header field is provided
case Info#mod.absolute_uri of
nohost ->
case Info#mod.http_version of
"HTTP/1.1" ->
httpd_response:send_status(Info, 400, none),
{error,"No host specified"};
_ ->
{ok, Info, BodyPart}
end;
_ ->
{ok, Info, BodyPart}
end
end.
hsplit(_MaxHdrSz, Accu,[]) ->
not_terminated;
hsplit(_MaxHdrSz, Accu, [ $\r, $\n, $\r, $\n | Tail]) ->
[lists:reverse(Accu), Tail];
hsplit(nolimit, Accu, [H|T]) ->
hsplit(nolimit,[H|Accu],T);
hsplit(MaxHdrSz, Accu, [H|T]) when length(Accu) < MaxHdrSz ->
hsplit(MaxHdrSz,[H|Accu],T);
hsplit(MaxHdrSz, Accu, D) ->
throw({error,{header_too_long,length(Accu),length(D)}}).
%%----------------------------------------------------------------------
%% The http/1.1 standard chapter 8.2.3 says that a request containing
%% An Except header-field must be responded to by 100 (Continue) by
%% the server before the client sends the body.
%%----------------------------------------------------------------------
read_entity_body(SocketType, Socket, Timeout, Max, Length, BodyPart, Info,
ConfigDB) when integer(Max) ->
case expect(Info#mod.http_version, Info#mod.parsed_header, ConfigDB) of
continue when Max > Length ->
?DEBUG("read_entity_body()->100 Continue ~n", []),
httpd_response:send_status(Info, 100, ""),
read_entity_body2(SocketType, Socket, Timeout, Max, Length,
BodyPart, Info, ConfigDB);
continue when Max < Length ->
httpd_response:send_status(Info, 417, "Body to big"),
httpd_socket:close(SocketType, Socket),
{socket_closed,"Expect denied according to size"};
break ->
httpd_response:send_status(Info, 417, "Method not allowed"),
httpd_socket:close(SocketType, Socket),
{socket_closed,"Expect conditions was not fullfilled"};
no_expect_header ->
read_entity_body2(SocketType, Socket, Timeout, Max, Length,
BodyPart, Info, ConfigDB);
http_1_0_expect_header ->
httpd_response:send_status(Info, 400,
"Only HTTP/1.1 Clients "
"may use the Expect Header"),
httpd_socket:close(SocketType, Socket),
{socket_closed,"Due to a HTTP/1.0 expect header"}
end;
read_entity_body(SocketType, Socket, Timeout, Max, Length, BodyPart,
Info, ConfigDB) ->
case expect(Info#mod.http_version, Info#mod.parsed_header, ConfigDB) of
continue ->
?DEBUG("read_entity_body() -> 100 Continue ~n", []),
httpd_response:send_status(Info, 100, ""),
read_entity_body2(SocketType, Socket, Timeout, Max, Length,
BodyPart, Info, ConfigDB);
break->
httpd_response:send_status(Info, 417, "Method not allowed"),
httpd_socket:close(SocketType, Socket),
{socket_closed,"Expect conditions was not fullfilled"};
no_expect_header ->
read_entity_body2(SocketType, Socket, Timeout, Max, Length,
BodyPart, Info, ConfigDB);
http_1_0_expect_header ->
httpd_response:send_status(Info, 400,
"HTTP/1.0 Clients are not allowed "
"to use the Expect Header"),
httpd_socket:close(SocketType, Socket),
{socket_closed,"Expect header field in an HTTP/1.0 request"}
end.
%%----------------------------------------------------------------------
%% control if the body is transfer encoded
%%----------------------------------------------------------------------
read_entity_body2(SocketType, Socket, Timeout, Max, Length, BodyPart,
Info, ConfigDB) ->
?DEBUG("read_entity_body2() -> "
"~n Max: ~p"
"~n Length: ~p"
"~n Socket: ~p", [Max, Length, Socket]),
case transfer_coding(Info) of
{chunked, ChunkedData} ->
?DEBUG("read_entity_body2() -> "
"Transfer-encoding: Chunked Data: BodyPart ~s", [BodyPart]),
read_chunked_entity(Info, Timeout, Max, Length, ChunkedData, [],
BodyPart);
unknown_coding ->
?DEBUG("read_entity_body2() -> Transfer-encoding: Unknown",[]),
httpd_response:send_status(Info, 501, "Unknown Transfer-Encoding"),
httpd_socket:close(SocketType, Socket),
{socket_closed,"Expect conditions was not fullfilled"};
none ->
?DEBUG("read_entity_body2() -> Transfer-encoding: none",[]),
read_entity_body(SocketType, Socket, Timeout, Max, Length,
BodyPart)
end.
%%----------------------------------------------------------------------
%% The body was plain read it from the socket
%% ----------------------------------------------------------------------
read_entity_body(_SocketType, _Socket, _Timeout, _Max, 0, _BodyPart) ->
{ok, []};
read_entity_body(_SocketType, _Socket, _Timeout, Max, Len, _BodyPart)
when Max < Len ->
?vlog("body to long: "
"~n Max: ~p"
"~n Len: ~p", [Max,Len]),
throw({error,{body_too_long,Max,Len}});
%% OTP-4409: Fixing POST problem
read_entity_body(_,_,_,_, Len, BodyPart) when Len == length(BodyPart) ->
?vtrace("read_entity_body -> done when"
"~n Len = length(BodyPart): ~p", [Len]),
{ok, BodyPart};
%% OTP-4550: Fix problem with trailing garbage produced by some clients.
read_entity_body(_, _, _, _, Len, BodyPart) when Len < length(BodyPart) ->
?vtrace("read_entity_body -> done when"
"~n Len: ~p"
"~n length(BodyPart): ~p", [Len, length(BodyPart)]),
{ok, lists:sublist(BodyPart,Len)};
read_entity_body(SocketType, Socket, Timeout, Max, Len, BodyPart) ->
?vtrace("read_entity_body -> entry when"
"~n Len: ~p"
"~n length(BodyPart): ~p", [Len, length(BodyPart)]),
%% OTP-4548:
%% The length calculation was previously (inets-2.*) done in the
%% read function. As of 3.0 it was removed from read but not
%% included here.
L = Len - length(BodyPart),
case httpd_socket:recv(SocketType, Socket, L, Timeout) of
{ok, Body} ->
?vtrace("read_entity_body -> received some data:"
"~n length(Body): ~p", [length(Body)]),
{ok, BodyPart ++ Body};
{error,closed} ->
{socket_closed,normal};
{error,etimedout} ->
{socket_closed, timeout};
{error,Reason} ->
{socket_closed, Reason};
Other ->
{socket_closed, Other}
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% If the body of the message is encoded used the chunked transfer encoding
%% it looks somethin like this:
%% METHOD URI HTTP/VSN
%% Transfer-Encoding: chunked
%% CRLF
%% ChunkSize
%% Chunk
%% ChunkSize
%% Chunk
%% 0
%% Trailer
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
read_chunked_entity(Info, Timeout, Max, Length, ChunkedData, Body, []) ->
?DEBUG("read_chunked_entity()->:no_chunks ~n", []),
read_chunked_entity(Info#mod.socket_type, Info#mod.socket,
Timeout, Max, Length, ChunkedData, Body,
Info#mod.config_db, Info);
read_chunked_entity(Info, Timeout, Max, Length, ChunkedData, Body, BodyPart) ->
%% Get the size
?DEBUG("read_chunked_entity() -> PrefetchedBodyPart: ~p ~n",[BodyPart]),
case parse_chunk_size(Info, Timeout, BodyPart) of
{ok, Size, NewBodyPart} when Size > 0 ->
?DEBUG("read_chunked_entity() -> Size: ~p ~n", [Size]),
case parse_chunked_entity_body(Info, Timeout, Max, length(Body),
Size, NewBodyPart) of
{ok, Chunk, NewBodyPart1} ->
?DEBUG("read_chunked_entity()->Size: ~p ~n", [Size]),
read_chunked_entity(Info, Timeout, Max, Length,
ChunkedData, Body ++ Chunk,
NewBodyPart1);
OK ->
httpd_socket:close(Info#mod.socket_type, Info#mod.socket),
{socket_closed, error}
end;
{ok, 0, Trailers} ->
?DEBUG("read_chunked_entity()->Size: 0, Trailers: ~s Body: ~s ~n",
[Trailers, Body]),
case parse_chunk_trailer(Info, Timeout, Info#mod.config_db,
Trailers) of
{ok, TrailerFields} ->
{ok, TrailerFields, Body};
_->
{ok, []}
end;
Error ->
Error
end.
parse_chunk_size(Info, Timeout, BodyPart) ->
case httpd_util:split(remove_newline(BodyPart), "\r\n", 2) of
{ok, [Size, Body]} ->
?DEBUG("parse_chunk_size()->Size: ~p ~n", [Size]),
{ok, httpd_util:hexlist_to_integer(Size), Body};
{ok, [Size]} ->
?DEBUG("parse_chunk_size()->Size: ~p ~n", [Size]),
Sz = get_chunk_size(Info#mod.socket_type,
Info#mod.socket, Timeout,
lists:reverse(Size)),
{ok, Sz, []}
end.
%%----------------------------------------------------------------------
%% We got the chunk size get the chunk
%%
%% Max: Max numbers of bytes to read may also be undefined
%% Length: Numbers of bytes already read
%% Size Numbers of byte to read for the chunk
%%----------------------------------------------------------------------
%% body to big
parse_chunked_entity_body(Info, Timeout, Max, Length, Size, BodyPart)
when Max =< (Length + Size) ->
{error, body_to_big};
%% Prefetched body part is bigger than the current chunk
%% (i.e. BodyPart includes more than one chunk)
parse_chunked_entity_body(Info, Timeout, Max, Length, Size, BodyPart)
when (Size+2) =< length(BodyPart) ->
Chunk = string:substr(BodyPart, 1, Size),
Rest = string:substr(BodyPart, Size+3),
?DEBUG("parse_chunked_entity_body() -> ~nChunk: ~s ~nRest: ~s ~n",
[Chunk, Rest]),
{ok, Chunk, Rest};
%% We just got a part of the current chunk
parse_chunked_entity_body(Info, Timeout, Max, Length, Size, BodyPart) ->
%% OTP-4551:
%% Subtracting BodyPart from Size does not produce an integer
%% when BodyPart is a list...
Remaining = Size - length(BodyPart),
LastPartOfChunk = read_chunked_entity_body(Info#mod.socket_type,
Info#mod.socket,
Timeout, Max,
Length, Remaining),
%% Remove newline
httpd_socket:recv(Info#mod.socket_type, Info#mod.socket, 2, Timeout),
?DEBUG("parse_chunked_entity_body() -> "
"~nBodyPart: ~s"
"~nLastPartOfChunk: ~s ~n",
[BodyPart, LastPartOfChunk]),
{ok, BodyPart ++ LastPartOfChunk, []}.
%%----------------------------------------------------------------------
%% If the data we got along with the header contained the whole chunked body
%% It may aswell contain the trailer :-(
%%----------------------------------------------------------------------
%% Either trailer begins with \r\n and then all data is there or
%% The trailer has data then read upto \r\n\r\n
parse_chunk_trailer(Info,Timeout,ConfigDB,"\r\n")->
{ok,[]};
parse_chunk_trailer(Info,Timeout,ConfigDB,Trailers) ->
?DEBUG("parse_chunk_trailer()->Trailers: ~s ~n", [Trailers]),
case string:rstr(Trailers,"\r\n\r\n") of
0 ->
MaxHdrSz=httpd_util:lookup(ConfigDB, max_header_size, 10240),
read_trailer_end(Info,Timeout,MaxHdrSz,Trailers);
_->
%%We got the whole header parse it up
parse_trailers(Trailers)
end.
parse_trailers(Trailer)->
?DEBUG("parse_trailer()->Trailer: ~s",[Trailer]),
{ok,[Fields0|Crap]}=httpd_util:split(Trailer,"\r\n\r\n",2),
Fields=string:tokens(Fields0,"\r\n"),
[getTrailerField(X)||X<-Fields,lists:member($:,X)].
read_trailer_end(Info,Timeout,MaxHdrSz,[])->
?DEBUG("read_trailer_end()->[]",[]),
case read_trailer(Info#mod.socket_type,Info#mod.socket,
Timeout,MaxHdrSz,[],[],
httpd_util:key1search(Info#mod.parsed_header,"trailer",[])) of
{ok,Trailers}->
Trailers;
_->
[]
end;
read_trailer_end(Info,Timeout,MaxHdrSz,Trailers)->
?DEBUG("read_trailer_end()->Trailers: ~s ~n ",[Trailers]),
%% Get the last paart of the the last headerfield
End=lists:reverse(lists:takewhile(fun(X)->case X of 10 ->false;13->false;_ ->true end end,lists:reverse(Trailers))),
Fields0=regexp:split(Trailers,"\r\n"),
%%Get rid of the last header field
[_Last|Fields]=lists:reverse(Fields0),
Headers=[getTrailerField(X)||X<-Fields,lists:member($:,X)],
case read_trailer(Info#mod.socket_type,Info#mod.socket,
Timeout,MaxHdrSz,Headers,End,
httpd_util:key1search(Info#mod.parsed_header,"trailer",[])) of
{ok,Trailers}->
Trailers;
_->
[]
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% The code below is a a good way to read in chunked encoding but
%% that require that the encoding comes from a stream and not from a list
%%&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
%%----------------------------------------------------------------------
%% The body is encoded by chubnked encoding read it in
%% ChunkedData= Chunked extensions
%% Body= the inread chunked body
%% Max: Max numbers of bytes to read
%% Length: Numbers of bytes already readed
%% Size Numbers of byte to read for the chunk
%%----------------------------------------------------------------------
read_chunked_entity(SocketType, Socket, Timeout, Max, Length, ChunkedData,
Body, ConfigDB, Info) ->
T = t(),
case get_chunk_size(SocketType,Socket,Timeout,[]) of
Size when integer(Size), Size>0 ->
case read_chunked_entity_body(SocketType, Socket,
Timeout-(t()-T),
Max, length(Body), Size) of
{ok,Chunk} ->
?DEBUG("read_chunked_entity/9 Got a chunk: ~p " ,[Chunk]),
%% Two bytes are left of the chunk, that is the CRLF
%% at the end that is not a part of the message
%% So we read it and do nothing with it.
httpd_socket:recv(SocketType,Socket,2,Timeout-(t()-T)),
read_chunked_entity(SocketType, Socket, Timeout-(t()-T),
Max, Length, ChunkedData, Body++Chunk,
ConfigDB, Info);
Error ->
?DEBUG("read_chunked_entity/9 Error: ~p " ,[Error]),
httpd_socket:close(SocketType,Socket),
{socket_closed,error}
end;
Size when integer(Size), Size == 0 ->
%% Must read in any trailer fields here
read_chunk_trailer(SocketType, Socket, Timeout,
Max, Info, ChunkedData, Body, ConfigDB);
Error ->
Error
end.
%% If a user wants to send header data after the chunked data we
%% must pick it out
read_chunk_trailer(SocketType, Socket, Timeout, Max, Info, ChunkedData,
Body, ConfigDB) ->
?DEBUG("read_chunk_trailer/8: ~p " ,[Body]),
MaxHdrSz = httpd_util:lookup(ConfigDB,max_header_size,10240),
case httpd_util:key1search(Info#mod.parsed_header,"trailer")of
undefined ->
{ok,Body};
Fields ->
case read_trailer(SocketType, Socket, Timeout,
MaxHdrSz, [], [],
string:tokens(
httpd_util:to_lower(Fields),",")) of
{ok,[]} ->
{ok,Body};
{ok,HeaderFields} ->
% ParsedExtraHeaders =
% httpd_parse:tagup_header(httpd_parse:split_lines(HeaderFields)),
{ok,HeaderFields,Body};
Error ->
Error
end
end.
read_chunked_entity_body(SocketType, Socket, Timeout, Max, Length, Size)
when integer(Max) ->
read_entity_body(SocketType, Socket, Timeout, Max-Length, Size, []);
read_chunked_entity_body(SocketType, Socket, Timeout, Max, _Length, Size) ->
read_entity_body(SocketType, Socket, Timeout, Max, Size, []).
%% If we read in the \r\n the httpd_util:hexlist_to_integer
%% Will remove it and we get rid of it emmediatly :-)
get_chunk_size(SocketType, Socket, Timeout, Size) ->
T = t(),
?DEBUG("get_chunk_size: ~p " ,[Size]),
case httpd_socket:recv(SocketType,Socket,1,Timeout) of
{ok,[Digit]} when Digit==$\n ->
httpd_util:hexlist_to_integer(lists:reverse(Size));
{ok,[Digit]} ->
get_chunk_size(SocketType,Socket,Timeout-(t()-T),[Digit|Size]);
{error,closed} ->
{socket_closed,normal};
{error,etimedout} ->
{socket_closed, timeout};
{error,Reason} ->
{socket_closed, Reason};
Other ->
{socket_closed,Other}
end.
%%----------------------------------------------------------------------
%% Reads the HTTP-trailer
%% Would be easy to tweak the read_head to do this but in this way
%% the chunked encoding can be updated better.
%%----------------------------------------------------------------------
%% When end is reached
%% read_trailer(SocketType,Socket,Timeout,MaxHdrSz,Headers,Last,[]) ->
%% {ok,Headers};
%% When header to big
read_trailer(_,_,_,MaxHdrSz,Headers,Bs,_Fields)
when MaxHdrSz < length(Headers) ->
?vlog("header to long: "
"~n MaxHdrSz: ~p"
"~n length(Bs): ~p", [MaxHdrSz,length(Bs)]),
throw({error,{header_too_long,MaxHdrSz,length(Bs)}});
%% The last Crlf is there
read_trailer(_, _, _, _, Headers, [$\n, $\r], _) ->
{ok,Headers};
read_trailer(SocketType, Socket, Timeout, MaxHdrSz, Headers,
[$\n, $\r|Rest], Fields) ->
case getTrailerField(lists:reverse(Rest))of
{error,Reason}->
{error,"Bad trailer"};
{HeaderField,Value}->
case lists:member(HeaderField,Fields) of
true ->
read_trailer(SocketType,Socket,Timeout,MaxHdrSz,
[{HeaderField,Value} |Headers],[],
lists:delete(HeaderField,Fields));
false ->
read_trailer(SocketType,Socket,Timeout,MaxHdrSz,
Headers,[],Fields)
end
end;
% read_trailer(SocketType,Socket,Timeout,MaxHdrSz,Headers,[$\n, $\r|Rest],Fields) ->
% case Rest of
% [] ->
% read_trailer(SocketType,Socket,Timeout,MaxHdrSz,Headers,Rest,Fields);
% Field ->
% case getTrailerField(lists:reverse(Rest))of
% {error,Reason}->
% {error,"Bad trailer"};
% {HeaderField,Value}->
% case lists:member(HeaderField,Fields) of
% true ->
% read_trailer(SocketType,Socket,Timeout,MaxHdrSz,
% [{HeaderField,Value} |Headers],[],
% lists:delete(HeaderField,Fields));
% false ->
% read_trailer(SocketType,Socket,Timeout,MaxHdrSz,
% Headers,[],Fields)
% end
% end
% end;
read_trailer(SocketType,Socket,Timeout,MaxHdrSz,Headers,Bs,Fields) ->
%% ?vlog("read_header -> entry with Timeout: ~p",[Timeout]),
T = t(),
case (catch httpd_socket:recv(SocketType,Socket,1,Timeout)) of
{ok,[B]} ->
read_trailer(SocketType, Socket, Timeout-(t()-T),
MaxHdrSz, Headers, [B|Bs], Fields);
{error,closed} ->
{socket_closed,normal};
{error,etimedout} ->
{socket_closed, timeout};
{error,Reason} ->
{socket_closed, Reason};
Other ->
{socket_closed,Other}
end.
getTrailerField(HeaderField)->
case string:str(HeaderField,":") of
0->
{error,"badheaderfield"};
Number ->
{httpd_util:to_lower(string:substr(HeaderField,1,Number-1)),
httpd_util:to_lower(string:substr(HeaderField,Number+1))}
end.
%% Time in milli seconds
t() ->
{A,B,C} = erlang:now(),
A*1000000000+B*1000+(C div 1000).
%%----------------------------------------------------------------------
%% If the user sends an expect header-field with the value 100-continue
%% We must send a 100 status message if he is a HTTP/1.1 client.
%% If it is an HTTP/1.0 client it's little more difficult.
%% If expect is not defined it is easy but in the other case shall we
%% Break or the transmission or let it continue the standard is not clear
%% if to break connection or wait for data.
%%----------------------------------------------------------------------
expect(HTTPVersion,ParsedHeader,ConfigDB)->
case HTTPVersion of
[$H,$T,$T,$P,$\/,$1,$.,N|_Whatever]when N>=1->
case httpd_util:key1search(ParsedHeader,"expect") of
"100-continue" ->
continue;
undefined ->
no_expect_header;
NewValue ->
break
end;
_OldVersion ->
case httpd_util:key1search(ParsedHeader,"expect") of
undefined ->
no_expect_header;
NewValue ->
case httpd_util:lookup(ConfigDB,expect,continue) of
continue->
no_expect_header;
_ ->
http_1_0_expect_header
end
end
end.
%%----------------------------------------------------------------------
%% According to the http/1.1 standard all applications must understand
%% Chunked encoded data. (Last line chapter 3.6.1).
transfer_coding(#mod{parsed_header = Ph}) ->
case httpd_util:key1search(Ph, "transfer-encoding", none) of
none ->
none;
[$c,$h,$u,$n,$k,$e,$d|Data]->
{chunked,Data};
_ ->
unknown_coding
end.
handle_read_error({header_too_long,Max,Rem},
SocketType,Socket,ConfigDB,Peername) ->
String = io_lib:format("header too long: ~p : ~p",[Max,Rem]),
handle_read_error(ConfigDB,String,SocketType,Socket,Peername,
max_header_action,close);
handle_read_error({body_too_long,Max,Actual},
SocketType,Socket,ConfigDB,Peername) ->
String = io_lib:format("body too long: ~p : ~p",[Max,Actual]),
handle_read_error(ConfigDB,String,SocketType,Socket,Peername,
max_body_action,close);
handle_read_error(Error,SocketType,Socket,ConfigDB,Peername) ->
ok.
handle_read_error(ConfigDB, ReasonString, SocketType, Socket, Peername,
Item, Default) ->
?vlog("error reading request: ~s",[ReasonString]),
E = lists:flatten(
io_lib:format("Error reading request: ~s",[ReasonString])),
error_log(mod_log, SocketType, Socket, ConfigDB, Peername, E),
error_log(mod_disk_log, SocketType, Socket, ConfigDB, Peername, E),
case httpd_util:lookup(ConfigDB,Item,Default) of
reply414 ->
send_read_status(SocketType, Socket, 414, ReasonString, ConfigDB);
_ ->
ok
end.
send_read_status(SocketType, Socket, Code, ReasonString, ConfigDB) ->
httpd_response:send_status(SocketType, Socket, Code, ReasonString,
ConfigDB).
error_log(Mod, SocketType, Socket, ConfigDB, Peername, String) ->
Modules = httpd_util:lookup(ConfigDB, modules,
[mod_get, mod_head, mod_log]),
case lists:member(Mod, Modules) of
true ->
Mod:error_log(SocketType, Socket, ConfigDB, Peername, String);
_ ->
ok
end.
sz(L) when list(L) ->
length(L);
sz(B) when binary(B) ->
size(B);
sz(O) ->
{unknown_size,O}.
%% Socket utility functions:
close(SocketType, Socket, ConfigDB) ->
case httpd_socket:close(SocketType, Socket) of
ok ->
ok;
{error, Reason} ->
?vlog("error while closing socket: ~p",[Reason]),
ok
end.
close_sleep({ssl, _}, Time) ->
sleep(Time);
close_sleep(_, _) ->
ok.
sleep(T) -> receive after T -> ok end.
dec(N) when integer(N) ->
N-1;
dec(N) ->
N.
content_length(#mod{parsed_header = Ph}) ->
list_to_integer(httpd_util:key1search(Ph, "content-length","0")).
remove_newline(List)->
lists:dropwhile(fun newline/1,List).
newline($\r) ->
true;
newline($\n) ->
true;
newline(_Sign) ->
false.