From 6153ba7599f2ce1ab22959a40b6ca33b4238f0d0 Mon Sep 17 00:00:00 2001 From: Micael Karlberg Date: Wed, 13 Jan 2010 16:18:24 +0000 Subject: OTP-8016, OTP-8056, OTP-8103, OTP-8106, OTP-8312, OTP-8315, OTP-8327, OTP-8349, OTP-8351, OTP-8359 & OTP-8371. --- lib/inets/test/httpd_1_1.erl | 494 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 494 insertions(+) create mode 100644 lib/inets/test/httpd_1_1.erl (limited to 'lib/inets/test/httpd_1_1.erl') diff --git a/lib/inets/test/httpd_1_1.erl b/lib/inets/test/httpd_1_1.erl new file mode 100644 index 0000000000..055d034bec --- /dev/null +++ b/lib/inets/test/httpd_1_1.erl @@ -0,0 +1,494 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2005-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_1_1). +-author('ingela@erix.ericsson.se'). + +-include("test_server.hrl"). +-include("test_server_line.hrl"). +-include_lib("kernel/include/file.hrl"). + +-export([host/4, chunked/4, expect/4, range/4, if_test/5, http_trace/4, + head/4, mod_cgi_chunked_encoding_test/5]). + +%% -define(all_keys_lower_case,true). +-ifndef(all_keys_lower_case). +-define(CONTENT_LENGTH, "Content-Length: "). +-define(CONTENT_RANGE, "Content-Range: "). +-define(CONTENT_TYPE, "Content-Type: "). +-else. +-define(CONTENT_LENGTH, "content-length:"). +-define(CONTENT_RANGE, "content-range:"). +-define(CONTENT_TYPE, "content-type:"). +-endif. + + +%%------------------------------------------------------------------------- +%% Test cases starts here. +%%------------------------------------------------------------------------- +host(Type, Port, Host, Node) -> + %% No host needed for HTTP/1.0 + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "GET / HTTP/1.0\r\n\r\n", + [{statuscode, 200}, + {version, "HTTP/1.0"}]), + %% No host must generate an error + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "GET / HTTP/1.1\r\n\r\n", + [{statuscode, 400}]), + + %% If it is a fully qualified URL no host is needed + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "GET HTTP://"++ Host ++ ":" ++ + integer_to_list(Port)++ + "/ HTTP/1.1\r\n\r\n", + [{statuscode, 200}]), + + %% If both look at the url. + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "GET HTTP://"++ Host ++ ":"++ + integer_to_list(Port) ++ + "/ HTTP/1.1\r\nHost:"++ Host ++ + "\r\n\r\n",[{statuscode, 200}]), + + %% Allow the request if its a Host field + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "GET / HTTP/1.1\r\nHost:"++ + Host ++ "\r\n\r\n", + [{statuscode, 200}]), + ok. + +chunked(Type, Port, Host, Node)-> + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "GET / HTTP/1.1\r\n" + "Host:"++ Host ++"\r\n" + "Transfer-Encoding:chunked\r\n" + "\r\n" + "A\r\n" + "1234567890\r\n" + "4\r\n" + "HEJ!\r\n" + "0\r\n\r\n", + [{statuscode, 200}]), + + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "GET / HTTP/1.1\r\n" + "Host:"++ Host ++"\r\n" + "Transfer-Encoding:chunked\r\n" + "Trailer:Content-Type\r\n" + "\r\n" + "A\r\n" + "1234567890\r\n" + "4\r\n" + "HEJ!\r\n" + "0\r\n" + "Content-Type:text/plain\r\n\r\n", + [{statuscode, 200}]), + ok. + +expect(Type, Port, Host, Node)-> + Request="GET / HTTP/1.1\r\nHost:" ++ Host ++ + "\r\nContent-Length:22\r\nExpect:100-continue\r\n\r\n", + + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + Request, + [{statuscode, 100}]). +range(Type, Port, Host, Node)-> + + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "GET /range.txt HTTP/1.1\r\nHost:" + ++ Host + ++ "\r\nRange:bytes=110-120\r\n\r\n", + [{statuscode,416}]), + %%The simples of all range request a range + Request1="GET /range.txt HTTP/1.1\r\nHost:" ++ Host ++ + "\r\nRange:bytes=0-9\r\n\r\n", + {ok, Socket1} = inets_test_lib:connect_byte(Type, Host, Port), + inets_test_lib:send(Type, Socket1,Request1), + ok = validateRangeRequest(Socket1,[],"1234567890",$2,$0,$6), + inets_test_lib:close(Type,Socket1), + + %% Request the end of the file + Request2 = + "GET /range.txt HTTP/1.1\r\nHost:" ++ Host ++ + "\r\nRange:bytes=90-\r\n\r\n", + + {ok, Socket2} = inets_test_lib:connect_byte(Type, Host, Port), + inets_test_lib:send(Type, Socket2, Request2), + ok = validateRangeRequest(Socket2,[],"1234567890",$2,$0,$6), + inets_test_lib:close(Type,Socket2), + + %% The last byte in the file + Request3 = + "GET /range.txt HTTP/1.1\r\nHost:"++ + Host ++ "\r\nRange:bytes=-1\r\n\r\n", + {ok, Socket3} = inets_test_lib:connect_byte(Type, Host, Port), + inets_test_lib:send(Type, Socket3,Request3), + ok = validateRangeRequest(Socket3,[],"0",$2,$0,$6), + inets_test_lib:close(Type, Socket3), + + %%Multi Range + Request4 = "GET /range.txt HTTP/1.1\r\nHost:" ++ Host ++ + "\r\nRange:bytes=0-0,2-2,-1\r\n\r\n", + {ok, Socket4} = inets_test_lib:connect_byte(Type, Host, Port), + inets_test_lib:send(Type, Socket4, Request4), + ok = validateRangeRequest(Socket4,[],"130",$2,$0,$6), + inets_test_lib:close(Type, Socket4). + +if_test(Type, Port, Host, Node, DocRoot)-> + {ok, FileInfo} = + file:read_file_info(filename:join([DocRoot,"index.html"])), + CreatedSec = + calendar:datetime_to_gregorian_seconds(FileInfo#file_info.mtime), + + Mod = httpd_util:rfc1123_date(calendar:gregorian_seconds_to_datetime( + CreatedSec-1)), + + %% Test that we get the data when the file is modified + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "GET / HTTP/1.1\r\nHost:" ++ Host ++ + "\r\nIf-Modified-Since:" ++ + Mod ++ "\r\n\r\n", + [{statuscode, 200}]), + Mod1 = httpd_util:rfc1123_date(calendar:gregorian_seconds_to_datetime( + CreatedSec+100)), + ok = httpd_test_lib:verify_request(Type,Host,Port,Node, + "GET / HTTP/1.1\r\nHost:" + ++ Host ++"\r\nIf-Modified-Since:" + ++ Mod1 ++"\r\n\r\n", + [{statuscode, 304}]), + + Mod2 = httpd_util:rfc1123_date(calendar:gregorian_seconds_to_datetime( + CreatedSec+1)), + %% Control that the If-Unmodified-Header lmits the response + ok = httpd_test_lib:verify_request(Type,Host,Port,Node, + "GET / HTTP/1.1\r\nHost:" + ++ Host ++ + "\r\nIf-Unmodified-Since:" ++ Mod2 + ++ "\r\n\r\n", + [{statuscode, 200}]), + Mod3 = httpd_util:rfc1123_date(calendar:gregorian_seconds_to_datetime( + CreatedSec-1)), + + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "GET / HTTP/1.1\r\nHost:" + ++ Host ++ + "\r\nIf-Unmodified-Since:"++ Mod3 + ++"\r\n\r\n", + [{statuscode, 412}]), + + %% Control that we get the body when the etag match + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "GET / HTTP/1.1\r\nHost:" ++ Host + ++"\r\n"++ + "If-Match:"++ + httpd_util:create_etag(FileInfo)++ + "\r\n\r\n", + [{statuscode, 200}]), + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "GET / HTTP/1.1\r\nHost:" ++ + Host ++ "\r\n"++ + "If-Match:NotEtag\r\n\r\n", + [{statuscode, 412}]), + + %% Control the response when the if-none-match header is there + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "GET / HTTP/1.1\r\nHost:" + ++ Host ++"\r\n"++ + "If-None-Match:NoTaag," ++ + httpd_util:create_etag(FileInfo) ++ + "\r\n\r\n", + [{statuscode, 304}]), + + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "GET / HTTP/1.1\r\nHost:" + ++ Host ++ "\r\n"++ + "If-None-Match:NotEtag," + "NeihterEtag\r\n\r\n", + [{statuscode,200}]). + +http_trace(Type, Port, Host, Node)-> + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "TRACE / HTTP/1.1\r\n" ++ + "Host:" ++ Host ++ "\r\n" ++ + "Max-Forwards:2\r\n\r\n", + [{statuscode, 200}]), + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "TRACE / HTTP/1.0\r\n\r\n", + [{statuscode, 501}, + {version, "HTTP/1.0"}]). +head(Type, Port, Host, Node)-> + %% mod_include + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "HEAD /fsize.shtml HTTP/1.0\r\n\r\n", + [{statuscode, 200}, + {version, "HTTP/1.0"}]), + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "HEAD /fsize.shtml HTTP/1.1\r\nhost:" ++ + Host ++ "\r\n\r\n", [{statuscode, 200}]), + %% mod_esi + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "HEAD /cgi-bin/erl/httpd_example/newformat" + " HTTP/1.0\r\n\r\n", [{statuscode, 200}, + {version, "HTTP/1.0"}]), + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, + "HEAD /cgi-bin/erl/httpd_example/newformat " + "HTTP/1.1\r\nhost:" ++ Host ++ "\r\n\r\n", + [{statuscode, 200}]), + %% mod_cgi + Script = + case test_server:os_type() of + {win32, _} -> + "printenv.bat"; + _ -> + "printenv.sh" + end, + ok = httpd_test_lib:verify_request(Type,Host,Port,Node,"HEAD /cgi-bin/" + ++ Script ++ " HTTP/1.0\r\n\r\n", + [{statuscode, 200}, + {version, "HTTP/1.0"}]), + ok = httpd_test_lib:verify_request(Type,Host,Port,Node, "HEAD /cgi-bin/" + ++ Script ++ " HTTP/1.1\r\nhost:" ++ + Host ++ "\r\n\r\n", + [{statuscode, 200}]). + +mod_cgi_chunked_encoding_test(_, _, _, _, [])-> + ok; +mod_cgi_chunked_encoding_test(Type, Port, Host, Node, [Request| Rest])-> + ok = httpd_test_lib:verify_request(Type, Host, Port, Node, Request, + [{statuscode, 200}]), + mod_cgi_chunked_encoding_test(Type, Port, Host, Node, Rest). + +%%-------------------------------------------------------------------- +%% Internal functions +%%-------------------------------------------------------------------- +validateRangeRequest(Socket,Response,ValidBody,C,O,DE)-> + receive + {tcp,Socket,Data} -> + case string:str(Data,"\r\n") of + 0-> + validateRangeRequest(Socket, + Response ++ Data, + ValidBody, C, O, DE); + _N -> + case Response ++ Data of + [$H,$T,$T,$P,$/,$1,$.,$1,$ ,C,O,DE | _Rest]-> + case [C,O,DE] of + "206" -> + validateRangeRequest1(Socket, + Response ++ Data, + ValidBody); + _ -> + bad_code + end; + _-> + error + end + end; + _Error -> + error + end. + +validateRangeRequest1(Socket, Response, ValidBody) -> + case end_of_header(Response) of + false -> + receive + {tcp,Socket,Data} -> + validateRangeRequest1(Socket, Response ++ Data, + ValidBody); + _-> + error + end; + {true, Head1, Body, _Size} -> + %% In this case size will be 0 if it is a multipart so + %% don't use it. + validateRangeRequest2(Socket, Head1, Body, ValidBody, + getRangeSize(Head1)) + end. + +validateRangeRequest2(Socket, Head, Body, ValidBody, {multiPart,Boundary})-> + case endReached(Body,Boundary) of + true -> + validateMultiPartRangeRequest(Body, ValidBody, Boundary); + false-> + receive + {tcp, Socket, Data} -> + validateRangeRequest2(Socket, Head, Body ++ Data, + ValidBody, {multiPart, Boundary}); + {tcp_closed, Socket} -> + error; + _ -> + error + end + end; + +validateRangeRequest2(Socket, Head, Body, ValidBody, BodySize) + when is_integer(BodySize) -> + case length(Body) of + Size when Size =:= BodySize -> + case Body of + ValidBody -> + ok; + Body -> + error + end; + Size when Size < BodySize -> + receive + {tcp, Socket, Data} -> + validateRangeRequest2(Socket, Head, + Body ++ Data, ValidBody, BodySize); + _ -> + error + end; + _ -> + error + end. + + +validateMultiPartRangeRequest(Body, ValidBody, Boundary)-> + case inets_regexp:split(Body,"--"++Boundary++"--") of + %%Last is the epilogue and must be ignored + {ok,[First | _Last]}-> + %%First is now the actuall http request body. + case inets_regexp:split(First, "--" ++ Boundary) of + %%Parts is now a list of ranges and the heads for each range + %%Gues we try to split out the body + {ok,Parts}-> + case lists:flatten(lists:map(fun splitRange/1,Parts)) of + ValidBody-> + ok; + ParsedBody-> + error = ParsedBody + end + end; + _ -> + error + end. + + +splitRange(Part)-> + case inets_regexp:split(Part, "\r\n\r\n") of + {ok,[_, Body]} -> + string:substr(Body, 1, length(Body) - 2); + _ -> + [] + end. + +endReached(Body, Boundary)-> + EndBound = "--" ++ Boundary ++ "--", + case string:str(Body, EndBound) of + 0 -> + false; + _ -> + true + end. + +getRangeSize(Head)-> + case controlMimeType(Head) of + {multiPart, BoundaryString}-> + {multiPart, BoundaryString}; + _X1 -> + case inets_regexp:match(Head, ?CONTENT_RANGE "bytes=.*\r\n") of + {match, Start, Lenght} -> + %% Get the range data remove the fieldname and the + %% end of line. + RangeInfo = string:substr(Head, Start + 20, + Lenght - (20 - 2)), + rangeSize(RangeInfo); + _X2 -> + error + end + end. +%%RangeInfo is NNN1-NNN2/NNN3 +%%NNN1=RangeStartByte +%%NNN2=RangeEndByte +%%NNN3=total amount of bytes in file +rangeSize([$=|RangeInfo]) -> + rangeSize(RangeInfo); +rangeSize(RangeInfo) -> + StartByte = lists:takewhile(fun(X)-> + num(X, true) + end, RangeInfo), + RangeInfo2 = string:substr(RangeInfo, length(StartByte) + 2), + EndByte = lists:takewhile(fun(X)-> + num(X,true) + end, RangeInfo2), + case list_to_integer(EndByte) - list_to_integer(StartByte) of + Val when is_number(Val) -> + %%Add one since it is startByte to endbyte ie 0-0 is 1 + %%byte 0-99 is 100 bytes + Val + 1; + _Val -> + error + end. + +num(CharVal, RetVal) when (CharVal >= 48) andalso (CharVal =< 57) -> + RetVal; +num(_CharVal, true) -> + false; +num(_CharVal, false) -> + true. + +controlMimeType(Head)-> + case inets_regexp:match(Head,?CONTENT_TYPE "multipart/byteranges.*\r\n") of + {match,Start,Length}-> + FieldNameLen = length(?CONTENT_TYPE "multipart/byteranges"), + case clearBoundary(string:substr(Head, Start + FieldNameLen, + Length - (FieldNameLen+2))) of + error -> + error; + BoundaryStr -> + {multiPart,BoundaryStr} + end; + nomatch-> + 0; + _ -> + error + end. + +clearBoundary(Boundary)-> + case inets_regexp:match(Boundary, "boundary=.*\$") of + {match, Start1, Length1}-> + BoundLen = length("boundary="), + string:substr(Boundary, Start1 + BoundLen, Length1 - BoundLen); + _ -> + error + end. + + +end_of_header(HeaderPart) -> + case httpd_util:split(HeaderPart,"\r\n\r\n",2) of + {ok, [Head, Body]} -> + {true, Head, Body, get_body_size(Head)}; + _Pos -> + false + end. + +get_body_size(Head) -> + case inets_regexp:match(Head,?CONTENT_LENGTH ".*\r\n") of + {match, Start, Length} -> + %% 15 is length of Content-Length, + %% 17 Is length of Content-Length and \r\ + S = list_to_integer( + string:strip(string:substr(Head, Start + 15, Length-17))), + S; + _-> + 0 + end. -- cgit v1.2.3