1#!/usr/bin/env perl
2BEGIN {
3	# add current source dir to the include-path
4	# we need this for make distcheck
5	(my $srcdir = $0) =~ s,/[^/]+$,/,;
6	unshift @INC, $srcdir;
7}
8
9use strict;
10use IO::Socket;
11use Test::More tests => 52;
12use LightyTest;
13
14my $tf = LightyTest->new();
15my $t;
16
17ok($tf->start_proc == 0, "Starting lighttpd") or die();
18
19## Basic Request-Handling
20
21$t->{REQUEST}  = ( <<EOF
22GET /foobar HTTP/1.0
23EOF
24 );
25$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 404 } ];
26ok($tf->handle_http($t) == 0, 'file not found');
27
28$t->{REQUEST}  = ( <<EOF
29GET /foobar?foobar HTTP/1.0
30EOF
31 );
32$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 404 } ];
33ok($tf->handle_http($t) == 0, 'file not found + querystring');
34
35$t->{REQUEST}  = ( <<EOF
36GET /12345.txt HTTP/1.0
37Host: 123.example.org
38EOF
39 );
40$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/plain' } ];
41ok($tf->handle_http($t) == 0, 'GET, content == 12345, mimetype text/plain');
42
43$t->{REQUEST}  = ( <<EOF
44GET /12345.html HTTP/1.0
45Host: 123.example.org
46EOF
47 );
48$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/html' } ];
49ok($tf->handle_http($t) == 0, 'GET, content == 12345, mimetype text/html');
50
51$t->{REQUEST}  = ( <<EOF
52GET /dummyfile.bla HTTP/1.0
53Host: 123.example.org
54EOF
55 );
56$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'application/octet-stream' } ];
57ok($tf->handle_http($t) == 0, 'GET, content == 12345, mimetype application/octet-stream');
58
59$t->{REQUEST}  = ( <<EOF
60POST / HTTP/1.0
61EOF
62 );
63$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 411 } ];
64ok($tf->handle_http($t) == 0, 'POST request, no Content-Length');
65
66
67$t->{REQUEST}  = ( <<EOF
68POST / HTTP/1.0
69Content-type: application/x-www-form-urlencoded
70Content-length: 0
71EOF
72 );
73$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ];
74ok($tf->handle_http($t) == 0, 'POST request, empty request-body');
75
76$t->{REQUEST}  = ( <<EOF
77HEAD / HTTP/1.0
78EOF
79 );
80$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, '-HTTP-Content' => ''} ];
81ok($tf->handle_http($t) == 0, 'HEAD request, no content');
82
83$t->{REQUEST}  = ( <<EOF
84HEAD /12345.html HTTP/1.0
85Host: 123.example.org
86EOF
87 );
88$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, '-HTTP-Content' => '', 'Content-Type' => 'text/html', 'Content-Length' => '6'} ];
89ok($tf->handle_http($t) == 0, 'HEAD request, mimetype text/html, content-length');
90
91$t->{REQUEST}  = ( <<EOF
92HEAD http://123.example.org/12345.html HTTP/1.1
93Connection: close
94EOF
95 );
96$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, '-HTTP-Content' => '', 'Content-Type' => 'text/html', 'Content-Length' => '6'} ];
97ok($tf->handle_http($t) == 0, 'Hostname in first line, HTTP/1.1');
98
99$t->{REQUEST}  = ( <<EOF
100HEAD https://123.example.org/12345.html HTTP/1.0
101EOF
102 );
103$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, '-HTTP-Content' => '', 'Content-Type' => 'text/html', 'Content-Length' => '6'} ];
104ok($tf->handle_http($t) == 0, 'Hostname in first line as https url');
105
106$t->{REQUEST}  = ( <<EOF
107HEAD /foobar?foobar HTTP/1.0
108EOF
109 );
110$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 404, '-HTTP-Content' => '' } ];
111ok($tf->handle_http($t) == 0, 'HEAD request, file-not-found, query-string');
112
113$t->{REQUEST}  = ( <<EOF
114GET / HTTP/1.1
115Connection: close
116Expect: 100-continue
117EOF
118 );
119$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 417 } ];
120ok($tf->handle_http($t) == 0, 'Continue, Expect');
121
122## ranges
123
124$t->{REQUEST}  = ( <<EOF
125GET /12345.txt HTTP/1.0
126Host: 123.example.org
127Range: bytes=0-3
128EOF
129 );
130$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 206, 'HTTP-Content' => '1234' } ];
131ok($tf->handle_http($t) == 0, 'GET, Range 0-3');
132
133$t->{REQUEST}  = ( <<EOF
134GET /12345.txt HTTP/1.0
135Host: 123.example.org
136Range: bytes=-3
137EOF
138 );
139$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 206, 'HTTP-Content' => '45'."\n" } ];
140ok($tf->handle_http($t) == 0, 'GET, Range -3');
141
142$t->{REQUEST}  = ( <<EOF
143GET /12345.txt HTTP/1.0
144Host: 123.example.org
145Range: bytes=3-
146EOF
147 );
148$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 206, 'HTTP-Content' => '45'."\n" } ];
149ok($tf->handle_http($t) == 0, 'GET, Range 3-');
150
151$t->{REQUEST}  = ( <<EOF
152GET /12345.txt HTTP/1.0
153Host: 123.example.org
154Range: bytes=0-1,3-4
155EOF
156 );
157$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 206, 'HTTP-Content' => <<EOF
158\r
159--fkj49sn38dcn3\r
160Content-Range: bytes 0-1/6\r
161Content-Type: text/plain\r
162\r
16312\r
164--fkj49sn38dcn3\r
165Content-Range: bytes 3-4/6\r
166Content-Type: text/plain\r
167\r
16845\r
169--fkj49sn38dcn3--\r
170EOF
171 } ];
172ok($tf->handle_http($t) == 0, 'GET, Range 0-1,3-4');
173
174$t->{REQUEST}  = ( <<EOF
175GET /12345.txt HTTP/1.0
176Host: 123.example.org
177Range: bytes=0--
178EOF
179 );
180$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ];
181ok($tf->handle_http($t) == 0, 'GET, Range 0--');
182
183$t->{REQUEST}  = ( <<EOF
184GET /12345.txt HTTP/1.0
185Host: 123.example.org
186Range: bytes=-2-3
187EOF
188 );
189$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ];
190ok($tf->handle_http($t) == 0, 'GET, Range -2-3');
191
192$t->{REQUEST}  = ( <<EOF
193GET /12345.txt HTTP/1.0
194Host: 123.example.org
195Range: bytes=-0
196EOF
197 );
198$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 416, 'HTTP-Content' => <<EOF
199<?xml version="1.0" encoding="iso-8859-1"?>
200<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
201         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
202<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
203 <head>
204  <title>416 - Requested Range Not Satisfiable</title>
205 </head>
206 <body>
207  <h1>416 - Requested Range Not Satisfiable</h1>
208 </body>
209</html>
210EOF
211 } ];
212ok($tf->handle_http($t) == 0, 'GET, Range -0');
213
214$t->{REQUEST}  = ( <<EOF
215GET /12345.txt HTTP/1.0
216Host: 123.example.org
217Range: bytes=25-
218EOF
219 );
220$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 416, 'HTTP-Content' => <<EOF
221<?xml version="1.0" encoding="iso-8859-1"?>
222<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
223         "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
224<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
225 <head>
226  <title>416 - Requested Range Not Satisfiable</title>
227 </head>
228 <body>
229  <h1>416 - Requested Range Not Satisfiable</h1>
230 </body>
231</html>
232EOF
233 } ];
234
235ok($tf->handle_http($t) == 0, 'GET, Range start out of range');
236
237
238$t->{REQUEST}  = ( <<EOF
239GET / HTTP/1.0
240Hsgfsdjf: asdfhdf
241hdhd: shdfhfdasd
242hfhr: jfghsdfg
243jfuuehdmn: sfdgjfdg
244jvcbzufdg: sgfdfg
245hrnvcnd: jfjdfg
246jfusfdngmd: gfjgfdusdfg
247nfj: jgfdjdfg
248jfue: jfdfdg
249EOF
250 );
251$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ];
252ok($tf->handle_http($t) == 0, 'larger headers');
253
254
255$t->{REQUEST}  = ( <<EOF
256GET / HTTP/1.0
257Host: www.example.org
258Host: 123.example.org
259EOF
260 );
261$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 400 } ];
262ok($tf->handle_http($t) == 0, 'Duplicate Host headers, Bug #25');
263
264
265$t->{REQUEST}  = ( <<EOF
266GET / HTTP/1.0
267Content-Length: 5
268Content-Length: 4
269EOF
270 );
271$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 400 } ];
272ok($tf->handle_http($t) == 0, 'Duplicate Content-Length headers');
273
274$t->{REQUEST}  = ( <<EOF
275GET / HTTP/1.0
276Content-Type: 5
277Content-Type: 4
278EOF
279 );
280$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 400 } ];
281ok($tf->handle_http($t) == 0, 'Duplicate Content-Type headers');
282
283$t->{REQUEST}  = ( <<EOF
284GET / HTTP/1.0
285Range: bytes=5-6
286Range: bytes=5-9
287EOF
288 );
289$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 400 } ];
290ok($tf->handle_http($t) == 0, 'Duplicate Range headers');
291
292$t->{REQUEST}  = ( <<EOF
293GET / HTTP/1.0
294If-None-Match: 5
295If-None-Match: 4
296EOF
297 );
298$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ];
299ok($tf->handle_http($t) == 0, 'Duplicate If-None-Match headers');
300
301$t->{REQUEST}  = ( <<EOF
302GET / HTTP/1.0
303If-Modified-Since: 5
304If-Modified-Since: 4
305EOF
306 );
307$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 400 } ];
308ok($tf->handle_http($t) == 0, 'Duplicate If-Modified-Since headers');
309
310$t->{REQUEST}  = ( <<EOF
311GET /range.pdf HTTP/1.0
312Range: bytes=0-
313EOF
314 );
315$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ];
316ok($tf->handle_http($t) == 0, 'GET, Range with range-requests-disabled');
317
318$t->{REQUEST}  = ( <<EOF
319GET / HTTP/1.0
320Content-Length: 4
321
3221234
323EOF
324 );
325$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 400 } ];
326ok($tf->handle_http($t) == 0, 'GET with Content-Length');
327
328$t->{REQUEST}  = ( <<EOF
329OPTIONS / HTTP/1.0
330Content-Length: 4
331
3321234
333EOF
334 );
335$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ];
336ok($tf->handle_http($t) == 0, 'OPTIONS with Content-Length');
337
338$t->{REQUEST}  = ( <<EOF
339OPTIONS rtsp://221.192.134.146:80 RTSP/1.1
340Host: 221.192.134.146:80
341EOF
342 );
343$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 400 } ];
344ok($tf->handle_http($t) == 0, 'OPTIONS for RTSP');
345
346$t->{REQUEST}  = ( <<EOF
347HEAD / HTTP/1.0
348Content-Length: 4
349
3501234
351EOF
352 );
353$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 400 } ];
354ok($tf->handle_http($t) == 0, 'HEAD with Content-Length');
355
356$t->{REQUEST}  = ( <<EOF
357GET /index.html HTTP/1.0
358If-Modified-Since: Sun, 01 Jan 2036 00:00:02 GMT
359If-Modified-Since: Sun, 01 Jan 2036 00:00:02 GMT
360EOF
361 );
362$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 304 } ];
363ok($tf->handle_http($t) == 0, 'Duplicate If-Mod-Since, with equal timestamps');
364
365$t->{REQUEST}  = ( "GET / HTTP/1.0\r\nIf-Modified-Since: \0\r\n\r\n" );
366$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 400 } ];
367ok($tf->handle_http($t) == 0, 'invalid chars in Header values (bug #1286)');
368
369$t->{REQUEST}  = ( "GET / HTTP/1.0\r\nIf-Modified-Since: \r\n\r\n" );
370$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ];
371ok($tf->handle_http($t) == 0, 'empty If-Modified-Since');
372
373$t->{REQUEST}  = ( "GET / HTTP/1.0\r\nIf-Modified-Since: foobar\r\n\r\n" );
374$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ];
375ok($tf->handle_http($t) == 0, 'broken If-Modified-Since');
376
377$t->{REQUEST}  = ( "GET / HTTP/1.0\r\nIf-Modified-Since: this string is too long to be a valid timestamp\r\n\r\n" );
378$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200 } ];
379ok($tf->handle_http($t) == 0, 'broken If-Modified-Since');
380
381
382$t->{REQUEST}  = ( <<EOF
383GET /index.html HTTP/1.0
384If-Modified-Since2: Sun, 01 Jan 2036 00:00:03 GMT
385If-Modified-Since: Sun, 01 Jan 2036 00:00:02 GMT
386EOF
387 );
388$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 304 } ];
389ok($tf->handle_http($t) == 0, 'Similar Headers (bug #1287)');
390
391$t->{REQUEST}  = ( <<EOF
392GET /index.html HTTP/1.0
393If-Modified-Since: Sun, 01 Jan 2036 00:00:02 GMT
394EOF
395 );
396$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 304, 'Content-Type' => 'text/html' } ];
397ok($tf->handle_http($t) == 0, 'If-Modified-Since');
398
399$t->{REQUEST}  = ( <<EOF
400GET /index.html HTTP/1.0
401If-Modified-Since: Sun, 01 Jan 2036 00:00:02 GMT
402EOF
403 );
404$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 304, '-Content-Length' => '' } ];
405ok($tf->handle_http($t) == 0, 'Status 304 has no Content-Length (#1002)');
406
407$t->{REQUEST}  = ( <<EOF
408GET /12345.txt HTTP/1.0
409Host: 123.example.org
410EOF
411 );
412$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/plain' } ];
413$t->{SLOWREQUEST} = 1;
414ok($tf->handle_http($t) == 0, 'GET, slow \\r\\n\\r\\n (#2105)');
415
416print "\nPathinfo for static files\n";
417$t->{REQUEST}  = ( <<EOF
418GET /image.jpg/index.php HTTP/1.0
419EOF
420 );
421$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 200, 'Content-Type' => 'image/jpeg' } ];
422ok($tf->handle_http($t) == 0, 'static file accepting pathinfo by default');
423
424$t->{REQUEST}  = ( <<EOF
425GET /image.jpg/index.php HTTP/1.0
426Host: zzz.example.org
427EOF
428 );
429$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.0', 'HTTP-Status' => 403 } ];
430ok($tf->handle_http($t) == 0, 'static file with forbidden pathinfo');
431
432
433print "\nConnection header\n";
434$t->{REQUEST}  = ( <<EOF
435GET /12345.txt HTTP/1.1
436Connection  : close
437Host: 123.example.org
438EOF
439 );
440$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/plain', 'Connection' => 'close' } ];
441ok($tf->handle_http($t) == 0, 'Connection-header, spaces before ":"');
442
443$t->{REQUEST}  = ( <<EOF
444GET /12345.txt HTTP/1.1
445Connection: ,close
446Host: 123.example.org
447EOF
448 );
449$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/plain', 'Connection' => 'close' } ];
450ok($tf->handle_http($t) == 0, 'Connection-header, leading comma');
451
452$t->{REQUEST}  = ( <<EOF
453GET /12345.txt HTTP/1.1
454Connection: close,,TE
455Host: 123.example.org
456EOF
457 );
458$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/plain', 'Connection' => 'close' } ];
459ok($tf->handle_http($t) == 0, 'Connection-header, no value between two commas');
460
461$t->{REQUEST}  = ( <<EOF
462GET /12345.txt HTTP/1.1
463Connection: close, ,TE
464Host: 123.example.org
465EOF
466 );
467$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/plain', 'Connection' => 'close' } ];
468ok($tf->handle_http($t) == 0, 'Connection-header, space between two commas');
469
470$t->{REQUEST}  = ( <<EOF
471GET /12345.txt HTTP/1.1
472Connection: close,
473Host: 123.example.org
474EOF
475 );
476$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/plain', 'Connection' => 'close' } ];
477ok($tf->handle_http($t) == 0, 'Connection-header, comma after value');
478
479$t->{REQUEST}  = ( <<EOF
480GET /12345.txt HTTP/1.1
481Connection: close,
482Host: 123.example.org
483EOF
484 );
485$t->{RESPONSE} = [ { 'HTTP-Protocol' => 'HTTP/1.1', 'HTTP-Status' => 200, 'HTTP-Content' => '12345'."\n", 'Content-Type' => 'text/plain', 'Connection' => 'close' } ];
486ok($tf->handle_http($t) == 0, 'Connection-header, comma and space after value');
487
488ok($tf->stop_proc == 0, "Stopping lighttpd");
489
490