diff options
Diffstat (limited to 'lib/inets/test/httpd_test_lib.erl')
-rw-r--r-- | lib/inets/test/httpd_test_lib.erl | 332 |
1 files changed, 332 insertions, 0 deletions
diff --git a/lib/inets/test/httpd_test_lib.erl b/lib/inets/test/httpd_test_lib.erl new file mode 100644 index 0000000000..6abee5be2c --- /dev/null +++ b/lib/inets/test/httpd_test_lib.erl @@ -0,0 +1,332 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2001-2009. All Rights Reserved. +%% +%% 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 online 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. +%% +%% %CopyrightEnd% +%% +%% +-module(httpd_test_lib). + +-include("inets_test_lib.hrl"). + +%% Poll functions +-export([verify_request/6, verify_request/7, is_expect/1]). + +-record(state, {request, % string() + socket, % socket() + status_line, % {Version, StatusCode, ReasonPharse} + headers, % #http_response_h{} + body, % binary() + mfa = {httpc_response, parse, [nolimit, false]}, + canceled = [], % [RequestId] + max_header_size = nolimit, % nolimit | integer() + max_body_size = nolimit, % nolimit | integer() + print = false + }). + +%%% Part of http.hrl - Temporary solution %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%%% Response headers +-record(http_response_h,{ +%%% --- Standard "General" headers + 'cache-control', + connection, + date, + pragma, + trailer, + 'transfer-encoding', + upgrade, + via, + warning, +%%% --- Standard "Response" headers + 'accept-ranges', + age, + etag, + location, + 'proxy-authenticate', + 'retry-after', + server, + vary, + 'www-authenticate', +%%% --- Standard "Entity" headers + allow, + 'content-encoding', + 'content-language', + 'content-length' = "0", + 'content-location', + 'content-md5', + 'content-range', + 'content-type', + expires, + 'last-modified', + other=[] % list() - Key/Value list with other headers + }). +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +%%-------------------------------------------------------------------- +%% API +%%------------------------------------------------------------------ +verify_request(SocketType, Host, Port, Node, RequestStr, Options) -> + verify_request(SocketType, Host, Port, Node, RequestStr, Options, 30000). +verify_request(SocketType, Host, Port, Node, RequestStr, Options, TimeOut) -> + {ok, Socket} = inets_test_lib:connect_bin(SocketType, Host, Port), + inets_test_lib:send(SocketType, Socket, RequestStr), + + State = case inets_regexp:match(RequestStr, "printenv") of + nomatch -> + #state{}; + _ -> + #state{print = true} + end, + + case request(State#state{request = RequestStr, socket = Socket}, TimeOut) of + {error, Reson} -> + {error, Reson}; + NewState -> + ValidateResult = validate(RequestStr, NewState, Options, + Node, Port), + inets_test_lib:close(SocketType, Socket), + ValidateResult + end. + +request(#state{mfa = {Module, Function, Args}, + request = RequestStr, socket = Socket} = State, TimeOut) -> + HeadRequest = lists:sublist(RequestStr, 1, 4), + receive + {tcp, Socket, Data} -> + print(tcp, Data, State), + case Module:Function([Data | Args]) of + {ok, Parsed} -> + handle_http_msg(Parsed, State); + {_, whole_body, _} when HeadRequest == "HEAD" -> + State#state{body = <<>>}; + NewMFA -> + request(State#state{mfa = NewMFA}, TimeOut) + end; + {tcp_closed, Socket} when Function == whole_body -> + print(tcp, "closed", State), + State#state{body = hd(Args)}; + {tcp_closed, Socket} -> + test_server:fail(connection_closed); + {tcp_error, Socket, Reason} -> + test_server:fail({tcp_error, Reason}); + {ssl, Socket, Data} -> + print(ssl, Data, State), + case Module:Function([Data | Args]) of + {ok, Parsed} -> + handle_http_msg(Parsed, State); + {_, whole_body, _} when HeadRequest == "HEAD" -> + State#state{body = <<>>}; + NewMFA -> + request(State#state{mfa = NewMFA}, TimeOut) + end; + {ssl_closed, Socket} when Function == whole_body -> + print(ssl, "closed", State), + State#state{body = hd(Args)}; + {ssl_closed, Socket} -> + test_server:fail(connection_closed); + {ssl_error, Socket, Reason} -> + test_server:fail({ssl_error, Reason}) + after TimeOut -> + test_server:fail(connection_timed_out) + end. + +handle_http_msg({Version, StatusCode, ReasonPharse, Headers, Body}, + State = #state{request = RequestStr}) -> + case is_expect(RequestStr) of + true -> + State#state{status_line = {Version, + StatusCode, + ReasonPharse}, + headers = Headers}; + false -> + handle_http_body(Body, + State#state{status_line = {Version, + StatusCode, + ReasonPharse}, + headers = Headers}) + end; + +handle_http_msg({ChunkedHeaders, Body}, + State = #state{headers = Headers}) -> + NewHeaders = http_chunk:handle_headers(Headers, ChunkedHeaders), + State#state{headers = NewHeaders, body = Body}; + +handle_http_msg(Body, State) -> + State#state{body = Body}. + +handle_http_body(<<>>, State = #state{request = "HEAD" ++ _}) -> + State#state{body = <<>>}; + +handle_http_body(Body, State = #state{headers = Headers, + max_body_size = MaxBodySize}) -> + case Headers#http_response_h.'transfer-encoding' of + "chunked" -> + case http_chunk:decode(Body, State#state.max_body_size, + State#state.max_header_size) of + {Module, Function, Args} -> + request(State#state{mfa = {Module, Function, Args}}, + 30000); + {ok, {ChunkedHeaders, NewBody}} -> + NewHeaders = http_chunk:handle_headers(Headers, + ChunkedHeaders), + State#state{headers = NewHeaders, body = NewBody} + end; + _ -> + Length = + list_to_integer(Headers#http_response_h.'content-length'), + case ((Length =< MaxBodySize) or (MaxBodySize == nolimit)) of + true -> + case httpc_response:whole_body(Body, Length) of + {ok, NewBody} -> + State#state{body = NewBody}; + MFA -> + request(State#state{mfa = MFA}, 5000) + end; + false -> + test_server:fail(body_too_big) + end + end. + +validate(RequestStr, #state{status_line = {Version, StatusCode, _}, + headers = Headers, + body = Body}, Options, N, P) -> + + %io:format("Status~p: H:~p B:~p~n", [StatusCode, Headers, Body]), + check_version(Version, Options), + case lists:keysearch(statuscode, 1, Options) of + {value, _} -> + check_status_code(StatusCode, Options, Options); + _ -> + ok + end, + do_validate(http_response:header_list(Headers), Options, N, P), + check_body(RequestStr, StatusCode, + Headers#http_response_h.'content-type', + list_to_integer(Headers#http_response_h.'content-length'), + Body). + +%%-------------------------------------------------------------------- +%% Internal functions +%%------------------------------------------------------------------ +check_version(Version, Options) -> + case lists:keysearch(version, 1, Options) of + {value, {version, Version}} -> + ok; + {value, {version, Ver}} -> + test_server:fail({wrong_version, [{got, Version}, + {expected, Ver}]}); + _ -> + case Version of + "HTTP/1.1" -> + ok; + _ -> + test_server:fail({wrong_version, [{got, Version}, + {expected, "HTTP/1.1"}]}) + end + end. + +check_status_code(StatusCode, [], Options) -> + test_server:fail({wrong_status_code, [{got, StatusCode}, + {expected, Options}]}); +check_status_code(StatusCode, Current = [_ | Rest], Options) -> + case lists:keysearch(statuscode, 1, Current) of + {value, {statuscode, StatusCode}} -> + ok; + {value, {statuscode, _OtherStatus}} -> + check_status_code(StatusCode, Rest, Options); + false -> + test_server:fail({wrong_status_code, [{got, StatusCode}, + {expected, Options}]}) + end. + +do_validate(_, [], _, _) -> + ok; +do_validate(Header, [{statuscode, _Code} | Rest], N, P) -> + do_validate(Header, Rest, N, P); +do_validate(Header, [{header, HeaderField}|Rest], N, P) -> + LowerHeaderField = http_util:to_lower(HeaderField), + case lists:keysearch(LowerHeaderField, 1, Header) of + {value, {LowerHeaderField, _Value}} -> + ok; + false -> + test_server:fail({missing_header_field, LowerHeaderField, Header}); + _ -> + test_server:fail({missing_header_field, LowerHeaderField, Header}) + end, + do_validate(Header, Rest, N, P); +do_validate(Header, [{header, HeaderField, Value}|Rest],N,P) -> + LowerHeaderField = http_util:to_lower(HeaderField), + case lists:keysearch(LowerHeaderField, 1, Header) of + {value, {LowerHeaderField, Value}} -> + ok; + false -> + test_server:fail({wrong_header_field_value, LowerHeaderField, + Header}); + _ -> + test_server:fail({wrong_header_field_value, LowerHeaderField, + Header}) + end, + do_validate(Header, Rest, N, P); +do_validate(Header,[{no_last_modified,HeaderField}|Rest],N,P) -> +% io:format("Header: ~p~nHeaderField: ~p~n",[Header,HeaderField]), + case lists:keysearch(HeaderField,1,Header) of + {value,_} -> + test_server:fail({wrong_header_field_value, HeaderField, + Header}); + _ -> + ok + end, + do_validate(Header, Rest, N, P); +do_validate(Header, [_Unknown | Rest], N, P) -> + do_validate(Header, Rest, N, P). + +is_expect(RequestStr) -> + + case inets_regexp:match(RequestStr, "xpect:100-continue") of + {match, _, _}-> + true; + _ -> + false + end. + +%% OTP-5775, content-length +check_body("GET /cgi-bin/erl/httpd_example:get_bin HTTP/1.0\r\n\r\n", 200, "text/html", Length, _Body) when Length /= 274-> + test_server:fail(content_length_error); +check_body("GET /cgi-bin/cgi_echo HTTP/1.0\r\n\r\n", 200, "text/plain", + _, Body) -> + case size(Body) of + 100 -> + ok; + _ -> + test_server:fail(content_length_error) + end; + +check_body(RequestStr, 200, "text/html", _, Body) -> + HeadRequest = lists:sublist(RequestStr, 1, 3), + case HeadRequest of + "GET" -> + inets_test_lib:check_body(binary_to_list(Body)); + _ -> + ok + end; + +check_body(_, _, _, _,_) -> + ok. + +print(Proto, Data, #state{print = true}) -> + test_server:format("Received ~p: ~p~n", [Proto, Data]); +print(_, _, #state{print = false}) -> + ok. + |