a368755978f6ebc820e835d3c3f6c2de6e909f72
[darkhttpd] / devel / test.py
1 #!/usr/bin/env python
2 import unittest
3 import socket
4 import signal
5 import re
6 import os
7
8 WWWROOT = "tmp.httpd.tests"
9
10 class Conn:
11 def __init__(self):
12 self.port = 12346
13 self.s = socket.socket()
14 self.s.connect(("0.0.0.0", self.port))
15 # connect throws socket.error on connection refused
16
17 def get(self, url, http_ver="1.0", endl="\n",
18 req_hdrs={"User-Agent": "test.py"}):
19 req = "GET "+url
20 if http_ver is not None:
21 req += " HTTP/"+http_ver
22 req += "\n"
23 for k,v in req_hdrs.items():
24 req += k+": "+v+endl
25 req += endl # end of request
26 self.s.send(req)
27 ret = ""
28 while True:
29 signal.alarm(1) # don't wait forever
30 r = self.s.recv(65536)
31 signal.alarm(0)
32 if r == "":
33 break
34 else:
35 ret += r
36 return ret
37
38 def parse(resp):
39 """
40 Parse response into status line, headers and body.
41 """
42 pos = resp.index("\r\n\r\n") # throws exception on failure
43 head = resp[:pos]
44 body = resp[pos+4:]
45 status,head = head.split("\r\n", 1)
46 hdrs = {}
47 for line in head.split("\r\n"):
48 k, v = line.split(": ", 1)
49 hdrs[k] = v
50 return (status, hdrs, body)
51
52 class TestCases(unittest.TestCase):
53 def assertContains(self, body, *strings):
54 for s in strings:
55 self.assertTrue(s in body,
56 msg="expected %s in %s"%(repr(s), repr(body)))
57
58 def assertIsIndex(self, body, path):
59 self.assertContains(body,
60 "<title>%s</title>\n"%path,
61 "<h1>%s</h1>\n"%path,
62 '<a href="..">..</a>/',
63 'Generated by darkhttpd')
64
65 def assertIsInvalid(self, body, path):
66 self.assertContains(body,
67 "<title>400 Bad Request</title>",
68 "<h1>Bad Request</h1>\n",
69 "You requested an invalid URL: %s\n"%path,
70 'Generated by darkhttpd')
71
72 def test_dirlist_escape(self):
73 fn = WWWROOT+"/escape#this"
74 open(fn, "w").write("x"*12345)
75 try:
76 resp = Conn().get("/")
77 finally:
78 os.unlink(fn)
79 status, hdrs, body = parse(resp)
80 self.assertEquals(ord("#"), 0x23)
81 self.assertContains(body, "escape%23this", "12345")
82
83 def nerf(s):
84 return re.sub("[^a-zA-Z0-9]", "_", s)
85
86 def makeCase(name, url, hdr_checker=None, body_checker=None,
87 req_hdrs={"User-Agent": "test.py"},
88 http_ver=None, endl="\n"):
89 def do_test(self):
90 resp = Conn().get(url, http_ver, endl, req_hdrs)
91 if http_ver is None:
92 status = ""
93 hdrs = {}
94 body = resp
95 else:
96 status, hdrs, body = parse(resp)
97
98 if hdr_checker is not None and http_ver is not None:
99 hdr_checker(self, hdrs)
100
101 if body_checker is not None:
102 body_checker(self, body)
103
104 # FIXME: check status
105 if http_ver is not None:
106 prefix = "HTTP/1.1 " # should 1.0 stay 1.0?
107 self.assertTrue(status.startswith(prefix),
108 msg="%s at start of %s"%(repr(prefix), repr(status)))
109
110 v = http_ver
111 if v is None:
112 v = "0.9"
113 test_name = "_".join([
114 "test",
115 nerf(name),
116 nerf("HTTP"+v),
117 {"\n":"LF", "\r\n":"CRLF"}[endl],
118 ])
119 do_test.__name__ = test_name # hax
120 setattr(TestCases, test_name, do_test)
121
122 def makeCases(name, url, hdr_checker=None, body_checker=None,
123 req_hdrs={"User-Agent": "test.py"}):
124 # FIXME: 0.9 is broken
125 for http_ver in [None, "1.0", "1.1"]:
126 #for http_ver in ["1.0", "1.1"]:
127 for endl in ["\n", "\r\n"]:
128 makeCase(name, url, hdr_checker, body_checker,
129 req_hdrs, http_ver, endl)
130
131 def makeSimpleCases(name, url, assert_name):
132 makeCases(name, url, None,
133 lambda self,body: getattr(self, assert_name)(body, url))
134
135 def setUpModule():
136 for args in [
137 ["index", "/", "assertIsIndex"],
138 ["up dir", "/dir/../", "assertIsIndex"],
139 ["extra slashes", "//dir///..////", "assertIsIndex"],
140 ["no trailing slash", "/dir/..", "assertIsIndex"],
141 ["no leading slash", "dir/../", "assertIsInvalid"],
142 ["invalid up dir", "/../", "assertIsInvalid"],
143 ["fancy invalid up dir", "/./dir/./../../", "assertIsInvalid"],
144 ]:
145 makeSimpleCases(*args)
146
147 if __name__ == '__main__':
148 setUpModule()
149 unittest.main()
150 #x = Conn().get("/xyz/../", "1.0")
151 #y = parse(x)
152 #print repr(y)
153
154 # vim:set ts=4 sw=4 et: