blob: 6ffc43ee03da9f362924de2c86cc46590301ebee [file] [log] [blame]
jcgregorio2d66d4f2006-02-07 05:34:14 +00001#!/usr/bin/env python2.4
2"""
3httplib2test
4
5A set of unit tests for httplib2.py.
6
7Requires Python 2.4 or later
8"""
9
10__author__ = "Joe Gregorio (joe@bitworking.org)"
11__copyright__ = "Copyright 2006, Joe Gregorio"
12__contributors__ = []
13__license__ = "MIT"
14__history__ = """ """
15__version__ = "0.1 ($Rev: 118 $)"
16
17
jcgregoriode8238d2007-03-07 19:08:26 +000018import sys
19import unittest
20import httplib
21import httplib2
22import os
23import urlparse
24import time
25import base64
jcgregorio2d66d4f2006-02-07 05:34:14 +000026
jcgregorio8421f272006-02-14 18:19:51 +000027
28# Python 2.3 support
29if not hasattr(unittest.TestCase, 'assertTrue'):
30 unittest.TestCase.assertTrue = unittest.TestCase.failUnless
31 unittest.TestCase.assertFalse = unittest.TestCase.failIf
32
jcgregorio2d66d4f2006-02-07 05:34:14 +000033# The test resources base uri
34base = 'http://bitworking.org/projects/httplib2/test/'
35#base = 'http://localhost/projects/httplib2/test/'
jcgregorio90fb4a42006-11-17 16:19:47 +000036cacheDirName = ".cache"
jcgregorio2d66d4f2006-02-07 05:34:14 +000037
jcgregoriode8238d2007-03-07 19:08:26 +000038
39class CredentialsTest(unittest.TestCase):
40 def test(self):
41 c = httplib2.Credentials()
42 c.add("joe", "password")
43 self.assertEqual(("joe", "password"), list(c.iter("bitworking.org"))[0])
44 self.assertEqual(("joe", "password"), list(c.iter(""))[0])
45 c.add("fred", "password2", "wellformedweb.org")
46 self.assertEqual(("joe", "password"), list(c.iter("bitworking.org"))[0])
47 self.assertEqual(1, len(list(c.iter("bitworking.org"))))
48 self.assertEqual(2, len(list(c.iter("wellformedweb.org"))))
49 self.assertTrue(("fred", "password2") in list(c.iter("wellformedweb.org")))
50 c.clear()
51 self.assertEqual(0, len(list(c.iter("bitworking.org"))))
52 c.add("fred", "password2", "wellformedweb.org")
53 self.assertTrue(("fred", "password2") in list(c.iter("wellformedweb.org")))
54 self.assertEqual(0, len(list(c.iter("bitworking.org"))))
55 self.assertEqual(0, len(list(c.iter(""))))
56
57
jcgregorio2d66d4f2006-02-07 05:34:14 +000058class ParserTest(unittest.TestCase):
59 def testFromStd66(self):
60 self.assertEqual( ('http', 'example.com', '', None, None ), httplib2.parse_uri("http://example.com"))
61 self.assertEqual( ('https', 'example.com', '', None, None ), httplib2.parse_uri("https://example.com"))
62 self.assertEqual( ('https', 'example.com:8080', '', None, None ), httplib2.parse_uri("https://example.com:8080"))
63 self.assertEqual( ('http', 'example.com', '/', None, None ), httplib2.parse_uri("http://example.com/"))
64 self.assertEqual( ('http', 'example.com', '/path', None, None ), httplib2.parse_uri("http://example.com/path"))
65 self.assertEqual( ('http', 'example.com', '/path', 'a=1&b=2', None ), httplib2.parse_uri("http://example.com/path?a=1&b=2"))
66 self.assertEqual( ('http', 'example.com', '/path', 'a=1&b=2', 'fred' ), httplib2.parse_uri("http://example.com/path?a=1&b=2#fred"))
67 self.assertEqual( ('http', 'example.com', '/path', 'a=1&b=2', 'fred' ), httplib2.parse_uri("http://example.com/path?a=1&b=2#fred"))
68
jcgregorio2d66d4f2006-02-07 05:34:14 +000069
jcgregorioa46fe4e2006-11-16 04:13:45 +000070class UrlNormTest(unittest.TestCase):
71 def test(self):
72 self.assertEqual( "http://example.org/", httplib2.urlnorm("http://example.org")[-1])
73 self.assertEqual( "http://example.org/", httplib2.urlnorm("http://EXAMple.org")[-1])
74 self.assertEqual( "http://example.org/?=b", httplib2.urlnorm("http://EXAMple.org?=b")[-1])
75 self.assertEqual( "http://example.org/mypath?a=b", httplib2.urlnorm("http://EXAMple.org/mypath?a=b")[-1])
76 self.assertEqual( "http://localhost:80/", httplib2.urlnorm("http://localhost:80")[-1])
jcgregoriob4e9ab02006-11-17 15:53:15 +000077 self.assertEqual( httplib2.urlnorm("http://localhost:80/"), httplib2.urlnorm("HTTP://LOCALHOST:80"))
jcgregorio132d28e2007-01-23 16:22:53 +000078 try:
79 httplib2.urlnorm("/")
80 self.fail("Non-absolute URIs should raise an exception")
81 except httplib2.RelativeURIError:
82 pass
jcgregorioa46fe4e2006-11-16 04:13:45 +000083
84class UrlSafenameTest(unittest.TestCase):
85 def test(self):
86 # Test that different URIs end up generating different safe names
87 self.assertEqual( "example.org,fred,a=b,58489f63a7a83c3b7794a6a398ee8b1f", httplib2.safename("http://example.org/fred/?a=b"))
88 self.assertEqual( "example.org,fred,a=b,8c5946d56fec453071f43329ff0be46b", httplib2.safename("http://example.org/fred?/a=b"))
89 self.assertEqual( "www.example.org,fred,a=b,499c44b8d844a011b67ea2c015116968", httplib2.safename("http://www.example.org/fred?/a=b"))
90 self.assertEqual( httplib2.safename(httplib2.urlnorm("http://www")[-1]), httplib2.safename(httplib2.urlnorm("http://WWW")[-1]))
91 self.assertEqual( "www.example.org,fred,a=b,692e843a333484ce0095b070497ab45d", httplib2.safename("https://www.example.org/fred?/a=b"))
92 self.assertNotEqual( httplib2.safename("http://www"), httplib2.safename("https://www"))
93 # Test the max length limits
94 uri = "http://" + ("w" * 200) + ".org"
95 uri2 = "http://" + ("w" * 201) + ".org"
96 self.assertNotEqual( httplib2.safename(uri2), httplib2.safename(uri))
97 # Max length should be 200 + 1 (",") + 32
98 self.assertEqual(233, len(httplib2.safename(uri2)))
99 self.assertEqual(233, len(httplib2.safename(uri)))
100 # Unicode
jcgregoriodebceec2006-12-12 20:26:02 +0000101 if sys.version_info >= (2,3):
102 self.assertEqual( "xn--http,-4y1d.org,fred,a=b,579924c35db315e5a32e3d9963388193", httplib2.safename(u"http://\u2304.org/fred/?a=b"))
jcgregorioa46fe4e2006-11-16 04:13:45 +0000103
104
jcgregorio90fb4a42006-11-17 16:19:47 +0000105
jcgregorio2d66d4f2006-02-07 05:34:14 +0000106class HttpTest(unittest.TestCase):
107 def setUp(self):
jcgregorio7e3608f2006-06-15 13:01:53 +0000108 if os.path.exists(cacheDirName):
109 [os.remove(os.path.join(cacheDirName, file)) for file in os.listdir(cacheDirName)]
110 self.http = httplib2.Http(cacheDirName)
jcgregorio36140b52006-06-13 02:17:52 +0000111 self.http.clear_credentials()
jcgregorio2d66d4f2006-02-07 05:34:14 +0000112
jcgregorio6a638172007-01-23 16:40:23 +0000113 def testGetUnknownServer(self):
jcgregorio07a9a4a2007-03-08 21:18:39 +0000114 self.http.force_exception_to_status_code = False
jcgregorio6a638172007-01-23 16:40:23 +0000115 try:
116 self.http.request("http://fred.bitworking.org/")
117 self.fail("An httplib2.ServerNotFoundError Exception must be thrown on an unresolvable server.")
118 except httplib2.ServerNotFoundError:
119 pass
120
jcgregorio07a9a4a2007-03-08 21:18:39 +0000121 # Now test with exceptions turned off
122 self.http.force_exception_to_status_code = True
123
124 (response, content) = self.http.request("http://fred.bitworking.org/")
125 self.assertEqual(response['content-type'], 'text/plain')
126 self.assertTrue(content.startswith("Unable to find"))
127 self.assertEqual(response.status, 400)
128
jcgregorioa898f8f2006-12-12 17:16:55 +0000129 def testGetIRI(self):
jcgregoriodebceec2006-12-12 20:26:02 +0000130 if sys.version_info >= (2,3):
131 uri = urlparse.urljoin(base, u"reflector/reflector.cgi?d=\N{CYRILLIC CAPITAL LETTER DJE}")
132 (response, content) = self.http.request(uri, "GET")
133 d = self.reflector(content)
134 self.assertTrue(d.has_key('QUERY_STRING'))
135 self.assertTrue(d['QUERY_STRING'].find('%D0%82') > 0)
jcgregorioa898f8f2006-12-12 17:16:55 +0000136
jcgregorio2d66d4f2006-02-07 05:34:14 +0000137 def testGetIsDefaultMethod(self):
138 # Test that GET is the default method
139 uri = urlparse.urljoin(base, "methods/method_reflector.cgi")
jcgregorio36140b52006-06-13 02:17:52 +0000140 (response, content) = self.http.request(uri)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000141 self.assertEqual(response['x-method'], "GET")
142
143 def testDifferentMethods(self):
144 # Test that all methods can be used
145 uri = urlparse.urljoin(base, "methods/method_reflector.cgi")
146 for method in ["GET", "PUT", "DELETE", "POST"]:
jcgregorio36140b52006-06-13 02:17:52 +0000147 (response, content) = self.http.request(uri, method, body=" ")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000148 self.assertEqual(response['x-method'], method)
149
150 def testGetNoCache(self):
151 # Test that can do a GET w/o the cache turned on.
152 http = httplib2.Http()
153 uri = urlparse.urljoin(base, "304/test_etag.txt")
154 (response, content) = http.request(uri, "GET")
155 self.assertEqual(response.status, 200)
jcgregorioa0713ab2006-07-01 05:21:34 +0000156 self.assertEqual(response.previous, None)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000157
jcgregorioe4ce13e2006-04-02 03:05:08 +0000158 def testGetOnlyIfCachedCacheMiss(self):
159 # Test that can do a GET with no cache with 'only-if-cached'
jcgregorio36140b52006-06-13 02:17:52 +0000160 http = httplib2.Http()
jcgregorioe4ce13e2006-04-02 03:05:08 +0000161 uri = urlparse.urljoin(base, "304/test_etag.txt")
162 (response, content) = http.request(uri, "GET", headers={'cache-control': 'only-if-cached'})
163 self.assertEqual(response.fromcache, False)
jcgregorio36140b52006-06-13 02:17:52 +0000164 self.assertEqual(response.status, 200)
jcgregorioe4ce13e2006-04-02 03:05:08 +0000165
166 def testGetOnlyIfCachedNoCacheAtAll(self):
167 # Test that can do a GET with no cache with 'only-if-cached'
168 # Of course, there might be an intermediary beyond us
169 # that responds to the 'only-if-cached', so this
170 # test can't really be guaranteed to pass.
171 http = httplib2.Http()
172 uri = urlparse.urljoin(base, "304/test_etag.txt")
173 (response, content) = http.request(uri, "GET", headers={'cache-control': 'only-if-cached'})
174 self.assertEqual(response.fromcache, False)
175 self.assertEqual(response.status, 200)
176
jcgregorio2d66d4f2006-02-07 05:34:14 +0000177 def testUserAgent(self):
178 # Test that we provide a default user-agent
179 uri = urlparse.urljoin(base, "user-agent/test.cgi")
jcgregorio36140b52006-06-13 02:17:52 +0000180 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000181 self.assertEqual(response.status, 200)
182 self.assertTrue(content.startswith("Python-httplib2/"))
183
184 def testUserAgentNonDefault(self):
185 # Test that the default user-agent can be over-ridden
186 uri = urlparse.urljoin(base, "user-agent/test.cgi")
jcgregorio36140b52006-06-13 02:17:52 +0000187 (response, content) = self.http.request(uri, "GET", headers={'User-Agent': 'fred/1.0'})
jcgregorio2d66d4f2006-02-07 05:34:14 +0000188 self.assertEqual(response.status, 200)
189 self.assertTrue(content.startswith("fred/1.0"))
190
191 def testGet300WithLocation(self):
192 # Test the we automatically follow 300 redirects if a Location: header is provided
193 uri = urlparse.urljoin(base, "300/with-location-header.asis")
jcgregorio36140b52006-06-13 02:17:52 +0000194 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000195 self.assertEqual(response.status, 200)
196 self.assertEqual(content, "This is the final destination.\n")
jcgregorioa0713ab2006-07-01 05:21:34 +0000197 self.assertEqual(response.previous.status, 300)
198 self.assertEqual(response.previous.fromcache, False)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000199
200 # Confirm that the intermediate 300 is not cached
jcgregorio36140b52006-06-13 02:17:52 +0000201 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000202 self.assertEqual(response.status, 200)
203 self.assertEqual(content, "This is the final destination.\n")
jcgregorioa0713ab2006-07-01 05:21:34 +0000204 self.assertEqual(response.previous.status, 300)
205 self.assertEqual(response.previous.fromcache, False)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000206
207 def testGet300WithoutLocation(self):
208 # Not giving a Location: header in a 300 response is acceptable
209 # In which case we just return the 300 response
210 uri = urlparse.urljoin(base, "300/without-location-header.asis")
jcgregorio36140b52006-06-13 02:17:52 +0000211 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000212 self.assertEqual(response.status, 300)
213 self.assertTrue(response['content-type'].startswith("text/html"))
jcgregorioa0713ab2006-07-01 05:21:34 +0000214 self.assertEqual(response.previous, None)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000215
216 def testGet301(self):
217 # Test that we automatically follow 301 redirects
218 # and that we cache the 301 response
219 uri = urlparse.urljoin(base, "301/onestep.asis")
jcgregorio8e300b92006-11-07 16:44:35 +0000220 destination = urlparse.urljoin(base, "302/final-destination.txt")
jcgregorio36140b52006-06-13 02:17:52 +0000221 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000222 self.assertEqual(response.status, 200)
jcgregorio772adc82006-11-17 21:52:34 +0000223 self.assertTrue(response.has_key('content-location'))
224 self.assertEqual(response['content-location'], destination)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000225 self.assertEqual(content, "This is the final destination.\n")
jcgregorioa0713ab2006-07-01 05:21:34 +0000226 self.assertEqual(response.previous.status, 301)
227 self.assertEqual(response.previous.fromcache, False)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000228
jcgregorio36140b52006-06-13 02:17:52 +0000229 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000230 self.assertEqual(response.status, 200)
jcgregorio772adc82006-11-17 21:52:34 +0000231 self.assertEqual(response['content-location'], destination)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000232 self.assertEqual(content, "This is the final destination.\n")
jcgregorioa0713ab2006-07-01 05:21:34 +0000233 self.assertEqual(response.previous.status, 301)
234 self.assertEqual(response.previous.fromcache, True)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000235
236 def testGet302(self):
237 # Test that we automatically follow 302 redirects
238 # and that we DO NOT cache the 302 response
239 uri = urlparse.urljoin(base, "302/onestep.asis")
jcgregorio8e300b92006-11-07 16:44:35 +0000240 destination = urlparse.urljoin(base, "302/final-destination.txt")
jcgregorio36140b52006-06-13 02:17:52 +0000241 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000242 self.assertEqual(response.status, 200)
jcgregorio772adc82006-11-17 21:52:34 +0000243 self.assertEqual(response['content-location'], destination)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000244 self.assertEqual(content, "This is the final destination.\n")
jcgregorioa0713ab2006-07-01 05:21:34 +0000245 self.assertEqual(response.previous.status, 302)
246 self.assertEqual(response.previous.fromcache, False)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000247
248 uri = urlparse.urljoin(base, "302/onestep.asis")
jcgregorio36140b52006-06-13 02:17:52 +0000249 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000250 self.assertEqual(response.status, 200)
251 self.assertEqual(response.fromcache, True)
jcgregorio772adc82006-11-17 21:52:34 +0000252 self.assertEqual(response['content-location'], destination)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000253 self.assertEqual(content, "This is the final destination.\n")
jcgregorioa0713ab2006-07-01 05:21:34 +0000254 self.assertEqual(response.previous.status, 302)
255 self.assertEqual(response.previous.fromcache, False)
jcgregorio772adc82006-11-17 21:52:34 +0000256 self.assertEqual(response.previous['content-location'], uri)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000257
258 uri = urlparse.urljoin(base, "302/twostep.asis")
259
jcgregorio36140b52006-06-13 02:17:52 +0000260 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000261 self.assertEqual(response.status, 200)
262 self.assertEqual(response.fromcache, True)
263 self.assertEqual(content, "This is the final destination.\n")
jcgregorioa0713ab2006-07-01 05:21:34 +0000264 self.assertEqual(response.previous.status, 302)
265 self.assertEqual(response.previous.fromcache, False)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000266
267 def testGet302RedirectionLimit(self):
268 # Test that we can set a lower redirection limit
269 # and that we raise an exception when we exceed
270 # that limit.
jcgregorio07a9a4a2007-03-08 21:18:39 +0000271 self.http.force_exception_to_status_code = False
272
jcgregorio2d66d4f2006-02-07 05:34:14 +0000273 uri = urlparse.urljoin(base, "302/twostep.asis")
274 try:
jcgregorio36140b52006-06-13 02:17:52 +0000275 (response, content) = self.http.request(uri, "GET", redirections = 1)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000276 self.fail("This should not happen")
277 except httplib2.RedirectLimit:
278 pass
279 except Exception, e:
280 self.fail("Threw wrong kind of exception ")
281
jcgregorio07a9a4a2007-03-08 21:18:39 +0000282 # Re-run the test with out the exceptions
283 self.http.force_exception_to_status_code = True
284
285 (response, content) = self.http.request(uri, "GET", redirections = 1)
286 self.assertEqual(response.status, 500)
287 self.assertTrue(response.reason.startswith("Redirected more"))
288 self.assertEqual("302", response['status'])
289 self.assertTrue(content.startswith("<html>"))
290 self.assertTrue(response.previous != None)
291
jcgregorio2d66d4f2006-02-07 05:34:14 +0000292 def testGet302NoLocation(self):
293 # Test that we throw an exception when we get
294 # a 302 with no Location: header.
jcgregorio07a9a4a2007-03-08 21:18:39 +0000295 self.http.force_exception_to_status_code = False
jcgregorio2d66d4f2006-02-07 05:34:14 +0000296 uri = urlparse.urljoin(base, "302/no-location.asis")
297 try:
jcgregorio36140b52006-06-13 02:17:52 +0000298 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000299 self.fail("Should never reach here")
300 except httplib2.RedirectMissingLocation:
301 pass
302 except Exception, e:
303 self.fail("Threw wrong kind of exception ")
304
jcgregorio07a9a4a2007-03-08 21:18:39 +0000305 # Re-run the test with out the exceptions
306 self.http.force_exception_to_status_code = True
307
308 (response, content) = self.http.request(uri, "GET")
309 self.assertEqual(response.status, 500)
310 self.assertTrue(response.reason.startswith("Redirected but"))
311 self.assertEqual("302", response['status'])
312 self.assertTrue(content.startswith("This is content"))
313
jcgregorio2d66d4f2006-02-07 05:34:14 +0000314 def testGet302ViaHttps(self):
jcgregorioadbb4f82006-05-19 15:17:42 +0000315 # Google always redirects to http://google.com
jcgregorio36140b52006-06-13 02:17:52 +0000316 (response, content) = self.http.request("https://google.com", "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000317 self.assertEqual(200, response.status)
jcgregorioa0713ab2006-07-01 05:21:34 +0000318 self.assertEqual(302, response.previous.status)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000319
320 def testGetViaHttps(self):
321 # Test that we can handle HTTPS
jcgregorio36140b52006-06-13 02:17:52 +0000322 (response, content) = self.http.request("https://google.com/adsense/", "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000323 self.assertEqual(200, response.status)
jcgregorioa0713ab2006-07-01 05:21:34 +0000324 self.assertEqual(None, response.previous)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000325
326 def testGetViaHttpsSpecViolationOnLocation(self):
327 # Test that we follow redirects through HTTPS
328 # even if they violate the spec by including
329 # a relative Location: header instead of an
330 # absolute one.
jcgregorio36140b52006-06-13 02:17:52 +0000331 (response, content) = self.http.request("https://google.com/adsense", "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000332 self.assertEqual(200, response.status)
jcgregorioa0713ab2006-07-01 05:21:34 +0000333 self.assertNotEqual(None, response.previous)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000334
jcgregoriode8238d2007-03-07 19:08:26 +0000335
336 def testGetViaHttpsKeyCert(self):
337 """At this point I can only test
338 that the key and cert files are passed in
339 correctly to httplib. It would be nice to have
340 a real https endpoint to test against.
341 """
342 http = httplib2.Http()
343 try:
344 (response, content) = http.request("https://example.org", "GET")
345 except:
346 pass
347 self.assertEqual(http.connections["https:example.org"].key_file, None)
348 self.assertEqual(http.connections["https:example.org"].cert_file, None)
349
350
351 http.add_certificate("akeyfile", "acertfile", "bitworking.org")
352 try:
353 (response, content) = http.request("https://bitworking.org", "GET")
354 except:
355 pass
356 self.assertEqual(http.connections["https:bitworking.org"].key_file, "akeyfile")
357 self.assertEqual(http.connections["https:bitworking.org"].cert_file, "acertfile")
358
359
jcgregorio2d66d4f2006-02-07 05:34:14 +0000360 def testGet303(self):
361 # Do a follow-up GET on a Location: header
362 # returned from a POST that gave a 303.
363 uri = urlparse.urljoin(base, "303/303.cgi")
jcgregorio36140b52006-06-13 02:17:52 +0000364 (response, content) = self.http.request(uri, "POST", " ")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000365 self.assertEqual(response.status, 200)
366 self.assertEqual(content, "This is the final destination.\n")
jcgregorioa0713ab2006-07-01 05:21:34 +0000367 self.assertEqual(response.previous.status, 303)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000368
369 def test303ForDifferentMethods(self):
370 # Test that all methods can be used
371 uri = urlparse.urljoin(base, "303/redirect-to-reflector.cgi")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000372 for (method, method_on_303) in [("PUT", "GET"), ("DELETE", "GET"), ("POST", "GET"), ("GET", "GET"), ("HEAD", "GET")]:
jcgregorio36140b52006-06-13 02:17:52 +0000373 (response, content) = self.http.request(uri, method, body=" ")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000374 self.assertEqual(response['x-method'], method_on_303)
375
376 def testGet304(self):
377 # Test that we use ETags properly to validate our cache
378 uri = urlparse.urljoin(base, "304/test_etag.txt")
jcgregorio36140b52006-06-13 02:17:52 +0000379 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000380 self.assertNotEqual(response['etag'], "")
381
jcgregorio36140b52006-06-13 02:17:52 +0000382 (response, content) = self.http.request(uri, "GET")
383 (response, content) = self.http.request(uri, "GET", headers = {'cache-control': 'must-revalidate'})
jcgregorio2d66d4f2006-02-07 05:34:14 +0000384 self.assertEqual(response.status, 200)
385 self.assertEqual(response.fromcache, True)
386
jcgregorio90fb4a42006-11-17 16:19:47 +0000387 cache_file_name = os.path.join(cacheDirName, httplib2.safename(httplib2.urlnorm(uri)[-1]))
388 f = open(cache_file_name, "r")
389 status_line = f.readline()
390 f.close()
391
392 self.assertTrue(status_line.startswith("status:"))
393
jcgregorio36140b52006-06-13 02:17:52 +0000394 (response, content) = self.http.request(uri, "HEAD")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000395 self.assertEqual(response.status, 200)
396 self.assertEqual(response.fromcache, True)
397
jcgregorio36140b52006-06-13 02:17:52 +0000398 (response, content) = self.http.request(uri, "GET", headers = {'range': 'bytes=0-0'})
jcgregorio2d66d4f2006-02-07 05:34:14 +0000399 self.assertEqual(response.status, 206)
400 self.assertEqual(response.fromcache, False)
401
jcgregorio25185622006-10-28 05:12:34 +0000402 def testGetIgnoreEtag(self):
403 # Test that we can forcibly ignore ETags
404 uri = urlparse.urljoin(base, "reflector/reflector.cgi")
405 (response, content) = self.http.request(uri, "GET")
406 self.assertNotEqual(response['etag'], "")
407
408 (response, content) = self.http.request(uri, "GET", headers = {'cache-control': 'max-age=0'})
409 d = self.reflector(content)
410 self.assertTrue(d.has_key('HTTP_IF_NONE_MATCH'))
411
412 self.http.ignore_etag = True
413 (response, content) = self.http.request(uri, "GET", headers = {'cache-control': 'max-age=0'})
414 d = self.reflector(content)
415 self.assertEqual(response.fromcache, False)
416 self.assertFalse(d.has_key('HTTP_IF_NONE_MATCH'))
417
jcgregorio4b145e82007-01-18 19:46:34 +0000418 def testOverrideEtag(self):
419 # Test that we can forcibly ignore ETags
420 uri = urlparse.urljoin(base, "reflector/reflector.cgi")
421 (response, content) = self.http.request(uri, "GET")
422 self.assertNotEqual(response['etag'], "")
423
424 (response, content) = self.http.request(uri, "GET", headers = {'cache-control': 'max-age=0'})
425 d = self.reflector(content)
426 self.assertTrue(d.has_key('HTTP_IF_NONE_MATCH'))
427 self.assertNotEqual(d['HTTP_IF_NONE_MATCH'], "fred")
428
429 (response, content) = self.http.request(uri, "GET", headers = {'cache-control': 'max-age=0', 'if=none-match': 'fred'})
430 d = self.reflector(content)
431 self.assertTrue(d.has_key('HTTP_IF_NONE_MATCH'))
432 self.assertEqual(d['HTTP_IF_NONE_MATCH'], "fred")
jcgregorio25185622006-10-28 05:12:34 +0000433
jcgregorio2d66d4f2006-02-07 05:34:14 +0000434 def testGet304EndToEnd(self):
435 # Test that end to end headers get overwritten in the cache
436 uri = urlparse.urljoin(base, "304/end2end.cgi")
jcgregorio36140b52006-06-13 02:17:52 +0000437 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000438 self.assertNotEqual(response['etag'], "")
439 old_date = response['date']
440 time.sleep(2)
441
jcgregorio36140b52006-06-13 02:17:52 +0000442 (response, content) = self.http.request(uri, "GET", headers = {'Cache-Control': 'max-age=0'})
jcgregorio2d66d4f2006-02-07 05:34:14 +0000443 # The response should be from the cache, but the Date: header should be updated.
444 new_date = response['date']
445 self.assertNotEqual(new_date, old_date)
446 self.assertEqual(response.status, 200)
447 self.assertEqual(response.fromcache, True)
448
449 def testGet304LastModified(self):
450 # Test that we can still handle a 304
451 # by only using the last-modified cache validator.
452 uri = urlparse.urljoin(base, "304/last-modified-only/last-modified-only.txt")
jcgregorio36140b52006-06-13 02:17:52 +0000453 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000454
455 self.assertNotEqual(response['last-modified'], "")
jcgregorio36140b52006-06-13 02:17:52 +0000456 (response, content) = self.http.request(uri, "GET")
457 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000458 self.assertEqual(response.status, 200)
459 self.assertEqual(response.fromcache, True)
460
461 def testGet307(self):
462 # Test that we do follow 307 redirects but
463 # do not cache the 307
464 uri = urlparse.urljoin(base, "307/onestep.asis")
jcgregorio36140b52006-06-13 02:17:52 +0000465 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000466 self.assertEqual(response.status, 200)
467 self.assertEqual(content, "This is the final destination.\n")
jcgregorioa0713ab2006-07-01 05:21:34 +0000468 self.assertEqual(response.previous.status, 307)
469 self.assertEqual(response.previous.fromcache, False)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000470
jcgregorio36140b52006-06-13 02:17:52 +0000471 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000472 self.assertEqual(response.status, 200)
473 self.assertEqual(response.fromcache, True)
474 self.assertEqual(content, "This is the final destination.\n")
jcgregorioa0713ab2006-07-01 05:21:34 +0000475 self.assertEqual(response.previous.status, 307)
476 self.assertEqual(response.previous.fromcache, False)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000477
478 def testGet410(self):
479 # Test that we pass 410's through
480 uri = urlparse.urljoin(base, "410/410.asis")
jcgregorio36140b52006-06-13 02:17:52 +0000481 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000482 self.assertEqual(response.status, 410)
483
484 def testGetGZip(self):
485 # Test that we support gzip compression
486 uri = urlparse.urljoin(base, "gzip/final-destination.txt")
jcgregorio36140b52006-06-13 02:17:52 +0000487 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000488 self.assertEqual(response.status, 200)
jcgregorio90fb4a42006-11-17 16:19:47 +0000489 self.assertFalse(response.has_key('content-encoding'))
jcgregorio153f5882006-11-06 03:33:24 +0000490 self.assertEqual(int(response['content-length']), len("This is the final destination.\n"))
jcgregorio2d66d4f2006-02-07 05:34:14 +0000491 self.assertEqual(content, "This is the final destination.\n")
492
493 def testGetGZipFailure(self):
494 # Test that we raise a good exception when the gzip fails
jcgregorio07a9a4a2007-03-08 21:18:39 +0000495 self.http.force_exception_to_status_code = False
jcgregorio2d66d4f2006-02-07 05:34:14 +0000496 uri = urlparse.urljoin(base, "gzip/failed-compression.asis")
497 try:
jcgregorio36140b52006-06-13 02:17:52 +0000498 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000499 self.fail("Should never reach here")
500 except httplib2.FailedToDecompressContent:
501 pass
502 except Exception:
503 self.fail("Threw wrong kind of exception")
504
jcgregorio07a9a4a2007-03-08 21:18:39 +0000505 # Re-run the test with out the exceptions
506 self.http.force_exception_to_status_code = True
507
508 (response, content) = self.http.request(uri, "GET")
509 self.assertEqual(response.status, 500)
510 self.assertTrue(response.reason.startswith("Content purported"))
511
512 def testTimeout(self):
513 uri = urlparse.urljoin(base, "timeout/timeout.cgi")
514 try:
515 import socket
516 socket.setdefaulttimeout(1)
517 except:
518 # Don't run the test if we can't set the timeout
519 return
520 (response, content) = self.http.request(uri)
521 self.assertEqual(response.status, 408)
522 self.assertTrue(response.reason.startswith("Request Timeout"))
523 self.assertTrue(content.startswith("Request Timeout"))
524
525
jcgregorio2d66d4f2006-02-07 05:34:14 +0000526 def testGetDeflate(self):
527 # Test that we support deflate compression
528 uri = urlparse.urljoin(base, "deflate/deflated.asis")
jcgregorio36140b52006-06-13 02:17:52 +0000529 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000530 self.assertEqual(response.status, 200)
jcgregorio90fb4a42006-11-17 16:19:47 +0000531 self.assertFalse(response.has_key('content-encoding'))
jcgregorio153f5882006-11-06 03:33:24 +0000532 self.assertEqual(int(response['content-length']), len("This is the final destination."))
jcgregorio2d66d4f2006-02-07 05:34:14 +0000533 self.assertEqual(content, "This is the final destination.")
534
535 def testGetDeflateFailure(self):
536 # Test that we raise a good exception when the deflate fails
jcgregorio07a9a4a2007-03-08 21:18:39 +0000537 self.http.force_exception_to_status_code = False
538
jcgregorio2d66d4f2006-02-07 05:34:14 +0000539 uri = urlparse.urljoin(base, "deflate/failed-compression.asis")
540 try:
jcgregorio36140b52006-06-13 02:17:52 +0000541 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000542 self.fail("Should never reach here")
543 except httplib2.FailedToDecompressContent:
544 pass
545 except Exception:
546 self.fail("Threw wrong kind of exception")
547
jcgregorio07a9a4a2007-03-08 21:18:39 +0000548 # Re-run the test with out the exceptions
549 self.http.force_exception_to_status_code = True
550
551 (response, content) = self.http.request(uri, "GET")
552 self.assertEqual(response.status, 500)
553 self.assertTrue(response.reason.startswith("Content purported"))
554
jcgregorio2d66d4f2006-02-07 05:34:14 +0000555 def testGetDuplicateHeaders(self):
556 # Test that duplicate headers get concatenated via ','
557 uri = urlparse.urljoin(base, "duplicate-headers/multilink.asis")
jcgregorio36140b52006-06-13 02:17:52 +0000558 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000559 self.assertEqual(response.status, 200)
560 self.assertEqual(content, "This is content\n")
561 self.assertEqual(response['link'].split(",")[0], '<http://bitworking.org>; rel="home"; title="BitWorking"')
562
563 def testGetCacheControlNoCache(self):
564 # Test Cache-Control: no-cache on requests
565 uri = urlparse.urljoin(base, "304/test_etag.txt")
jcgregorio36140b52006-06-13 02:17:52 +0000566 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000567 self.assertNotEqual(response['etag'], "")
jcgregorio36140b52006-06-13 02:17:52 +0000568 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000569 self.assertEqual(response.status, 200)
570 self.assertEqual(response.fromcache, True)
571
jcgregorio36140b52006-06-13 02:17:52 +0000572 (response, content) = self.http.request(uri, "GET", headers={'Cache-Control': 'no-cache'})
jcgregorio2d66d4f2006-02-07 05:34:14 +0000573 self.assertEqual(response.status, 200)
574 self.assertEqual(response.fromcache, False)
575
576 def testGetCacheControlPragmaNoCache(self):
577 # Test Pragma: no-cache on requests
578 uri = urlparse.urljoin(base, "304/test_etag.txt")
jcgregorio36140b52006-06-13 02:17:52 +0000579 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000580 self.assertNotEqual(response['etag'], "")
jcgregorio36140b52006-06-13 02:17:52 +0000581 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000582 self.assertEqual(response.status, 200)
583 self.assertEqual(response.fromcache, True)
584
jcgregorio36140b52006-06-13 02:17:52 +0000585 (response, content) = self.http.request(uri, "GET", headers={'Pragma': 'no-cache'})
jcgregorio2d66d4f2006-02-07 05:34:14 +0000586 self.assertEqual(response.status, 200)
587 self.assertEqual(response.fromcache, False)
588
589 def testGetCacheControlNoStoreRequest(self):
590 # A no-store request means that the response should not be stored.
591 uri = urlparse.urljoin(base, "304/test_etag.txt")
592
jcgregorio36140b52006-06-13 02:17:52 +0000593 (response, content) = self.http.request(uri, "GET", headers={'Cache-Control': 'no-store'})
jcgregorio2d66d4f2006-02-07 05:34:14 +0000594 self.assertEqual(response.status, 200)
595 self.assertEqual(response.fromcache, False)
596
jcgregorio36140b52006-06-13 02:17:52 +0000597 (response, content) = self.http.request(uri, "GET", headers={'Cache-Control': 'no-store'})
jcgregorio2d66d4f2006-02-07 05:34:14 +0000598 self.assertEqual(response.status, 200)
599 self.assertEqual(response.fromcache, False)
600
601 def testGetCacheControlNoStoreResponse(self):
602 # A no-store response means that the response should not be stored.
603 uri = urlparse.urljoin(base, "no-store/no-store.asis")
604
jcgregorio36140b52006-06-13 02:17:52 +0000605 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000606 self.assertEqual(response.status, 200)
607 self.assertEqual(response.fromcache, False)
608
jcgregorio36140b52006-06-13 02:17:52 +0000609 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000610 self.assertEqual(response.status, 200)
611 self.assertEqual(response.fromcache, False)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000612
613 def testGetCacheControlNoCacheNoStoreRequest(self):
614 # Test that a no-store, no-cache clears the entry from the cache
615 # even if it was cached previously.
616 uri = urlparse.urljoin(base, "304/test_etag.txt")
617
jcgregorio36140b52006-06-13 02:17:52 +0000618 (response, content) = self.http.request(uri, "GET")
619 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000620 self.assertEqual(response.fromcache, True)
jcgregorio36140b52006-06-13 02:17:52 +0000621 (response, content) = self.http.request(uri, "GET", headers={'Cache-Control': 'no-store, no-cache'})
622 (response, content) = self.http.request(uri, "GET", headers={'Cache-Control': 'no-store, no-cache'})
jcgregorio2d66d4f2006-02-07 05:34:14 +0000623 self.assertEqual(response.status, 200)
624 self.assertEqual(response.fromcache, False)
jcgregorio2d66d4f2006-02-07 05:34:14 +0000625
626 def testUpdateInvalidatesCache(self):
627 # Test that calling PUT or DELETE on a
628 # URI that is cache invalidates that cache.
629 uri = urlparse.urljoin(base, "304/test_etag.txt")
630
jcgregorio36140b52006-06-13 02:17:52 +0000631 (response, content) = self.http.request(uri, "GET")
632 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000633 self.assertEqual(response.fromcache, True)
jcgregorio36140b52006-06-13 02:17:52 +0000634 (response, content) = self.http.request(uri, "DELETE")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000635 self.assertEqual(response.status, 405)
636
jcgregorio36140b52006-06-13 02:17:52 +0000637 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000638 self.assertEqual(response.fromcache, False)
639
640 def testUpdateUsesCachedETag(self):
641 # Test that we natively support http://www.w3.org/1999/04/Editing/
642 uri = urlparse.urljoin(base, "conditional-updates/test.cgi")
643
jcgregorio36140b52006-06-13 02:17:52 +0000644 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000645 self.assertEqual(response.status, 200)
646 self.assertEqual(response.fromcache, False)
jcgregorio36140b52006-06-13 02:17:52 +0000647 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000648 self.assertEqual(response.status, 200)
649 self.assertEqual(response.fromcache, True)
jcgregorio36140b52006-06-13 02:17:52 +0000650 (response, content) = self.http.request(uri, "PUT")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000651 self.assertEqual(response.status, 200)
jcgregorio36140b52006-06-13 02:17:52 +0000652 (response, content) = self.http.request(uri, "PUT")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000653 self.assertEqual(response.status, 412)
654
jcgregorio4b145e82007-01-18 19:46:34 +0000655 def testUpdateUsesCachedETagOverridden(self):
656 # Test that we natively support http://www.w3.org/1999/04/Editing/
657 uri = urlparse.urljoin(base, "conditional-updates/test.cgi")
658
659 (response, content) = self.http.request(uri, "GET")
660 self.assertEqual(response.status, 200)
661 self.assertEqual(response.fromcache, False)
662 (response, content) = self.http.request(uri, "GET")
663 self.assertEqual(response.status, 200)
664 self.assertEqual(response.fromcache, True)
665 (response, content) = self.http.request(uri, "PUT", headers={'if-match': 'fred'})
666 self.assertEqual(response.status, 412)
667
668
669
jcgregorio2d66d4f2006-02-07 05:34:14 +0000670 def testBasicAuth(self):
671 # Test Basic Authentication
672 uri = urlparse.urljoin(base, "basic/file.txt")
jcgregorio36140b52006-06-13 02:17:52 +0000673 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000674 self.assertEqual(response.status, 401)
675
676 uri = urlparse.urljoin(base, "basic/")
jcgregorio36140b52006-06-13 02:17:52 +0000677 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000678 self.assertEqual(response.status, 401)
679
jcgregorio36140b52006-06-13 02:17:52 +0000680 self.http.add_credentials('joe', 'password')
681 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000682 self.assertEqual(response.status, 200)
683
684 uri = urlparse.urljoin(base, "basic/file.txt")
jcgregorio36140b52006-06-13 02:17:52 +0000685 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000686 self.assertEqual(response.status, 200)
687
jcgregoriode8238d2007-03-07 19:08:26 +0000688 def testBasicAuthWithDomain(self):
689 # Test Basic Authentication
690 uri = urlparse.urljoin(base, "basic/file.txt")
691 (response, content) = self.http.request(uri, "GET")
692 self.assertEqual(response.status, 401)
693
694 uri = urlparse.urljoin(base, "basic/")
695 (response, content) = self.http.request(uri, "GET")
696 self.assertEqual(response.status, 401)
697
698 self.http.add_credentials('joe', 'password', "example.org")
699 (response, content) = self.http.request(uri, "GET")
700 self.assertEqual(response.status, 401)
701
702 uri = urlparse.urljoin(base, "basic/file.txt")
703 (response, content) = self.http.request(uri, "GET")
704 self.assertEqual(response.status, 401)
705
706 domain = urlparse.urlparse(base)[1]
707 self.http.add_credentials('joe', 'password', domain)
708 (response, content) = self.http.request(uri, "GET")
709 self.assertEqual(response.status, 200)
710
711 uri = urlparse.urljoin(base, "basic/file.txt")
712 (response, content) = self.http.request(uri, "GET")
713 self.assertEqual(response.status, 200)
714
715
716
717
718
719
jcgregorio2d66d4f2006-02-07 05:34:14 +0000720 def testBasicAuthTwoDifferentCredentials(self):
jcgregorioadbb4f82006-05-19 15:17:42 +0000721 # Test Basic Authentication with multiple sets of credentials
jcgregorio2d66d4f2006-02-07 05:34:14 +0000722 uri = urlparse.urljoin(base, "basic2/file.txt")
jcgregorio36140b52006-06-13 02:17:52 +0000723 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000724 self.assertEqual(response.status, 401)
725
726 uri = urlparse.urljoin(base, "basic2/")
jcgregorio36140b52006-06-13 02:17:52 +0000727 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000728 self.assertEqual(response.status, 401)
729
jcgregorio36140b52006-06-13 02:17:52 +0000730 self.http.add_credentials('fred', 'barney')
731 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000732 self.assertEqual(response.status, 200)
733
734 uri = urlparse.urljoin(base, "basic2/file.txt")
jcgregorio36140b52006-06-13 02:17:52 +0000735 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000736 self.assertEqual(response.status, 200)
737
738 def testBasicAuthNested(self):
739 # Test Basic Authentication with resources
740 # that are nested
741 uri = urlparse.urljoin(base, "basic-nested/")
jcgregorio36140b52006-06-13 02:17:52 +0000742 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000743 self.assertEqual(response.status, 401)
744
745 uri = urlparse.urljoin(base, "basic-nested/subdir")
jcgregorio36140b52006-06-13 02:17:52 +0000746 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000747 self.assertEqual(response.status, 401)
748
jcgregorioadbb4f82006-05-19 15:17:42 +0000749 # Now add in credentials one at a time and test.
jcgregorio36140b52006-06-13 02:17:52 +0000750 self.http.add_credentials('joe', 'password')
jcgregorio2d66d4f2006-02-07 05:34:14 +0000751
752 uri = urlparse.urljoin(base, "basic-nested/")
jcgregorio36140b52006-06-13 02:17:52 +0000753 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000754 self.assertEqual(response.status, 200)
755
756 uri = urlparse.urljoin(base, "basic-nested/subdir")
jcgregorio36140b52006-06-13 02:17:52 +0000757 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000758 self.assertEqual(response.status, 401)
759
jcgregorio36140b52006-06-13 02:17:52 +0000760 self.http.add_credentials('fred', 'barney')
jcgregorio2d66d4f2006-02-07 05:34:14 +0000761
762 uri = urlparse.urljoin(base, "basic-nested/")
jcgregorio36140b52006-06-13 02:17:52 +0000763 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000764 self.assertEqual(response.status, 200)
765
766 uri = urlparse.urljoin(base, "basic-nested/subdir")
jcgregorio36140b52006-06-13 02:17:52 +0000767 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000768 self.assertEqual(response.status, 200)
769
770 def testDigestAuth(self):
771 # Test that we support Digest Authentication
772 uri = urlparse.urljoin(base, "digest/")
jcgregorio36140b52006-06-13 02:17:52 +0000773 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000774 self.assertEqual(response.status, 401)
775
jcgregorio36140b52006-06-13 02:17:52 +0000776 self.http.add_credentials('joe', 'password')
777 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000778 self.assertEqual(response.status, 200)
779
780 uri = urlparse.urljoin(base, "digest/file.txt")
jcgregorio36140b52006-06-13 02:17:52 +0000781 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000782
783 def testDigestAuthNextNonceAndNC(self):
784 # Test that if the server sets nextnonce that we reset
785 # the nonce count back to 1
786 uri = urlparse.urljoin(base, "digest/file.txt")
jcgregorio36140b52006-06-13 02:17:52 +0000787 self.http.add_credentials('joe', 'password')
788 (response, content) = self.http.request(uri, "GET", headers = {"cache-control":"no-cache"})
jcgregorio2d66d4f2006-02-07 05:34:14 +0000789 info = httplib2._parse_www_authenticate(response, 'authentication-info')
790 self.assertEqual(response.status, 200)
jcgregorio36140b52006-06-13 02:17:52 +0000791 (response, content) = self.http.request(uri, "GET", headers = {"cache-control":"no-cache"})
jcgregorio2d66d4f2006-02-07 05:34:14 +0000792 info2 = httplib2._parse_www_authenticate(response, 'authentication-info')
793 self.assertEqual(response.status, 200)
794
795 if info.has_key('nextnonce'):
796 self.assertEqual(info2['nc'], 1)
797
798 def testDigestAuthStale(self):
799 # Test that we can handle a nonce becoming stale
800 uri = urlparse.urljoin(base, "digest-expire/file.txt")
jcgregorio36140b52006-06-13 02:17:52 +0000801 self.http.add_credentials('joe', 'password')
802 (response, content) = self.http.request(uri, "GET", headers = {"cache-control":"no-cache"})
jcgregorio2d66d4f2006-02-07 05:34:14 +0000803 info = httplib2._parse_www_authenticate(response, 'authentication-info')
804 self.assertEqual(response.status, 200)
805
806 time.sleep(3)
807 # Sleep long enough that the nonce becomes stale
808
jcgregorio36140b52006-06-13 02:17:52 +0000809 (response, content) = self.http.request(uri, "GET", headers = {"cache-control":"no-cache"})
jcgregorio2d66d4f2006-02-07 05:34:14 +0000810 self.assertFalse(response.fromcache)
811 self.assertTrue(response._stale_digest)
812 info3 = httplib2._parse_www_authenticate(response, 'authentication-info')
813 self.assertEqual(response.status, 200)
814
815 def reflector(self, content):
jcgregorio25185622006-10-28 05:12:34 +0000816 return dict( [tuple(x.split("=", 1)) for x in content.strip().split("\n")] )
jcgregorio2d66d4f2006-02-07 05:34:14 +0000817
818 def testReflector(self):
819 uri = urlparse.urljoin(base, "reflector/reflector.cgi")
jcgregorio36140b52006-06-13 02:17:52 +0000820 (response, content) = self.http.request(uri, "GET")
jcgregorio2d66d4f2006-02-07 05:34:14 +0000821 d = self.reflector(content)
822 self.assertTrue(d.has_key('HTTP_USER_AGENT'))
823
jcgregorio36140b52006-06-13 02:17:52 +0000824try:
825 import memcache
826 class HttpTestMemCached(HttpTest):
827 def setUp(self):
828 self.cache = memcache.Client(['127.0.0.1:11211'], debug=0)
jcgregorio47d24672006-06-29 05:18:59 +0000829 #self.cache = memcache.Client(['10.0.0.4:11211'], debug=1)
jcgregorio36140b52006-06-13 02:17:52 +0000830 self.http = httplib2.Http(self.cache)
831 self.cache.flush_all()
jcgregorio47d24672006-06-29 05:18:59 +0000832 # Not exactly sure why the sleep is needed here, but
833 # if not present then some unit tests that rely on caching
834 # fail. Memcached seems to lose some sets immediately
835 # after a flush_all if the set is to a value that
836 # was previously cached. (Maybe the flush is handled async?)
837 time.sleep(1)
jcgregorio36140b52006-06-13 02:17:52 +0000838 self.http.clear_credentials()
839except:
840 pass
841
842
843
jcgregoriodb8dfc82006-03-31 14:59:46 +0000844# ------------------------------------------------------------------------
jcgregorio2d66d4f2006-02-07 05:34:14 +0000845
846class HttpPrivateTest(unittest.TestCase):
847
848 def testParseCacheControl(self):
849 # Test that we can parse the Cache-Control header
850 self.assertEqual({}, httplib2._parse_cache_control({}))
851 self.assertEqual({'no-cache': 1}, httplib2._parse_cache_control({'cache-control': ' no-cache'}))
852 cc = httplib2._parse_cache_control({'cache-control': ' no-cache, max-age = 7200'})
853 self.assertEqual(cc['no-cache'], 1)
854 self.assertEqual(cc['max-age'], '7200')
855 cc = httplib2._parse_cache_control({'cache-control': ' , '})
856 self.assertEqual(cc[''], 1)
857
858 def testNormalizeHeaders(self):
859 # Test that we normalize headers to lowercase
860 h = httplib2._normalize_headers({'Cache-Control': 'no-cache', 'Other': 'Stuff'})
861 self.assertTrue(h.has_key('cache-control'))
862 self.assertTrue(h.has_key('other'))
863 self.assertEqual('Stuff', h['other'])
864
865 def testExpirationModelTransparent(self):
866 # Test that no-cache makes our request TRANSPARENT
867 response_headers = {
868 'cache-control': 'max-age=7200'
869 }
870 request_headers = {
871 'cache-control': 'no-cache'
872 }
873 self.assertEqual("TRANSPARENT", httplib2._entry_disposition(response_headers, request_headers))
874
jcgregorio45865012007-01-18 16:38:22 +0000875 def testMaxAgeNonNumeric(self):
876 # Test that no-cache makes our request TRANSPARENT
877 response_headers = {
878 'cache-control': 'max-age=fred, min-fresh=barney'
879 }
880 request_headers = {
881 }
882 self.assertEqual("STALE", httplib2._entry_disposition(response_headers, request_headers))
883
884
jcgregorio2d66d4f2006-02-07 05:34:14 +0000885 def testExpirationModelNoCacheResponse(self):
886 # The date and expires point to an entry that should be
887 # FRESH, but the no-cache over-rides that.
888 now = time.time()
889 response_headers = {
890 'date': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(now)),
891 'expires': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(now+4)),
892 'cache-control': 'no-cache'
893 }
894 request_headers = {
895 }
896 self.assertEqual("STALE", httplib2._entry_disposition(response_headers, request_headers))
897
898 def testExpirationModelStaleRequestMustReval(self):
899 # must-revalidate forces STALE
900 self.assertEqual("STALE", httplib2._entry_disposition({}, {'cache-control': 'must-revalidate'}))
901
902 def testExpirationModelStaleResponseMustReval(self):
903 # must-revalidate forces STALE
904 self.assertEqual("STALE", httplib2._entry_disposition({'cache-control': 'must-revalidate'}, {}))
905
906 def testExpirationModelFresh(self):
907 response_headers = {
908 'date': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()),
909 'cache-control': 'max-age=2'
910 }
911 request_headers = {
912 }
913 self.assertEqual("FRESH", httplib2._entry_disposition(response_headers, request_headers))
914 time.sleep(3)
915 self.assertEqual("STALE", httplib2._entry_disposition(response_headers, request_headers))
916
917 def testExpirationMaxAge0(self):
918 response_headers = {
919 'date': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime()),
920 'cache-control': 'max-age=0'
921 }
922 request_headers = {
923 }
924 self.assertEqual("STALE", httplib2._entry_disposition(response_headers, request_headers))
925
926 def testExpirationModelDateAndExpires(self):
927 now = time.time()
928 response_headers = {
929 'date': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(now)),
930 'expires': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(now+2)),
931 }
932 request_headers = {
933 }
934 self.assertEqual("FRESH", httplib2._entry_disposition(response_headers, request_headers))
935 time.sleep(3)
936 self.assertEqual("STALE", httplib2._entry_disposition(response_headers, request_headers))
937
938 def testExpirationModelDateOnly(self):
939 now = time.time()
940 response_headers = {
941 'date': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(now+3)),
942 }
943 request_headers = {
944 }
945 self.assertEqual("STALE", httplib2._entry_disposition(response_headers, request_headers))
946
947 def testExpirationModelOnlyIfCached(self):
948 response_headers = {
949 }
950 request_headers = {
951 'cache-control': 'only-if-cached',
952 }
953 self.assertEqual("FRESH", httplib2._entry_disposition(response_headers, request_headers))
954
955 def testExpirationModelMaxAgeBoth(self):
956 now = time.time()
957 response_headers = {
958 'date': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(now)),
959 'cache-control': 'max-age=2'
960 }
961 request_headers = {
962 'cache-control': 'max-age=0'
963 }
964 self.assertEqual("STALE", httplib2._entry_disposition(response_headers, request_headers))
965
966 def testExpirationModelDateAndExpiresMinFresh1(self):
967 now = time.time()
968 response_headers = {
969 'date': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(now)),
970 'expires': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(now+2)),
971 }
972 request_headers = {
973 'cache-control': 'min-fresh=2'
974 }
975 self.assertEqual("STALE", httplib2._entry_disposition(response_headers, request_headers))
976
977 def testExpirationModelDateAndExpiresMinFresh2(self):
978 now = time.time()
979 response_headers = {
980 'date': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(now)),
981 'expires': time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(now+4)),
982 }
983 request_headers = {
984 'cache-control': 'min-fresh=2'
985 }
986 self.assertEqual("FRESH", httplib2._entry_disposition(response_headers, request_headers))
987
988 def testParseWWWAuthenticateEmpty(self):
989 res = httplib2._parse_www_authenticate({})
990 self.assertEqual(len(res.keys()), 0)
991
jcgregoriofd22e432006-04-27 02:00:08 +0000992 def testParseWWWAuthenticate(self):
993 # different uses of spaces around commas
994 res = httplib2._parse_www_authenticate({ 'www-authenticate': 'Test realm="test realm" , foo=foo ,bar="bar", baz=baz,qux=qux'})
995 self.assertEqual(len(res.keys()), 1)
996 self.assertEqual(len(res['test'].keys()), 5)
997
998 # tokens with non-alphanum
999 res = httplib2._parse_www_authenticate({ 'www-authenticate': 'T*!%#st realm=to*!%#en, to*!%#en="quoted string"'})
1000 self.assertEqual(len(res.keys()), 1)
1001 self.assertEqual(len(res['t*!%#st'].keys()), 2)
1002
1003 # quoted string with quoted pairs
1004 res = httplib2._parse_www_authenticate({ 'www-authenticate': 'Test realm="a \\"test\\" realm"'})
1005 self.assertEqual(len(res.keys()), 1)
1006 self.assertEqual(res['test']['realm'], 'a "test" realm')
1007
1008 def testParseWWWAuthenticateStrict(self):
1009 httplib2.USE_WWW_AUTH_STRICT_PARSING = 1;
1010 self.testParseWWWAuthenticate();
1011 httplib2.USE_WWW_AUTH_STRICT_PARSING = 0;
1012
jcgregorio2d66d4f2006-02-07 05:34:14 +00001013 def testParseWWWAuthenticateBasic(self):
1014 res = httplib2._parse_www_authenticate({ 'www-authenticate': 'Basic realm="me"'})
1015 basic = res['basic']
1016 self.assertEqual('me', basic['realm'])
1017
1018 res = httplib2._parse_www_authenticate({ 'www-authenticate': 'Basic realm="me", algorithm="MD5"'})
1019 basic = res['basic']
1020 self.assertEqual('me', basic['realm'])
1021 self.assertEqual('MD5', basic['algorithm'])
1022
1023 res = httplib2._parse_www_authenticate({ 'www-authenticate': 'Basic realm="me", algorithm=MD5'})
1024 basic = res['basic']
1025 self.assertEqual('me', basic['realm'])
1026 self.assertEqual('MD5', basic['algorithm'])
1027
1028 def testParseWWWAuthenticateBasic2(self):
1029 res = httplib2._parse_www_authenticate({ 'www-authenticate': 'Basic realm="me",other="fred" '})
1030 basic = res['basic']
1031 self.assertEqual('me', basic['realm'])
1032 self.assertEqual('fred', basic['other'])
1033
1034 def testParseWWWAuthenticateBasic3(self):
1035 res = httplib2._parse_www_authenticate({ 'www-authenticate': 'Basic REAlm="me" '})
1036 basic = res['basic']
1037 self.assertEqual('me', basic['realm'])
1038
1039
1040 def testParseWWWAuthenticateDigest(self):
1041 res = httplib2._parse_www_authenticate({ 'www-authenticate':
1042 'Digest realm="testrealm@host.com", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"'})
1043 digest = res['digest']
1044 self.assertEqual('testrealm@host.com', digest['realm'])
1045 self.assertEqual('auth,auth-int', digest['qop'])
1046
1047
1048 def testParseWWWAuthenticateMultiple(self):
1049 res = httplib2._parse_www_authenticate({ 'www-authenticate':
1050 'Digest realm="testrealm@host.com", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41" Basic REAlm="me" '})
1051 digest = res['digest']
1052 self.assertEqual('testrealm@host.com', digest['realm'])
1053 self.assertEqual('auth,auth-int', digest['qop'])
1054 self.assertEqual('dcd98b7102dd2f0e8b11d0f600bfb0c093', digest['nonce'])
1055 self.assertEqual('5ccc069c403ebaf9f0171e9517f40e41', digest['opaque'])
1056 basic = res['basic']
1057 self.assertEqual('me', basic['realm'])
1058
1059 def testParseWWWAuthenticateMultiple2(self):
1060 # Handle an added comma between challenges, which might get thrown in if the challenges were
1061 # originally sent in separate www-authenticate headers.
1062 res = httplib2._parse_www_authenticate({ 'www-authenticate':
1063 'Digest realm="testrealm@host.com", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41", Basic REAlm="me" '})
1064 digest = res['digest']
1065 self.assertEqual('testrealm@host.com', digest['realm'])
1066 self.assertEqual('auth,auth-int', digest['qop'])
1067 self.assertEqual('dcd98b7102dd2f0e8b11d0f600bfb0c093', digest['nonce'])
1068 self.assertEqual('5ccc069c403ebaf9f0171e9517f40e41', digest['opaque'])
1069 basic = res['basic']
1070 self.assertEqual('me', basic['realm'])
1071
1072 def testParseWWWAuthenticateMultiple3(self):
1073 # Handle an added comma between challenges, which might get thrown in if the challenges were
1074 # originally sent in separate www-authenticate headers.
1075 res = httplib2._parse_www_authenticate({ 'www-authenticate':
1076 'Digest realm="testrealm@host.com", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41", Basic REAlm="me", WSSE realm="foo", profile="UsernameToken"'})
1077 digest = res['digest']
1078 self.assertEqual('testrealm@host.com', digest['realm'])
1079 self.assertEqual('auth,auth-int', digest['qop'])
1080 self.assertEqual('dcd98b7102dd2f0e8b11d0f600bfb0c093', digest['nonce'])
1081 self.assertEqual('5ccc069c403ebaf9f0171e9517f40e41', digest['opaque'])
1082 basic = res['basic']
1083 self.assertEqual('me', basic['realm'])
1084 wsse = res['wsse']
1085 self.assertEqual('foo', wsse['realm'])
1086 self.assertEqual('UsernameToken', wsse['profile'])
1087
1088 def testParseWWWAuthenticateMultiple4(self):
1089 res = httplib2._parse_www_authenticate({ 'www-authenticate':
1090 'Digest realm="test-real.m@host.com", qop \t=\t"\tauth,auth-int", nonce="(*)&^&$%#",opaque="5ccc069c403ebaf9f0171e9517f40e41", Basic REAlm="me", WSSE realm="foo", profile="UsernameToken"'})
1091 digest = res['digest']
1092 self.assertEqual('test-real.m@host.com', digest['realm'])
1093 self.assertEqual('\tauth,auth-int', digest['qop'])
1094 self.assertEqual('(*)&^&$%#', digest['nonce'])
1095
1096 def testParseWWWAuthenticateMoreQuoteCombos(self):
1097 res = httplib2._parse_www_authenticate({'www-authenticate':'Digest realm="myrealm", nonce="Ygk86AsKBAA=3516200d37f9a3230352fde99977bd6d472d4306", algorithm=MD5, qop="auth", stale=true'})
1098 digest = res['digest']
1099 self.assertEqual('myrealm', digest['realm'])
1100
1101 def testDigestObject(self):
1102 credentials = ('joe', 'password')
1103 host = None
1104 request_uri = '/projects/httplib2/test/digest/'
1105 headers = {}
1106 response = {
1107 'www-authenticate': 'Digest realm="myrealm", nonce="Ygk86AsKBAA=3516200d37f9a3230352fde99977bd6d472d4306", algorithm=MD5, qop="auth"'
1108 }
1109 content = ""
1110
jcgregorio6cbab7e2006-04-21 20:35:43 +00001111 d = httplib2.DigestAuthentication(credentials, host, request_uri, headers, response, content, None)
jcgregorio2d66d4f2006-02-07 05:34:14 +00001112 d.request("GET", request_uri, headers, content, cnonce="33033375ec278a46")
1113 our_request = "Authorization: %s" % headers['Authorization']
1114 working_request = 'Authorization: Digest username="joe", realm="myrealm", nonce="Ygk86AsKBAA=3516200d37f9a3230352fde99977bd6d472d4306", uri="/projects/httplib2/test/digest/", algorithm=MD5, response="97ed129401f7cdc60e5db58a80f3ea8b", qop=auth, nc=00000001, cnonce="33033375ec278a46"'
1115 self.assertEqual(our_request, working_request)
1116
1117
1118 def testDigestObjectStale(self):
1119 credentials = ('joe', 'password')
1120 host = None
1121 request_uri = '/projects/httplib2/test/digest/'
1122 headers = {}
1123 response = httplib2.Response({ })
1124 response['www-authenticate'] = 'Digest realm="myrealm", nonce="Ygk86AsKBAA=3516200d37f9a3230352fde99977bd6d472d4306", algorithm=MD5, qop="auth", stale=true'
1125 response.status = 401
1126 content = ""
jcgregorio6cbab7e2006-04-21 20:35:43 +00001127 d = httplib2.DigestAuthentication(credentials, host, request_uri, headers, response, content, None)
jcgregorio2d66d4f2006-02-07 05:34:14 +00001128 # Returns true to force a retry
1129 self.assertTrue( d.response(response, content) )
1130
1131 def testDigestObjectAuthInfo(self):
1132 credentials = ('joe', 'password')
1133 host = None
1134 request_uri = '/projects/httplib2/test/digest/'
1135 headers = {}
1136 response = httplib2.Response({ })
1137 response['www-authenticate'] = 'Digest realm="myrealm", nonce="Ygk86AsKBAA=3516200d37f9a3230352fde99977bd6d472d4306", algorithm=MD5, qop="auth", stale=true'
1138 response['authentication-info'] = 'nextnonce="fred"'
1139 content = ""
jcgregorio6cbab7e2006-04-21 20:35:43 +00001140 d = httplib2.DigestAuthentication(credentials, host, request_uri, headers, response, content, None)
jcgregorio2d66d4f2006-02-07 05:34:14 +00001141 # Returns true to force a retry
1142 self.assertFalse( d.response(response, content) )
1143 self.assertEqual('fred', d.challenge['nonce'])
1144 self.assertEqual(1, d.challenge['nc'])
1145
1146 def testWsseAlgorithm(self):
1147 digest = httplib2._wsse_username_token("d36e316282959a9ed4c89851497a717f", "2003-12-15T14:43:07Z", "taadtaadpstcsm")
1148 expected = "quR/EWLAV4xLf9Zqyw4pDmfV9OY="
1149 self.assertEqual(expected, digest)
1150
jcgregoriodb8dfc82006-03-31 14:59:46 +00001151 def testEnd2End(self):
1152 # one end to end header
1153 response = {'content-type': 'application/atom+xml', 'te': 'deflate'}
1154 end2end = httplib2._get_end2end_headers(response)
1155 self.assertTrue('content-type' in end2end)
1156 self.assertTrue('te' not in end2end)
1157 self.assertTrue('connection' not in end2end)
1158
1159 # one end to end header that gets eliminated
1160 response = {'connection': 'content-type', 'content-type': 'application/atom+xml', 'te': 'deflate'}
1161 end2end = httplib2._get_end2end_headers(response)
1162 self.assertTrue('content-type' not in end2end)
1163 self.assertTrue('te' not in end2end)
1164 self.assertTrue('connection' not in end2end)
1165
1166 # Degenerate case of no headers
1167 response = {}
1168 end2end = httplib2._get_end2end_headers(response)
1169 self.assertEquals(0, len(end2end))
1170
1171 # Degenerate case of connection referrring to a header not passed in
1172 response = {'connection': 'content-type'}
1173 end2end = httplib2._get_end2end_headers(response)
1174 self.assertEquals(0, len(end2end))
jcgregorio2d66d4f2006-02-07 05:34:14 +00001175
1176unittest.main()
1177