9 WWWROOT
= "tmp.httpd.tests"
14 self
.s
= socket
.socket()
15 self
.s
.connect(("0.0.0.0", self
.port
))
16 # connect throws socket.error on connection refused
18 def get(self
, url
, http_ver
="1.0", endl
="\n", req_hdrs
={}, method
="GET"):
20 if http_ver
is not None:
21 req
+= " HTTP/"+http_ver
23 if http_ver
is not None:
24 req_hdrs
["User-Agent"] = "test.py"
25 req_hdrs
["Connection"] = "close"
26 for k
,v
in req_hdrs
.items():
28 req
+= endl
# end of request
32 signal
.alarm(1) # don't wait forever
33 r
= self
.s
.recv(65536)
43 Parse response into status line, headers and body.
45 pos
= resp
.index("\r\n\r\n") # throws exception on failure
48 status
,head
= head
.split("\r\n", 1)
50 for line
in head
.split("\r\n"):
51 k
, v
= line
.split(": ", 1)
53 return (status
, hdrs
, body
)
55 class TestHelper(unittest
.TestCase
):
56 def assertContains(self
, body
, *strings
):
58 self
.assertTrue(s
in body
,
59 msg
="expected %s in %s"%(repr(s
), repr(body
)))
61 def assertIsIndex(self
, body
, path
):
62 self
.assertContains(body
,
63 "<title>%s</title>\n"%path
,
65 '<a href="..">..</a>/',
66 'Generated by darkhttpd')
68 def assertIsInvalid(self
, body
, path
):
69 self
.assertContains(body
,
70 "<title>400 Bad Request</title>",
71 "<h1>Bad Request</h1>\n",
72 "You requested an invalid URL: %s\n"%path
,
73 'Generated by darkhttpd')
75 class TestDirList(TestHelper
):
77 self
.fn
= WWWROOT
+"/escape#this"
78 open(self
.fn
, "w").write("x"*12345)
83 def test_dirlist_escape(self
):
84 resp
= Conn().get("/")
85 status
, hdrs
, body
= parse(resp
)
86 self
.assertEquals(ord("#"), 0x23)
87 self
.assertContains(body
, "escape%23this", "12345")
89 class TestCases(TestHelper
):
90 pass # these get autogenerated in setUpModule()
93 return re
.sub("[^a-zA-Z0-9]", "_", s
)
95 def makeCase(name
, url
, hdr_checker
=None, body_checker
=None,
96 req_hdrs
={"User-Agent": "test.py"},
97 http_ver
=None, endl
="\n"):
99 resp
= Conn().get(url
, http_ver
, endl
, req_hdrs
)
105 status
, hdrs
, body
= parse(resp
)
107 if hdr_checker
is not None and http_ver
is not None:
108 hdr_checker(self
, hdrs
)
110 if body_checker
is not None:
111 body_checker(self
, body
)
113 # FIXME: check status
114 if http_ver
is not None:
115 prefix
= "HTTP/1.1 " # should 1.0 stay 1.0?
116 self
.assertTrue(status
.startswith(prefix
),
117 msg
="%s at start of %s"%(repr(prefix
), repr(status
)))
122 test_name
= "_".join([
126 {"\n":"LF", "\r\n":"CRLF"}[endl
],
128 do_test
.__name
__ = test_name
# hax
129 setattr(TestCases
, test_name
, do_test
)
131 def makeCases(name
, url
, hdr_checker
=None, body_checker
=None,
132 req_hdrs
={"User-Agent": "test.py"}):
133 for http_ver
in [None, "1.0", "1.1"]:
134 for endl
in ["\n", "\r\n"]:
135 makeCase(name
, url
, hdr_checker
, body_checker
,
136 req_hdrs
, http_ver
, endl
)
138 def makeSimpleCases(name
, url
, assert_name
):
139 makeCases(name
, url
, None,
140 lambda self
,body
: getattr(self
, assert_name
)(body
, url
))
144 ["index", "/", "assertIsIndex"],
145 ["up dir", "/dir/../", "assertIsIndex"],
146 ["extra slashes", "//dir///..////", "assertIsIndex"],
147 ["no trailing slash", "/dir/..", "assertIsIndex"],
148 ["no leading slash", "dir/../", "assertIsInvalid"],
149 ["invalid up dir", "/../", "assertIsInvalid"],
150 ["fancy invalid up dir", "/./dir/./../../", "assertIsInvalid"],
152 makeSimpleCases(*args
)
154 class TestDirRedirect(TestHelper
):
157 self
.fn
= WWWROOT
+ self
.url
163 def test_dir_redirect(self
):
164 resp
= Conn().get(self
.url
)
165 status
, hdrs
, body
= parse(resp
)
166 self
.assertContains(status
, "301 Moved Permanently")
167 self
.assertEquals(hdrs
["Location"], self
.url
+"/") # trailing slash
169 class TestFileGet(TestHelper
):
173 [chr(random
.randint(0,255)) for _
in xrange(self
.datalen
)])
174 self
.url
= "/data.jpeg"
175 self
.fn
= WWWROOT
+ self
.url
176 open(self
.fn
, "w").write(self
.data
)
181 def test_file_get(self
):
182 resp
= Conn().get(self
.url
)
183 status
, hdrs
, body
= parse(resp
)
184 self
.assertContains(status
, "200 OK")
185 self
.assertEquals(hdrs
["Accept-Ranges"], "bytes")
186 self
.assertEquals(hdrs
["Content-Length"], str(self
.datalen
))
187 self
.assertEquals(hdrs
["Content-Type"], "image/jpeg")
188 self
.assertEquals(body
, self
.data
)
190 def test_file_head(self
):
191 resp
= Conn().get(self
.url
, method
="HEAD")
192 status
, hdrs
, body
= parse(resp
)
193 self
.assertContains(status
, "200 OK")
194 self
.assertEquals(hdrs
["Accept-Ranges"], "bytes")
195 self
.assertEquals(hdrs
["Content-Length"], str(self
.datalen
))
196 self
.assertEquals(hdrs
["Content-Type"], "image/jpeg")
198 def test_if_modified_since(self
):
199 resp1
= Conn().get(self
.url
, method
="HEAD")
200 status
, hdrs
, body
= parse(resp1
)
201 lastmod
= hdrs
["Last-Modified"]
203 resp2
= Conn().get(self
.url
, method
="GET", req_hdrs
=
204 {"If-Modified-Since": lastmod
})
205 status
, hdrs
, body
= parse(resp2
)
206 self
.assertContains(status
, "304 Not Modified")
207 self
.assertEquals(hdrs
["Accept-Ranges"], "bytes")
208 self
.assertFalse(hdrs
.has_key("Last-Modified"))
209 self
.assertFalse(hdrs
.has_key("Content-Length"))
210 self
.assertFalse(hdrs
.has_key("Content-Type"))
212 def drive_range(self
, range_in
, range_out
, len_out
, data_out
,
213 status_out
= "206 Partial Content"):
214 resp
= Conn().get(self
.url
, req_hdrs
= {"Range": "bytes="+range_in
})
215 status
, hdrs
, body
= parse(resp
)
216 self
.assertContains(status
, status_out
)
217 self
.assertEquals(hdrs
["Accept-Ranges"], "bytes")
218 self
.assertEquals(hdrs
["Content-Range"], "bytes "+range_out
)
219 self
.assertEquals(hdrs
["Content-Length"], str(len_out
))
220 self
.assertEquals(body
, data_out
)
222 def test_range_single(self
):
223 self
.drive_range("5-5", "5-5/%d" % self
.datalen
,
226 def test_range_single_first(self
):
227 self
.drive_range("0-0", "0-0/%d" % self
.datalen
,
230 def test_range_single_last(self
):
231 self
.drive_range("%d-%d"%(self
.datalen
-1, self
.datalen
-1),
232 "%d-%d/%d"%(self
.datalen
-1, self
.datalen
-1, self
.datalen
),
235 def test_range_single_bad(self
):
236 resp
= Conn().get(self
.url
, req_hdrs
= {"Range":
237 "bytes=%d-%d"%(self
.datalen
, self
.datalen
)})
238 status
, hdrs
, body
= parse(resp
)
239 self
.assertContains(status
, "416 Requested Range Not Satisfiable")
241 def test_range_reasonable(self
):
242 self
.drive_range("10-20", "10-20/%d" % self
.datalen
,
243 20-10+1, self
.data
[10:20+1])
245 def test_range_start_given(self
):
246 self
.drive_range("10-", "10-%d/%d" % (self
.datalen
-1, self
.datalen
),
247 self
.datalen
-10, self
.data
[10:])
249 def test_range_end_given(self
):
250 self
.drive_range("-25",
251 "%d-%d/%d"%(self
.datalen
-25, self
.datalen
-1, self
.datalen
),
254 def test_range_beyond_end(self
):
255 # expecting same result as test_range_end_given
256 self
.drive_range("%d-%d"%(self
.datalen
-25, self
.datalen
*2),
257 "%d-%d/%d"%(self
.datalen
-25, self
.datalen
-1, self
.datalen
),
260 def test_range_end_given_oversize(self
):
261 # expecting full file
262 self
.drive_range("-%d"%(self
.datalen
*3),
263 "0-%d/%d"%(self
.datalen
-1, self
.datalen
),
264 self
.datalen
, self
.data
)
266 def test_range_bad_start(self
):
267 resp
= Conn().get(self
.url
, req_hdrs
= {"Range": "bytes=%d-"%(
269 status
, hdrs
, body
= parse(resp
)
270 self
.assertContains(status
, "416 Requested Range Not Satisfiable")
272 def test_range_backwards(self
):
273 resp
= Conn().get(self
.url
, req_hdrs
= {"Range": "bytes=20-10"})
274 status
, hdrs
, body
= parse(resp
)
275 self
.assertContains(status
, "416 Requested Range Not Satisfiable")
277 if __name__
== '__main__':
281 # vim:set ts=4 sw=4 et: