4
%% Copyright Ericsson AB 2005-2010. All Rights Reserved.
6
%% The contents of this file are subject to the Erlang Public License,
7
%% Version 1.1, (the "License"); you may not use this file except in
8
%% compliance with the License. You should have received a copy of the
9
%% Erlang Public License along with this software. If not, it can be
10
%% retrieved online at http://www.erlang.org/.
12
%% Software distributed under the License is distributed on an "AS IS"
13
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
14
%% the License for the specific language governing rights and limitations
22
-author('ingela@erix.ericsson.se').
24
-include("test_server.hrl").
25
-include("test_server_line.hrl").
26
-include_lib("kernel/include/file.hrl").
28
-export([host/4, chunked/4, expect/4, range/4, if_test/5, http_trace/4,
29
head/4, mod_cgi_chunked_encoding_test/5]).
31
%% -define(all_keys_lower_case,true).
32
-ifndef(all_keys_lower_case).
33
-define(CONTENT_LENGTH, "Content-Length: ").
34
-define(CONTENT_RANGE, "Content-Range: ").
35
-define(CONTENT_TYPE, "Content-Type: ").
37
-define(CONTENT_LENGTH, "content-length:").
38
-define(CONTENT_RANGE, "content-range:").
39
-define(CONTENT_TYPE, "content-type:").
43
%%-------------------------------------------------------------------------
44
%% Test cases starts here.
45
%%-------------------------------------------------------------------------
46
host(Type, Port, Host, Node) ->
47
%% No host needed for HTTP/1.0
48
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
49
"GET / HTTP/1.0\r\n\r\n",
51
{version, "HTTP/1.0"}]),
52
%% No host must generate an error
53
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
54
"GET / HTTP/1.1\r\n\r\n",
57
%% If it is a fully qualified URL no host is needed
58
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
59
"GET HTTP://"++ Host ++ ":" ++
60
integer_to_list(Port)++
64
%% If both look at the url.
65
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
66
"GET HTTP://"++ Host ++ ":"++
67
integer_to_list(Port) ++
68
"/ HTTP/1.1\r\nHost:"++ Host ++
69
"\r\n\r\n",[{statuscode, 200}]),
71
%% Allow the request if its a Host field
72
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
73
"GET / HTTP/1.1\r\nHost:"++
78
chunked(Type, Port, Host, Node)->
79
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
81
"Host:"++ Host ++"\r\n"
82
"Transfer-Encoding:chunked\r\n"
91
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
93
"Host:"++ Host ++"\r\n"
94
"Transfer-Encoding:chunked\r\n"
95
"Trailer:Content-Type\r\n"
102
"Content-Type:text/plain\r\n\r\n",
103
[{statuscode, 200}]),
106
expect(Type, Port, Host, Node)->
107
Request="GET / HTTP/1.1\r\nHost:" ++ Host ++
108
"\r\nContent-Length:22\r\nExpect:100-continue\r\n\r\n",
110
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
112
[{statuscode, 100}]).
113
range(Type, Port, Host, Node)->
115
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
116
"GET /range.txt HTTP/1.1\r\nHost:"
118
++ "\r\nRange:bytes=110-120\r\n\r\n",
120
%%The simples of all range request a range
121
Request1="GET /range.txt HTTP/1.1\r\nHost:" ++ Host ++
122
"\r\nRange:bytes=0-9\r\n\r\n",
123
{ok, Socket1} = inets_test_lib:connect_byte(Type, Host, Port),
124
inets_test_lib:send(Type, Socket1,Request1),
125
ok = validateRangeRequest(Socket1,[],"1234567890",$2,$0,$6),
126
inets_test_lib:close(Type,Socket1),
128
%% Request the end of the file
130
"GET /range.txt HTTP/1.1\r\nHost:" ++ Host ++
131
"\r\nRange:bytes=90-\r\n\r\n",
133
{ok, Socket2} = inets_test_lib:connect_byte(Type, Host, Port),
134
inets_test_lib:send(Type, Socket2, Request2),
135
ok = validateRangeRequest(Socket2,[],"1234567890",$2,$0,$6),
136
inets_test_lib:close(Type,Socket2),
138
%% The last byte in the file
140
"GET /range.txt HTTP/1.1\r\nHost:"++
141
Host ++ "\r\nRange:bytes=-1\r\n\r\n",
142
{ok, Socket3} = inets_test_lib:connect_byte(Type, Host, Port),
143
inets_test_lib:send(Type, Socket3,Request3),
144
ok = validateRangeRequest(Socket3,[],"0",$2,$0,$6),
145
inets_test_lib:close(Type, Socket3),
148
Request4 = "GET /range.txt HTTP/1.1\r\nHost:" ++ Host ++
149
"\r\nRange:bytes=0-0,2-2,-1\r\n\r\n",
150
{ok, Socket4} = inets_test_lib:connect_byte(Type, Host, Port),
151
inets_test_lib:send(Type, Socket4, Request4),
152
ok = validateRangeRequest(Socket4,[],"130",$2,$0,$6),
153
inets_test_lib:close(Type, Socket4).
155
if_test(Type, Port, Host, Node, DocRoot)->
157
file:read_file_info(filename:join([DocRoot,"index.html"])),
159
calendar:datetime_to_gregorian_seconds(FileInfo#file_info.mtime),
161
Mod = httpd_util:rfc1123_date(calendar:gregorian_seconds_to_datetime(
164
%% Test that we get the data when the file is modified
165
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
166
"GET / HTTP/1.1\r\nHost:" ++ Host ++
167
"\r\nIf-Modified-Since:" ++
169
[{statuscode, 200}]),
170
Mod1 = httpd_util:rfc1123_date(calendar:gregorian_seconds_to_datetime(
172
ok = httpd_test_lib:verify_request(Type,Host,Port,Node,
173
"GET / HTTP/1.1\r\nHost:"
174
++ Host ++"\r\nIf-Modified-Since:"
175
++ Mod1 ++"\r\n\r\n",
176
[{statuscode, 304}]),
178
Mod2 = httpd_util:rfc1123_date(calendar:gregorian_seconds_to_datetime(
180
%% Control that the If-Unmodified-Header lmits the response
181
ok = httpd_test_lib:verify_request(Type,Host,Port,Node,
182
"GET / HTTP/1.1\r\nHost:"
184
"\r\nIf-Unmodified-Since:" ++ Mod2
186
[{statuscode, 200}]),
187
Mod3 = httpd_util:rfc1123_date(calendar:gregorian_seconds_to_datetime(
190
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
191
"GET / HTTP/1.1\r\nHost:"
193
"\r\nIf-Unmodified-Since:"++ Mod3
195
[{statuscode, 412}]),
197
%% Control that we get the body when the etag match
198
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
199
"GET / HTTP/1.1\r\nHost:" ++ Host
202
httpd_util:create_etag(FileInfo)++
204
[{statuscode, 200}]),
205
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
206
"GET / HTTP/1.1\r\nHost:" ++
208
"If-Match:NotEtag\r\n\r\n",
209
[{statuscode, 412}]),
211
%% Control the response when the if-none-match header is there
212
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
213
"GET / HTTP/1.1\r\nHost:"
215
"If-None-Match:NoTaag," ++
216
httpd_util:create_etag(FileInfo) ++
218
[{statuscode, 304}]),
220
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
221
"GET / HTTP/1.1\r\nHost:"
223
"If-None-Match:NotEtag,"
224
"NeihterEtag\r\n\r\n",
227
http_trace(Type, Port, Host, Node)->
228
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
229
"TRACE / HTTP/1.1\r\n" ++
230
"Host:" ++ Host ++ "\r\n" ++
231
"Max-Forwards:2\r\n\r\n",
232
[{statuscode, 200}]),
233
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
234
"TRACE / HTTP/1.0\r\n\r\n",
236
{version, "HTTP/1.0"}]).
237
head(Type, Port, Host, Node)->
239
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
240
"HEAD /fsize.shtml HTTP/1.0\r\n\r\n",
242
{version, "HTTP/1.0"}]),
243
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
244
"HEAD /fsize.shtml HTTP/1.1\r\nhost:" ++
245
Host ++ "\r\n\r\n", [{statuscode, 200}]),
247
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
248
"HEAD /cgi-bin/erl/httpd_example/newformat"
249
" HTTP/1.0\r\n\r\n", [{statuscode, 200},
250
{version, "HTTP/1.0"}]),
251
ok = httpd_test_lib:verify_request(Type, Host, Port, Node,
252
"HEAD /cgi-bin/erl/httpd_example/newformat "
253
"HTTP/1.1\r\nhost:" ++ Host ++ "\r\n\r\n",
254
[{statuscode, 200}]),
257
case test_server:os_type() of
263
ok = httpd_test_lib:verify_request(Type,Host,Port,Node,"HEAD /cgi-bin/"
264
++ Script ++ " HTTP/1.0\r\n\r\n",
266
{version, "HTTP/1.0"}]),
267
ok = httpd_test_lib:verify_request(Type,Host,Port,Node, "HEAD /cgi-bin/"
268
++ Script ++ " HTTP/1.1\r\nhost:" ++
270
[{statuscode, 200}]).
272
mod_cgi_chunked_encoding_test(_, _, _, _, [])->
274
mod_cgi_chunked_encoding_test(Type, Port, Host, Node, [Request| Rest])->
275
ok = httpd_test_lib:verify_request(Type, Host, Port, Node, Request,
276
[{statuscode, 200}]),
277
mod_cgi_chunked_encoding_test(Type, Port, Host, Node, Rest).
279
%%--------------------------------------------------------------------
280
%% Internal functions
281
%%--------------------------------------------------------------------
282
validateRangeRequest(Socket,Response,ValidBody,C,O,DE)->
285
case string:str(Data,"\r\n") of
287
validateRangeRequest(Socket,
289
ValidBody, C, O, DE);
291
case Response ++ Data of
292
[$H,$T,$T,$P,$/,$1,$.,$1,$ ,C,O,DE | _Rest]->
295
validateRangeRequest1(Socket,
309
validateRangeRequest1(Socket, Response, ValidBody) ->
310
case end_of_header(Response) of
314
validateRangeRequest1(Socket, Response ++ Data,
319
{true, Head1, Body, _Size} ->
320
%% In this case size will be 0 if it is a multipart so
322
validateRangeRequest2(Socket, Head1, Body, ValidBody,
326
validateRangeRequest2(Socket, Head, Body, ValidBody, {multiPart,Boundary})->
327
case endReached(Body,Boundary) of
329
validateMultiPartRangeRequest(Body, ValidBody, Boundary);
332
{tcp, Socket, Data} ->
333
validateRangeRequest2(Socket, Head, Body ++ Data,
334
ValidBody, {multiPart, Boundary});
335
{tcp_closed, Socket} ->
342
validateRangeRequest2(Socket, Head, Body, ValidBody, BodySize)
343
when is_integer(BodySize) ->
345
Size when Size =:= BodySize ->
352
Size when Size < BodySize ->
354
{tcp, Socket, Data} ->
355
validateRangeRequest2(Socket, Head,
356
Body ++ Data, ValidBody, BodySize);
365
validateMultiPartRangeRequest(Body, ValidBody, Boundary)->
366
case inets_regexp:split(Body,"--"++Boundary++"--") of
367
%%Last is the epilogue and must be ignored
368
{ok,[First | _Last]}->
369
%%First is now the actuall http request body.
370
case inets_regexp:split(First, "--" ++ Boundary) of
371
%%Parts is now a list of ranges and the heads for each range
372
%%Gues we try to split out the body
374
case lists:flatten(lists:map(fun splitRange/1,Parts)) of
387
case inets_regexp:split(Part, "\r\n\r\n") of
389
string:substr(Body, 1, length(Body) - 2);
394
endReached(Body, Boundary)->
395
EndBound = "--" ++ Boundary ++ "--",
396
case string:str(Body, EndBound) of
404
case controlMimeType(Head) of
405
{multiPart, BoundaryString}->
406
{multiPart, BoundaryString};
408
case inets_regexp:match(Head, ?CONTENT_RANGE "bytes=.*\r\n") of
409
{match, Start, Lenght} ->
410
%% Get the range data remove the fieldname and the
412
RangeInfo = string:substr(Head, Start + 20,
414
rangeSize(RangeInfo);
419
%%RangeInfo is NNN1-NNN2/NNN3
420
%%NNN1=RangeStartByte
422
%%NNN3=total amount of bytes in file
423
rangeSize([$=|RangeInfo]) ->
424
rangeSize(RangeInfo);
425
rangeSize(RangeInfo) ->
426
StartByte = lists:takewhile(fun(X)->
429
RangeInfo2 = string:substr(RangeInfo, length(StartByte) + 2),
430
EndByte = lists:takewhile(fun(X)->
433
case list_to_integer(EndByte) - list_to_integer(StartByte) of
434
Val when is_number(Val) ->
435
%%Add one since it is startByte to endbyte ie 0-0 is 1
436
%%byte 0-99 is 100 bytes
442
num(CharVal, RetVal) when (CharVal >= 48) andalso (CharVal =< 57) ->
444
num(_CharVal, true) ->
446
num(_CharVal, false) ->
449
controlMimeType(Head)->
450
case inets_regexp:match(Head,?CONTENT_TYPE "multipart/byteranges.*\r\n") of
451
{match,Start,Length}->
452
FieldNameLen = length(?CONTENT_TYPE "multipart/byteranges"),
453
case clearBoundary(string:substr(Head, Start + FieldNameLen,
454
Length - (FieldNameLen+2))) of
458
{multiPart,BoundaryStr}
466
clearBoundary(Boundary)->
467
case inets_regexp:match(Boundary, "boundary=.*\$") of
468
{match, Start1, Length1}->
469
BoundLen = length("boundary="),
470
string:substr(Boundary, Start1 + BoundLen, Length1 - BoundLen);
476
end_of_header(HeaderPart) ->
477
case httpd_util:split(HeaderPart,"\r\n\r\n",2) of
478
{ok, [Head, Body]} ->
479
{true, Head, Body, get_body_size(Head)};
484
get_body_size(Head) ->
485
case inets_regexp:match(Head,?CONTENT_LENGTH ".*\r\n") of
486
{match, Start, Length} ->
487
%% 15 is length of Content-Length,
488
%% 17 Is length of Content-Length and \r\
490
string:strip(string:substr(Head, Start + 15, Length-17))),