1"""Regression tests for what was in Python 2's "urllib" module""" 2 3import urllib.parse 4import urllib.request 5import urllib.error 6import http.client 7import email.message 8import io 9import unittest 10from unittest.mock import patch 11from test import support 12import os 13try: 14 import ssl 15except ImportError: 16 ssl = None 17import sys 18import tempfile 19from nturl2path import url2pathname, pathname2url 20 21from base64 import b64encode 22import collections 23 24 25def hexescape(char): 26 """Escape char as RFC 2396 specifies""" 27 hex_repr = hex(ord(char))[2:].upper() 28 if len(hex_repr) == 1: 29 hex_repr = "0%s" % hex_repr 30 return "%" + hex_repr 31 32# Shortcut for testing FancyURLopener 33_urlopener = None 34 35 36def urlopen(url, data=None, proxies=None): 37 """urlopen(url [, data]) -> open file-like object""" 38 global _urlopener 39 if proxies is not None: 40 opener = urllib.request.FancyURLopener(proxies=proxies) 41 elif not _urlopener: 42 opener = FancyURLopener() 43 _urlopener = opener 44 else: 45 opener = _urlopener 46 if data is None: 47 return opener.open(url) 48 else: 49 return opener.open(url, data) 50 51 52def FancyURLopener(): 53 with support.check_warnings( 54 ('FancyURLopener style of invoking requests is deprecated.', 55 DeprecationWarning)): 56 return urllib.request.FancyURLopener() 57 58 59def fakehttp(fakedata, mock_close=False): 60 class FakeSocket(io.BytesIO): 61 io_refs = 1 62 63 def sendall(self, data): 64 FakeHTTPConnection.buf = data 65 66 def makefile(self, *args, **kwds): 67 self.io_refs += 1 68 return self 69 70 def read(self, amt=None): 71 if self.closed: 72 return b"" 73 return io.BytesIO.read(self, amt) 74 75 def readline(self, length=None): 76 if self.closed: 77 return b"" 78 return io.BytesIO.readline(self, length) 79 80 def close(self): 81 self.io_refs -= 1 82 if self.io_refs == 0: 83 io.BytesIO.close(self) 84 85 class FakeHTTPConnection(http.client.HTTPConnection): 86 87 # buffer to store data for verification in urlopen tests. 88 buf = None 89 90 def connect(self): 91 self.sock = FakeSocket(self.fakedata) 92 type(self).fakesock = self.sock 93 94 if mock_close: 95 # bpo-36918: HTTPConnection destructor calls close() which calls 96 # flush(). Problem: flush() calls self.fp.flush() which raises 97 # "ValueError: I/O operation on closed file" which is logged as an 98 # "Exception ignored in". Override close() to silence this error. 99 def close(self): 100 pass 101 FakeHTTPConnection.fakedata = fakedata 102 103 return FakeHTTPConnection 104 105 106class FakeHTTPMixin(object): 107 def fakehttp(self, fakedata, mock_close=False): 108 fake_http_class = fakehttp(fakedata, mock_close=mock_close) 109 self._connection_class = http.client.HTTPConnection 110 http.client.HTTPConnection = fake_http_class 111 112 def unfakehttp(self): 113 http.client.HTTPConnection = self._connection_class 114 115 116class FakeFTPMixin(object): 117 def fakeftp(self): 118 class FakeFtpWrapper(object): 119 def __init__(self, user, passwd, host, port, dirs, timeout=None, 120 persistent=True): 121 pass 122 123 def retrfile(self, file, type): 124 return io.BytesIO(), 0 125 126 def close(self): 127 pass 128 129 self._ftpwrapper_class = urllib.request.ftpwrapper 130 urllib.request.ftpwrapper = FakeFtpWrapper 131 132 def unfakeftp(self): 133 urllib.request.ftpwrapper = self._ftpwrapper_class 134 135 136class urlopen_FileTests(unittest.TestCase): 137 """Test urlopen() opening a temporary file. 138 139 Try to test as much functionality as possible so as to cut down on reliance 140 on connecting to the Net for testing. 141 142 """ 143 144 def setUp(self): 145 # Create a temp file to use for testing 146 self.text = bytes("test_urllib: %s\n" % self.__class__.__name__, 147 "ascii") 148 f = open(support.TESTFN, 'wb') 149 try: 150 f.write(self.text) 151 finally: 152 f.close() 153 self.pathname = support.TESTFN 154 self.returned_obj = urlopen("file:%s" % self.pathname) 155 156 def tearDown(self): 157 """Shut down the open object""" 158 self.returned_obj.close() 159 os.remove(support.TESTFN) 160 161 def test_interface(self): 162 # Make sure object returned by urlopen() has the specified methods 163 for attr in ("read", "readline", "readlines", "fileno", 164 "close", "info", "geturl", "getcode", "__iter__"): 165 self.assertTrue(hasattr(self.returned_obj, attr), 166 "object returned by urlopen() lacks %s attribute" % 167 attr) 168 169 def test_read(self): 170 self.assertEqual(self.text, self.returned_obj.read()) 171 172 def test_readline(self): 173 self.assertEqual(self.text, self.returned_obj.readline()) 174 self.assertEqual(b'', self.returned_obj.readline(), 175 "calling readline() after exhausting the file did not" 176 " return an empty string") 177 178 def test_readlines(self): 179 lines_list = self.returned_obj.readlines() 180 self.assertEqual(len(lines_list), 1, 181 "readlines() returned the wrong number of lines") 182 self.assertEqual(lines_list[0], self.text, 183 "readlines() returned improper text") 184 185 def test_fileno(self): 186 file_num = self.returned_obj.fileno() 187 self.assertIsInstance(file_num, int, "fileno() did not return an int") 188 self.assertEqual(os.read(file_num, len(self.text)), self.text, 189 "Reading on the file descriptor returned by fileno() " 190 "did not return the expected text") 191 192 def test_close(self): 193 # Test close() by calling it here and then having it be called again 194 # by the tearDown() method for the test 195 self.returned_obj.close() 196 197 def test_info(self): 198 self.assertIsInstance(self.returned_obj.info(), email.message.Message) 199 200 def test_geturl(self): 201 self.assertEqual(self.returned_obj.geturl(), self.pathname) 202 203 def test_getcode(self): 204 self.assertIsNone(self.returned_obj.getcode()) 205 206 def test_iter(self): 207 # Test iterator 208 # Don't need to count number of iterations since test would fail the 209 # instant it returned anything beyond the first line from the 210 # comparison. 211 # Use the iterator in the usual implicit way to test for ticket #4608. 212 for line in self.returned_obj: 213 self.assertEqual(line, self.text) 214 215 def test_relativelocalfile(self): 216 self.assertRaises(ValueError,urllib.request.urlopen,'./' + self.pathname) 217 218 219class ProxyTests(unittest.TestCase): 220 221 def setUp(self): 222 # Records changes to env vars 223 self.env = support.EnvironmentVarGuard() 224 # Delete all proxy related env vars 225 for k in list(os.environ): 226 if 'proxy' in k.lower(): 227 self.env.unset(k) 228 229 def tearDown(self): 230 # Restore all proxy related env vars 231 self.env.__exit__() 232 del self.env 233 234 def test_getproxies_environment_keep_no_proxies(self): 235 self.env.set('NO_PROXY', 'localhost') 236 proxies = urllib.request.getproxies_environment() 237 # getproxies_environment use lowered case truncated (no '_proxy') keys 238 self.assertEqual('localhost', proxies['no']) 239 # List of no_proxies with space. 240 self.env.set('NO_PROXY', 'localhost, anotherdomain.com, newdomain.com:1234') 241 self.assertTrue(urllib.request.proxy_bypass_environment('anotherdomain.com')) 242 self.assertTrue(urllib.request.proxy_bypass_environment('anotherdomain.com:8888')) 243 self.assertTrue(urllib.request.proxy_bypass_environment('newdomain.com:1234')) 244 245 def test_proxy_cgi_ignore(self): 246 try: 247 self.env.set('HTTP_PROXY', 'http://somewhere:3128') 248 proxies = urllib.request.getproxies_environment() 249 self.assertEqual('http://somewhere:3128', proxies['http']) 250 self.env.set('REQUEST_METHOD', 'GET') 251 proxies = urllib.request.getproxies_environment() 252 self.assertNotIn('http', proxies) 253 finally: 254 self.env.unset('REQUEST_METHOD') 255 self.env.unset('HTTP_PROXY') 256 257 def test_proxy_bypass_environment_host_match(self): 258 bypass = urllib.request.proxy_bypass_environment 259 self.env.set('NO_PROXY', 260 'localhost, anotherdomain.com, newdomain.com:1234, .d.o.t') 261 self.assertTrue(bypass('localhost')) 262 self.assertTrue(bypass('LocalHost')) # MixedCase 263 self.assertTrue(bypass('LOCALHOST')) # UPPERCASE 264 self.assertTrue(bypass('.localhost')) 265 self.assertTrue(bypass('newdomain.com:1234')) 266 self.assertTrue(bypass('.newdomain.com:1234')) 267 self.assertTrue(bypass('foo.d.o.t')) # issue 29142 268 self.assertTrue(bypass('d.o.t')) 269 self.assertTrue(bypass('anotherdomain.com:8888')) 270 self.assertTrue(bypass('.anotherdomain.com:8888')) 271 self.assertTrue(bypass('www.newdomain.com:1234')) 272 self.assertFalse(bypass('prelocalhost')) 273 self.assertFalse(bypass('newdomain.com')) # no port 274 self.assertFalse(bypass('newdomain.com:1235')) # wrong port 275 276 def test_proxy_bypass_environment_always_match(self): 277 bypass = urllib.request.proxy_bypass_environment 278 self.env.set('NO_PROXY', '*') 279 self.assertTrue(bypass('newdomain.com')) 280 self.assertTrue(bypass('newdomain.com:1234')) 281 self.env.set('NO_PROXY', '*, anotherdomain.com') 282 self.assertTrue(bypass('anotherdomain.com')) 283 self.assertFalse(bypass('newdomain.com')) 284 self.assertFalse(bypass('newdomain.com:1234')) 285 286 def test_proxy_bypass_environment_newline(self): 287 bypass = urllib.request.proxy_bypass_environment 288 self.env.set('NO_PROXY', 289 'localhost, anotherdomain.com, newdomain.com:1234') 290 self.assertFalse(bypass('localhost\n')) 291 self.assertFalse(bypass('anotherdomain.com:8888\n')) 292 self.assertFalse(bypass('newdomain.com:1234\n')) 293 294 295class ProxyTests_withOrderedEnv(unittest.TestCase): 296 297 def setUp(self): 298 # We need to test conditions, where variable order _is_ significant 299 self._saved_env = os.environ 300 # Monkey patch os.environ, start with empty fake environment 301 os.environ = collections.OrderedDict() 302 303 def tearDown(self): 304 os.environ = self._saved_env 305 306 def test_getproxies_environment_prefer_lowercase(self): 307 # Test lowercase preference with removal 308 os.environ['no_proxy'] = '' 309 os.environ['No_Proxy'] = 'localhost' 310 self.assertFalse(urllib.request.proxy_bypass_environment('localhost')) 311 self.assertFalse(urllib.request.proxy_bypass_environment('arbitrary')) 312 os.environ['http_proxy'] = '' 313 os.environ['HTTP_PROXY'] = 'http://somewhere:3128' 314 proxies = urllib.request.getproxies_environment() 315 self.assertEqual({}, proxies) 316 # Test lowercase preference of proxy bypass and correct matching including ports 317 os.environ['no_proxy'] = 'localhost, noproxy.com, my.proxy:1234' 318 os.environ['No_Proxy'] = 'xyz.com' 319 self.assertTrue(urllib.request.proxy_bypass_environment('localhost')) 320 self.assertTrue(urllib.request.proxy_bypass_environment('noproxy.com:5678')) 321 self.assertTrue(urllib.request.proxy_bypass_environment('my.proxy:1234')) 322 self.assertFalse(urllib.request.proxy_bypass_environment('my.proxy')) 323 self.assertFalse(urllib.request.proxy_bypass_environment('arbitrary')) 324 # Test lowercase preference with replacement 325 os.environ['http_proxy'] = 'http://somewhere:3128' 326 os.environ['Http_Proxy'] = 'http://somewhereelse:3128' 327 proxies = urllib.request.getproxies_environment() 328 self.assertEqual('http://somewhere:3128', proxies['http']) 329 330 331class urlopen_HttpTests(unittest.TestCase, FakeHTTPMixin, FakeFTPMixin): 332 """Test urlopen() opening a fake http connection.""" 333 334 def check_read(self, ver): 335 self.fakehttp(b"HTTP/" + ver + b" 200 OK\r\n\r\nHello!") 336 try: 337 fp = urlopen("http://python.org/") 338 self.assertEqual(fp.readline(), b"Hello!") 339 self.assertEqual(fp.readline(), b"") 340 self.assertEqual(fp.geturl(), 'http://python.org/') 341 self.assertEqual(fp.getcode(), 200) 342 finally: 343 self.unfakehttp() 344 345 def test_url_fragment(self): 346 # Issue #11703: geturl() omits fragments in the original URL. 347 url = 'http://docs.python.org/library/urllib.html#OK' 348 self.fakehttp(b"HTTP/1.1 200 OK\r\n\r\nHello!") 349 try: 350 fp = urllib.request.urlopen(url) 351 self.assertEqual(fp.geturl(), url) 352 finally: 353 self.unfakehttp() 354 355 def test_willclose(self): 356 self.fakehttp(b"HTTP/1.1 200 OK\r\n\r\nHello!") 357 try: 358 resp = urlopen("http://www.python.org") 359 self.assertTrue(resp.fp.will_close) 360 finally: 361 self.unfakehttp() 362 363 @unittest.skipUnless(ssl, "ssl module required") 364 def test_url_path_with_control_char_rejected(self): 365 for char_no in list(range(0, 0x21)) + [0x7f]: 366 char = chr(char_no) 367 schemeless_url = f"//localhost:7777/test{char}/" 368 self.fakehttp(b"HTTP/1.1 200 OK\r\n\r\nHello.") 369 try: 370 # We explicitly test urllib.request.urlopen() instead of the top 371 # level 'def urlopen()' function defined in this... (quite ugly) 372 # test suite. They use different url opening codepaths. Plain 373 # urlopen uses FancyURLOpener which goes via a codepath that 374 # calls urllib.parse.quote() on the URL which makes all of the 375 # above attempts at injection within the url _path_ safe. 376 escaped_char_repr = repr(char).replace('\\', r'\\') 377 InvalidURL = http.client.InvalidURL 378 with self.assertRaisesRegex( 379 InvalidURL, f"contain control.*{escaped_char_repr}"): 380 urllib.request.urlopen(f"http:{schemeless_url}") 381 with self.assertRaisesRegex( 382 InvalidURL, f"contain control.*{escaped_char_repr}"): 383 urllib.request.urlopen(f"https:{schemeless_url}") 384 # This code path quotes the URL so there is no injection. 385 resp = urlopen(f"http:{schemeless_url}") 386 self.assertNotIn(char, resp.geturl()) 387 finally: 388 self.unfakehttp() 389 390 @unittest.skipUnless(ssl, "ssl module required") 391 def test_url_path_with_newline_header_injection_rejected(self): 392 self.fakehttp(b"HTTP/1.1 200 OK\r\n\r\nHello.") 393 host = "localhost:7777?a=1 HTTP/1.1\r\nX-injected: header\r\nTEST: 123" 394 schemeless_url = "//" + host + ":8080/test/?test=a" 395 try: 396 # We explicitly test urllib.request.urlopen() instead of the top 397 # level 'def urlopen()' function defined in this... (quite ugly) 398 # test suite. They use different url opening codepaths. Plain 399 # urlopen uses FancyURLOpener which goes via a codepath that 400 # calls urllib.parse.quote() on the URL which makes all of the 401 # above attempts at injection within the url _path_ safe. 402 InvalidURL = http.client.InvalidURL 403 with self.assertRaisesRegex( 404 InvalidURL, r"contain control.*\\r.*(found at least . .)"): 405 urllib.request.urlopen(f"http:{schemeless_url}") 406 with self.assertRaisesRegex(InvalidURL, r"contain control.*\\n"): 407 urllib.request.urlopen(f"https:{schemeless_url}") 408 # This code path quotes the URL so there is no injection. 409 resp = urlopen(f"http:{schemeless_url}") 410 self.assertNotIn(' ', resp.geturl()) 411 self.assertNotIn('\r', resp.geturl()) 412 self.assertNotIn('\n', resp.geturl()) 413 finally: 414 self.unfakehttp() 415 416 @unittest.skipUnless(ssl, "ssl module required") 417 def test_url_host_with_control_char_rejected(self): 418 for char_no in list(range(0, 0x21)) + [0x7f]: 419 char = chr(char_no) 420 schemeless_url = f"//localhost{char}/test/" 421 self.fakehttp(b"HTTP/1.1 200 OK\r\n\r\nHello.") 422 try: 423 escaped_char_repr = repr(char).replace('\\', r'\\') 424 InvalidURL = http.client.InvalidURL 425 with self.assertRaisesRegex( 426 InvalidURL, f"contain control.*{escaped_char_repr}"): 427 urlopen(f"http:{schemeless_url}") 428 with self.assertRaisesRegex(InvalidURL, f"contain control.*{escaped_char_repr}"): 429 urlopen(f"https:{schemeless_url}") 430 finally: 431 self.unfakehttp() 432 433 @unittest.skipUnless(ssl, "ssl module required") 434 def test_url_host_with_newline_header_injection_rejected(self): 435 self.fakehttp(b"HTTP/1.1 200 OK\r\n\r\nHello.") 436 host = "localhost\r\nX-injected: header\r\n" 437 schemeless_url = "//" + host + ":8080/test/?test=a" 438 try: 439 InvalidURL = http.client.InvalidURL 440 with self.assertRaisesRegex( 441 InvalidURL, r"contain control.*\\r"): 442 urlopen(f"http:{schemeless_url}") 443 with self.assertRaisesRegex(InvalidURL, r"contain control.*\\n"): 444 urlopen(f"https:{schemeless_url}") 445 finally: 446 self.unfakehttp() 447 448 def test_read_0_9(self): 449 # "0.9" response accepted (but not "simple responses" without 450 # a status line) 451 self.check_read(b"0.9") 452 453 def test_read_1_0(self): 454 self.check_read(b"1.0") 455 456 def test_read_1_1(self): 457 self.check_read(b"1.1") 458 459 def test_read_bogus(self): 460 # urlopen() should raise OSError for many error codes. 461 self.fakehttp(b'''HTTP/1.1 401 Authentication Required 462Date: Wed, 02 Jan 2008 03:03:54 GMT 463Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e 464Connection: close 465Content-Type: text/html; charset=iso-8859-1 466''', mock_close=True) 467 try: 468 self.assertRaises(OSError, urlopen, "http://python.org/") 469 finally: 470 self.unfakehttp() 471 472 def test_invalid_redirect(self): 473 # urlopen() should raise OSError for many error codes. 474 self.fakehttp(b'''HTTP/1.1 302 Found 475Date: Wed, 02 Jan 2008 03:03:54 GMT 476Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e 477Location: file://guidocomputer.athome.com:/python/license 478Connection: close 479Content-Type: text/html; charset=iso-8859-1 480''', mock_close=True) 481 try: 482 msg = "Redirection to url 'file:" 483 with self.assertRaisesRegex(urllib.error.HTTPError, msg): 484 urlopen("http://python.org/") 485 finally: 486 self.unfakehttp() 487 488 def test_redirect_limit_independent(self): 489 # Ticket #12923: make sure independent requests each use their 490 # own retry limit. 491 for i in range(FancyURLopener().maxtries): 492 self.fakehttp(b'''HTTP/1.1 302 Found 493Location: file://guidocomputer.athome.com:/python/license 494Connection: close 495''', mock_close=True) 496 try: 497 self.assertRaises(urllib.error.HTTPError, urlopen, 498 "http://something") 499 finally: 500 self.unfakehttp() 501 502 def test_empty_socket(self): 503 # urlopen() raises OSError if the underlying socket does not send any 504 # data. (#1680230) 505 self.fakehttp(b'') 506 try: 507 self.assertRaises(OSError, urlopen, "http://something") 508 finally: 509 self.unfakehttp() 510 511 def test_missing_localfile(self): 512 # Test for #10836 513 with self.assertRaises(urllib.error.URLError) as e: 514 urlopen('file://localhost/a/file/which/doesnot/exists.py') 515 self.assertTrue(e.exception.filename) 516 self.assertTrue(e.exception.reason) 517 518 def test_file_notexists(self): 519 fd, tmp_file = tempfile.mkstemp() 520 tmp_fileurl = 'file://localhost/' + tmp_file.replace(os.path.sep, '/') 521 try: 522 self.assertTrue(os.path.exists(tmp_file)) 523 with urlopen(tmp_fileurl) as fobj: 524 self.assertTrue(fobj) 525 finally: 526 os.close(fd) 527 os.unlink(tmp_file) 528 self.assertFalse(os.path.exists(tmp_file)) 529 with self.assertRaises(urllib.error.URLError): 530 urlopen(tmp_fileurl) 531 532 def test_ftp_nohost(self): 533 test_ftp_url = 'ftp:///path' 534 with self.assertRaises(urllib.error.URLError) as e: 535 urlopen(test_ftp_url) 536 self.assertFalse(e.exception.filename) 537 self.assertTrue(e.exception.reason) 538 539 def test_ftp_nonexisting(self): 540 with self.assertRaises(urllib.error.URLError) as e: 541 urlopen('ftp://localhost/a/file/which/doesnot/exists.py') 542 self.assertFalse(e.exception.filename) 543 self.assertTrue(e.exception.reason) 544 545 @patch.object(urllib.request, 'MAXFTPCACHE', 0) 546 def test_ftp_cache_pruning(self): 547 self.fakeftp() 548 try: 549 urllib.request.ftpcache['test'] = urllib.request.ftpwrapper('user', 'pass', 'localhost', 21, []) 550 urlopen('ftp://localhost') 551 finally: 552 self.unfakeftp() 553 554 def test_userpass_inurl(self): 555 self.fakehttp(b"HTTP/1.0 200 OK\r\n\r\nHello!") 556 try: 557 fp = urlopen("http://user:pass@python.org/") 558 self.assertEqual(fp.readline(), b"Hello!") 559 self.assertEqual(fp.readline(), b"") 560 self.assertEqual(fp.geturl(), 'http://user:pass@python.org/') 561 self.assertEqual(fp.getcode(), 200) 562 finally: 563 self.unfakehttp() 564 565 def test_userpass_inurl_w_spaces(self): 566 self.fakehttp(b"HTTP/1.0 200 OK\r\n\r\nHello!") 567 try: 568 userpass = "a b:c d" 569 url = "http://{}@python.org/".format(userpass) 570 fakehttp_wrapper = http.client.HTTPConnection 571 authorization = ("Authorization: Basic %s\r\n" % 572 b64encode(userpass.encode("ASCII")).decode("ASCII")) 573 fp = urlopen(url) 574 # The authorization header must be in place 575 self.assertIn(authorization, fakehttp_wrapper.buf.decode("UTF-8")) 576 self.assertEqual(fp.readline(), b"Hello!") 577 self.assertEqual(fp.readline(), b"") 578 # the spaces are quoted in URL so no match 579 self.assertNotEqual(fp.geturl(), url) 580 self.assertEqual(fp.getcode(), 200) 581 finally: 582 self.unfakehttp() 583 584 def test_URLopener_deprecation(self): 585 with support.check_warnings(('',DeprecationWarning)): 586 urllib.request.URLopener() 587 588 @unittest.skipUnless(ssl, "ssl module required") 589 def test_cafile_and_context(self): 590 context = ssl.create_default_context() 591 with support.check_warnings(('', DeprecationWarning)): 592 with self.assertRaises(ValueError): 593 urllib.request.urlopen( 594 "https://localhost", cafile="/nonexistent/path", context=context 595 ) 596 597 598class urlopen_DataTests(unittest.TestCase): 599 """Test urlopen() opening a data URL.""" 600 601 def setUp(self): 602 # text containing URL special- and unicode-characters 603 self.text = "test data URLs :;,%=& \u00f6 \u00c4 " 604 # 2x1 pixel RGB PNG image with one black and one white pixel 605 self.image = ( 606 b'\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x00\x02\x00\x00\x00' 607 b'\x01\x08\x02\x00\x00\x00{@\xe8\xdd\x00\x00\x00\x01sRGB\x00\xae' 608 b'\xce\x1c\xe9\x00\x00\x00\x0fIDAT\x08\xd7c```\xf8\xff\xff?\x00' 609 b'\x06\x01\x02\xfe\no/\x1e\x00\x00\x00\x00IEND\xaeB`\x82') 610 611 self.text_url = ( 612 "data:text/plain;charset=UTF-8,test%20data%20URLs%20%3A%3B%2C%25%3" 613 "D%26%20%C3%B6%20%C3%84%20") 614 self.text_url_base64 = ( 615 "data:text/plain;charset=ISO-8859-1;base64,dGVzdCBkYXRhIFVSTHMgOjs" 616 "sJT0mIPYgxCA%3D") 617 # base64 encoded data URL that contains ignorable spaces, 618 # such as "\n", " ", "%0A", and "%20". 619 self.image_url = ( 620 "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAIAAAB7\n" 621 "QOjdAAAAAXNSR0IArs4c6QAAAA9JREFUCNdj%0AYGBg%2BP//PwAGAQL%2BCm8 " 622 "vHgAAAABJRU5ErkJggg%3D%3D%0A%20") 623 624 self.text_url_resp = urllib.request.urlopen(self.text_url) 625 self.text_url_base64_resp = urllib.request.urlopen( 626 self.text_url_base64) 627 self.image_url_resp = urllib.request.urlopen(self.image_url) 628 629 def test_interface(self): 630 # Make sure object returned by urlopen() has the specified methods 631 for attr in ("read", "readline", "readlines", 632 "close", "info", "geturl", "getcode", "__iter__"): 633 self.assertTrue(hasattr(self.text_url_resp, attr), 634 "object returned by urlopen() lacks %s attribute" % 635 attr) 636 637 def test_info(self): 638 self.assertIsInstance(self.text_url_resp.info(), email.message.Message) 639 self.assertEqual(self.text_url_base64_resp.info().get_params(), 640 [('text/plain', ''), ('charset', 'ISO-8859-1')]) 641 self.assertEqual(self.image_url_resp.info()['content-length'], 642 str(len(self.image))) 643 self.assertEqual(urllib.request.urlopen("data:,").info().get_params(), 644 [('text/plain', ''), ('charset', 'US-ASCII')]) 645 646 def test_geturl(self): 647 self.assertEqual(self.text_url_resp.geturl(), self.text_url) 648 self.assertEqual(self.text_url_base64_resp.geturl(), 649 self.text_url_base64) 650 self.assertEqual(self.image_url_resp.geturl(), self.image_url) 651 652 def test_read_text(self): 653 self.assertEqual(self.text_url_resp.read().decode( 654 dict(self.text_url_resp.info().get_params())['charset']), self.text) 655 656 def test_read_text_base64(self): 657 self.assertEqual(self.text_url_base64_resp.read().decode( 658 dict(self.text_url_base64_resp.info().get_params())['charset']), 659 self.text) 660 661 def test_read_image(self): 662 self.assertEqual(self.image_url_resp.read(), self.image) 663 664 def test_missing_comma(self): 665 self.assertRaises(ValueError,urllib.request.urlopen,'data:text/plain') 666 667 def test_invalid_base64_data(self): 668 # missing padding character 669 self.assertRaises(ValueError,urllib.request.urlopen,'data:;base64,Cg=') 670 671 672class urlretrieve_FileTests(unittest.TestCase): 673 """Test urllib.urlretrieve() on local files""" 674 675 def setUp(self): 676 # Create a list of temporary files. Each item in the list is a file 677 # name (absolute path or relative to the current working directory). 678 # All files in this list will be deleted in the tearDown method. Note, 679 # this only helps to makes sure temporary files get deleted, but it 680 # does nothing about trying to close files that may still be open. It 681 # is the responsibility of the developer to properly close files even 682 # when exceptional conditions occur. 683 self.tempFiles = [] 684 685 # Create a temporary file. 686 self.registerFileForCleanUp(support.TESTFN) 687 self.text = b'testing urllib.urlretrieve' 688 try: 689 FILE = open(support.TESTFN, 'wb') 690 FILE.write(self.text) 691 FILE.close() 692 finally: 693 try: FILE.close() 694 except: pass 695 696 def tearDown(self): 697 # Delete the temporary files. 698 for each in self.tempFiles: 699 try: os.remove(each) 700 except: pass 701 702 def constructLocalFileUrl(self, filePath): 703 filePath = os.path.abspath(filePath) 704 try: 705 filePath.encode("utf-8") 706 except UnicodeEncodeError: 707 raise unittest.SkipTest("filePath is not encodable to utf8") 708 return "file://%s" % urllib.request.pathname2url(filePath) 709 710 def createNewTempFile(self, data=b""): 711 """Creates a new temporary file containing the specified data, 712 registers the file for deletion during the test fixture tear down, and 713 returns the absolute path of the file.""" 714 715 newFd, newFilePath = tempfile.mkstemp() 716 try: 717 self.registerFileForCleanUp(newFilePath) 718 newFile = os.fdopen(newFd, "wb") 719 newFile.write(data) 720 newFile.close() 721 finally: 722 try: newFile.close() 723 except: pass 724 return newFilePath 725 726 def registerFileForCleanUp(self, fileName): 727 self.tempFiles.append(fileName) 728 729 def test_basic(self): 730 # Make sure that a local file just gets its own location returned and 731 # a headers value is returned. 732 result = urllib.request.urlretrieve("file:%s" % support.TESTFN) 733 self.assertEqual(result[0], support.TESTFN) 734 self.assertIsInstance(result[1], email.message.Message, 735 "did not get an email.message.Message instance " 736 "as second returned value") 737 738 def test_copy(self): 739 # Test that setting the filename argument works. 740 second_temp = "%s.2" % support.TESTFN 741 self.registerFileForCleanUp(second_temp) 742 result = urllib.request.urlretrieve(self.constructLocalFileUrl( 743 support.TESTFN), second_temp) 744 self.assertEqual(second_temp, result[0]) 745 self.assertTrue(os.path.exists(second_temp), "copy of the file was not " 746 "made") 747 FILE = open(second_temp, 'rb') 748 try: 749 text = FILE.read() 750 FILE.close() 751 finally: 752 try: FILE.close() 753 except: pass 754 self.assertEqual(self.text, text) 755 756 def test_reporthook(self): 757 # Make sure that the reporthook works. 758 def hooktester(block_count, block_read_size, file_size, count_holder=[0]): 759 self.assertIsInstance(block_count, int) 760 self.assertIsInstance(block_read_size, int) 761 self.assertIsInstance(file_size, int) 762 self.assertEqual(block_count, count_holder[0]) 763 count_holder[0] = count_holder[0] + 1 764 second_temp = "%s.2" % support.TESTFN 765 self.registerFileForCleanUp(second_temp) 766 urllib.request.urlretrieve( 767 self.constructLocalFileUrl(support.TESTFN), 768 second_temp, hooktester) 769 770 def test_reporthook_0_bytes(self): 771 # Test on zero length file. Should call reporthook only 1 time. 772 report = [] 773 def hooktester(block_count, block_read_size, file_size, _report=report): 774 _report.append((block_count, block_read_size, file_size)) 775 srcFileName = self.createNewTempFile() 776 urllib.request.urlretrieve(self.constructLocalFileUrl(srcFileName), 777 support.TESTFN, hooktester) 778 self.assertEqual(len(report), 1) 779 self.assertEqual(report[0][2], 0) 780 781 def test_reporthook_5_bytes(self): 782 # Test on 5 byte file. Should call reporthook only 2 times (once when 783 # the "network connection" is established and once when the block is 784 # read). 785 report = [] 786 def hooktester(block_count, block_read_size, file_size, _report=report): 787 _report.append((block_count, block_read_size, file_size)) 788 srcFileName = self.createNewTempFile(b"x" * 5) 789 urllib.request.urlretrieve(self.constructLocalFileUrl(srcFileName), 790 support.TESTFN, hooktester) 791 self.assertEqual(len(report), 2) 792 self.assertEqual(report[0][2], 5) 793 self.assertEqual(report[1][2], 5) 794 795 def test_reporthook_8193_bytes(self): 796 # Test on 8193 byte file. Should call reporthook only 3 times (once 797 # when the "network connection" is established, once for the next 8192 798 # bytes, and once for the last byte). 799 report = [] 800 def hooktester(block_count, block_read_size, file_size, _report=report): 801 _report.append((block_count, block_read_size, file_size)) 802 srcFileName = self.createNewTempFile(b"x" * 8193) 803 urllib.request.urlretrieve(self.constructLocalFileUrl(srcFileName), 804 support.TESTFN, hooktester) 805 self.assertEqual(len(report), 3) 806 self.assertEqual(report[0][2], 8193) 807 self.assertEqual(report[0][1], 8192) 808 self.assertEqual(report[1][1], 8192) 809 self.assertEqual(report[2][1], 8192) 810 811 812class urlretrieve_HttpTests(unittest.TestCase, FakeHTTPMixin): 813 """Test urllib.urlretrieve() using fake http connections""" 814 815 def test_short_content_raises_ContentTooShortError(self): 816 self.fakehttp(b'''HTTP/1.1 200 OK 817Date: Wed, 02 Jan 2008 03:03:54 GMT 818Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e 819Connection: close 820Content-Length: 100 821Content-Type: text/html; charset=iso-8859-1 822 823FF 824''') 825 826 def _reporthook(par1, par2, par3): 827 pass 828 829 with self.assertRaises(urllib.error.ContentTooShortError): 830 try: 831 urllib.request.urlretrieve(support.TEST_HTTP_URL, 832 reporthook=_reporthook) 833 finally: 834 self.unfakehttp() 835 836 def test_short_content_raises_ContentTooShortError_without_reporthook(self): 837 self.fakehttp(b'''HTTP/1.1 200 OK 838Date: Wed, 02 Jan 2008 03:03:54 GMT 839Server: Apache/1.3.33 (Debian GNU/Linux) mod_ssl/2.8.22 OpenSSL/0.9.7e 840Connection: close 841Content-Length: 100 842Content-Type: text/html; charset=iso-8859-1 843 844FF 845''') 846 with self.assertRaises(urllib.error.ContentTooShortError): 847 try: 848 urllib.request.urlretrieve(support.TEST_HTTP_URL) 849 finally: 850 self.unfakehttp() 851 852 853class QuotingTests(unittest.TestCase): 854 r"""Tests for urllib.quote() and urllib.quote_plus() 855 856 According to RFC 3986 (Uniform Resource Identifiers), to escape a 857 character you write it as '%' + <2 character US-ASCII hex value>. 858 The Python code of ``'%' + hex(ord(<character>))[2:]`` escapes a 859 character properly. Case does not matter on the hex letters. 860 861 The various character sets specified are: 862 863 Reserved characters : ";/?:@&=+$," 864 Have special meaning in URIs and must be escaped if not being used for 865 their special meaning 866 Data characters : letters, digits, and "-_.!~*'()" 867 Unreserved and do not need to be escaped; can be, though, if desired 868 Control characters : 0x00 - 0x1F, 0x7F 869 Have no use in URIs so must be escaped 870 space : 0x20 871 Must be escaped 872 Delimiters : '<>#%"' 873 Must be escaped 874 Unwise : "{}|\^[]`" 875 Must be escaped 876 877 """ 878 879 def test_never_quote(self): 880 # Make sure quote() does not quote letters, digits, and "_,.-" 881 do_not_quote = '' .join(["ABCDEFGHIJKLMNOPQRSTUVWXYZ", 882 "abcdefghijklmnopqrstuvwxyz", 883 "0123456789", 884 "_.-~"]) 885 result = urllib.parse.quote(do_not_quote) 886 self.assertEqual(do_not_quote, result, 887 "using quote(): %r != %r" % (do_not_quote, result)) 888 result = urllib.parse.quote_plus(do_not_quote) 889 self.assertEqual(do_not_quote, result, 890 "using quote_plus(): %r != %r" % (do_not_quote, result)) 891 892 def test_default_safe(self): 893 # Test '/' is default value for 'safe' parameter 894 self.assertEqual(urllib.parse.quote.__defaults__[0], '/') 895 896 def test_safe(self): 897 # Test setting 'safe' parameter does what it should do 898 quote_by_default = "<>" 899 result = urllib.parse.quote(quote_by_default, safe=quote_by_default) 900 self.assertEqual(quote_by_default, result, 901 "using quote(): %r != %r" % (quote_by_default, result)) 902 result = urllib.parse.quote_plus(quote_by_default, 903 safe=quote_by_default) 904 self.assertEqual(quote_by_default, result, 905 "using quote_plus(): %r != %r" % 906 (quote_by_default, result)) 907 # Safe expressed as bytes rather than str 908 result = urllib.parse.quote(quote_by_default, safe=b"<>") 909 self.assertEqual(quote_by_default, result, 910 "using quote(): %r != %r" % (quote_by_default, result)) 911 # "Safe" non-ASCII characters should have no effect 912 # (Since URIs are not allowed to have non-ASCII characters) 913 result = urllib.parse.quote("a\xfcb", encoding="latin-1", safe="\xfc") 914 expect = urllib.parse.quote("a\xfcb", encoding="latin-1", safe="") 915 self.assertEqual(expect, result, 916 "using quote(): %r != %r" % 917 (expect, result)) 918 # Same as above, but using a bytes rather than str 919 result = urllib.parse.quote("a\xfcb", encoding="latin-1", safe=b"\xfc") 920 expect = urllib.parse.quote("a\xfcb", encoding="latin-1", safe="") 921 self.assertEqual(expect, result, 922 "using quote(): %r != %r" % 923 (expect, result)) 924 925 def test_default_quoting(self): 926 # Make sure all characters that should be quoted are by default sans 927 # space (separate test for that). 928 should_quote = [chr(num) for num in range(32)] # For 0x00 - 0x1F 929 should_quote.append(r'<>#%"{}|\^[]`') 930 should_quote.append(chr(127)) # For 0x7F 931 should_quote = ''.join(should_quote) 932 for char in should_quote: 933 result = urllib.parse.quote(char) 934 self.assertEqual(hexescape(char), result, 935 "using quote(): " 936 "%s should be escaped to %s, not %s" % 937 (char, hexescape(char), result)) 938 result = urllib.parse.quote_plus(char) 939 self.assertEqual(hexescape(char), result, 940 "using quote_plus(): " 941 "%s should be escapes to %s, not %s" % 942 (char, hexescape(char), result)) 943 del should_quote 944 partial_quote = "ab[]cd" 945 expected = "ab%5B%5Dcd" 946 result = urllib.parse.quote(partial_quote) 947 self.assertEqual(expected, result, 948 "using quote(): %r != %r" % (expected, result)) 949 result = urllib.parse.quote_plus(partial_quote) 950 self.assertEqual(expected, result, 951 "using quote_plus(): %r != %r" % (expected, result)) 952 953 def test_quoting_space(self): 954 # Make sure quote() and quote_plus() handle spaces as specified in 955 # their unique way 956 result = urllib.parse.quote(' ') 957 self.assertEqual(result, hexescape(' '), 958 "using quote(): %r != %r" % (result, hexescape(' '))) 959 result = urllib.parse.quote_plus(' ') 960 self.assertEqual(result, '+', 961 "using quote_plus(): %r != +" % result) 962 given = "a b cd e f" 963 expect = given.replace(' ', hexescape(' ')) 964 result = urllib.parse.quote(given) 965 self.assertEqual(expect, result, 966 "using quote(): %r != %r" % (expect, result)) 967 expect = given.replace(' ', '+') 968 result = urllib.parse.quote_plus(given) 969 self.assertEqual(expect, result, 970 "using quote_plus(): %r != %r" % (expect, result)) 971 972 def test_quoting_plus(self): 973 self.assertEqual(urllib.parse.quote_plus('alpha+beta gamma'), 974 'alpha%2Bbeta+gamma') 975 self.assertEqual(urllib.parse.quote_plus('alpha+beta gamma', '+'), 976 'alpha+beta+gamma') 977 # Test with bytes 978 self.assertEqual(urllib.parse.quote_plus(b'alpha+beta gamma'), 979 'alpha%2Bbeta+gamma') 980 # Test with safe bytes 981 self.assertEqual(urllib.parse.quote_plus('alpha+beta gamma', b'+'), 982 'alpha+beta+gamma') 983 984 def test_quote_bytes(self): 985 # Bytes should quote directly to percent-encoded values 986 given = b"\xa2\xd8ab\xff" 987 expect = "%A2%D8ab%FF" 988 result = urllib.parse.quote(given) 989 self.assertEqual(expect, result, 990 "using quote(): %r != %r" % (expect, result)) 991 # Encoding argument should raise type error on bytes input 992 self.assertRaises(TypeError, urllib.parse.quote, given, 993 encoding="latin-1") 994 # quote_from_bytes should work the same 995 result = urllib.parse.quote_from_bytes(given) 996 self.assertEqual(expect, result, 997 "using quote_from_bytes(): %r != %r" 998 % (expect, result)) 999 1000 def test_quote_with_unicode(self): 1001 # Characters in Latin-1 range, encoded by default in UTF-8 1002 given = "\xa2\xd8ab\xff" 1003 expect = "%C2%A2%C3%98ab%C3%BF" 1004 result = urllib.parse.quote(given) 1005 self.assertEqual(expect, result, 1006 "using quote(): %r != %r" % (expect, result)) 1007 # Characters in Latin-1 range, encoded by with None (default) 1008 result = urllib.parse.quote(given, encoding=None, errors=None) 1009 self.assertEqual(expect, result, 1010 "using quote(): %r != %r" % (expect, result)) 1011 # Characters in Latin-1 range, encoded with Latin-1 1012 given = "\xa2\xd8ab\xff" 1013 expect = "%A2%D8ab%FF" 1014 result = urllib.parse.quote(given, encoding="latin-1") 1015 self.assertEqual(expect, result, 1016 "using quote(): %r != %r" % (expect, result)) 1017 # Characters in BMP, encoded by default in UTF-8 1018 given = "\u6f22\u5b57" # "Kanji" 1019 expect = "%E6%BC%A2%E5%AD%97" 1020 result = urllib.parse.quote(given) 1021 self.assertEqual(expect, result, 1022 "using quote(): %r != %r" % (expect, result)) 1023 # Characters in BMP, encoded with Latin-1 1024 given = "\u6f22\u5b57" 1025 self.assertRaises(UnicodeEncodeError, urllib.parse.quote, given, 1026 encoding="latin-1") 1027 # Characters in BMP, encoded with Latin-1, with replace error handling 1028 given = "\u6f22\u5b57" 1029 expect = "%3F%3F" # "??" 1030 result = urllib.parse.quote(given, encoding="latin-1", 1031 errors="replace") 1032 self.assertEqual(expect, result, 1033 "using quote(): %r != %r" % (expect, result)) 1034 # Characters in BMP, Latin-1, with xmlcharref error handling 1035 given = "\u6f22\u5b57" 1036 expect = "%26%2328450%3B%26%2323383%3B" # "漢字" 1037 result = urllib.parse.quote(given, encoding="latin-1", 1038 errors="xmlcharrefreplace") 1039 self.assertEqual(expect, result, 1040 "using quote(): %r != %r" % (expect, result)) 1041 1042 def test_quote_plus_with_unicode(self): 1043 # Encoding (latin-1) test for quote_plus 1044 given = "\xa2\xd8 \xff" 1045 expect = "%A2%D8+%FF" 1046 result = urllib.parse.quote_plus(given, encoding="latin-1") 1047 self.assertEqual(expect, result, 1048 "using quote_plus(): %r != %r" % (expect, result)) 1049 # Errors test for quote_plus 1050 given = "ab\u6f22\u5b57 cd" 1051 expect = "ab%3F%3F+cd" 1052 result = urllib.parse.quote_plus(given, encoding="latin-1", 1053 errors="replace") 1054 self.assertEqual(expect, result, 1055 "using quote_plus(): %r != %r" % (expect, result)) 1056 1057 1058class UnquotingTests(unittest.TestCase): 1059 """Tests for unquote() and unquote_plus() 1060 1061 See the doc string for quoting_Tests for details on quoting and such. 1062 1063 """ 1064 1065 def test_unquoting(self): 1066 # Make sure unquoting of all ASCII values works 1067 escape_list = [] 1068 for num in range(128): 1069 given = hexescape(chr(num)) 1070 expect = chr(num) 1071 result = urllib.parse.unquote(given) 1072 self.assertEqual(expect, result, 1073 "using unquote(): %r != %r" % (expect, result)) 1074 result = urllib.parse.unquote_plus(given) 1075 self.assertEqual(expect, result, 1076 "using unquote_plus(): %r != %r" % 1077 (expect, result)) 1078 escape_list.append(given) 1079 escape_string = ''.join(escape_list) 1080 del escape_list 1081 result = urllib.parse.unquote(escape_string) 1082 self.assertEqual(result.count('%'), 1, 1083 "using unquote(): not all characters escaped: " 1084 "%s" % result) 1085 self.assertRaises((TypeError, AttributeError), urllib.parse.unquote, None) 1086 self.assertRaises((TypeError, AttributeError), urllib.parse.unquote, ()) 1087 with support.check_warnings(('', BytesWarning), quiet=True): 1088 self.assertRaises((TypeError, AttributeError), urllib.parse.unquote, b'') 1089 1090 def test_unquoting_badpercent(self): 1091 # Test unquoting on bad percent-escapes 1092 given = '%xab' 1093 expect = given 1094 result = urllib.parse.unquote(given) 1095 self.assertEqual(expect, result, "using unquote(): %r != %r" 1096 % (expect, result)) 1097 given = '%x' 1098 expect = given 1099 result = urllib.parse.unquote(given) 1100 self.assertEqual(expect, result, "using unquote(): %r != %r" 1101 % (expect, result)) 1102 given = '%' 1103 expect = given 1104 result = urllib.parse.unquote(given) 1105 self.assertEqual(expect, result, "using unquote(): %r != %r" 1106 % (expect, result)) 1107 # unquote_to_bytes 1108 given = '%xab' 1109 expect = bytes(given, 'ascii') 1110 result = urllib.parse.unquote_to_bytes(given) 1111 self.assertEqual(expect, result, "using unquote_to_bytes(): %r != %r" 1112 % (expect, result)) 1113 given = '%x' 1114 expect = bytes(given, 'ascii') 1115 result = urllib.parse.unquote_to_bytes(given) 1116 self.assertEqual(expect, result, "using unquote_to_bytes(): %r != %r" 1117 % (expect, result)) 1118 given = '%' 1119 expect = bytes(given, 'ascii') 1120 result = urllib.parse.unquote_to_bytes(given) 1121 self.assertEqual(expect, result, "using unquote_to_bytes(): %r != %r" 1122 % (expect, result)) 1123 self.assertRaises((TypeError, AttributeError), urllib.parse.unquote_to_bytes, None) 1124 self.assertRaises((TypeError, AttributeError), urllib.parse.unquote_to_bytes, ()) 1125 1126 def test_unquoting_mixed_case(self): 1127 # Test unquoting on mixed-case hex digits in the percent-escapes 1128 given = '%Ab%eA' 1129 expect = b'\xab\xea' 1130 result = urllib.parse.unquote_to_bytes(given) 1131 self.assertEqual(expect, result, 1132 "using unquote_to_bytes(): %r != %r" 1133 % (expect, result)) 1134 1135 def test_unquoting_parts(self): 1136 # Make sure unquoting works when have non-quoted characters 1137 # interspersed 1138 given = 'ab%sd' % hexescape('c') 1139 expect = "abcd" 1140 result = urllib.parse.unquote(given) 1141 self.assertEqual(expect, result, 1142 "using quote(): %r != %r" % (expect, result)) 1143 result = urllib.parse.unquote_plus(given) 1144 self.assertEqual(expect, result, 1145 "using unquote_plus(): %r != %r" % (expect, result)) 1146 1147 def test_unquoting_plus(self): 1148 # Test difference between unquote() and unquote_plus() 1149 given = "are+there+spaces..." 1150 expect = given 1151 result = urllib.parse.unquote(given) 1152 self.assertEqual(expect, result, 1153 "using unquote(): %r != %r" % (expect, result)) 1154 expect = given.replace('+', ' ') 1155 result = urllib.parse.unquote_plus(given) 1156 self.assertEqual(expect, result, 1157 "using unquote_plus(): %r != %r" % (expect, result)) 1158 1159 def test_unquote_to_bytes(self): 1160 given = 'br%C3%BCckner_sapporo_20050930.doc' 1161 expect = b'br\xc3\xbcckner_sapporo_20050930.doc' 1162 result = urllib.parse.unquote_to_bytes(given) 1163 self.assertEqual(expect, result, 1164 "using unquote_to_bytes(): %r != %r" 1165 % (expect, result)) 1166 # Test on a string with unescaped non-ASCII characters 1167 # (Technically an invalid URI; expect those characters to be UTF-8 1168 # encoded). 1169 result = urllib.parse.unquote_to_bytes("\u6f22%C3%BC") 1170 expect = b'\xe6\xbc\xa2\xc3\xbc' # UTF-8 for "\u6f22\u00fc" 1171 self.assertEqual(expect, result, 1172 "using unquote_to_bytes(): %r != %r" 1173 % (expect, result)) 1174 # Test with a bytes as input 1175 given = b'%A2%D8ab%FF' 1176 expect = b'\xa2\xd8ab\xff' 1177 result = urllib.parse.unquote_to_bytes(given) 1178 self.assertEqual(expect, result, 1179 "using unquote_to_bytes(): %r != %r" 1180 % (expect, result)) 1181 # Test with a bytes as input, with unescaped non-ASCII bytes 1182 # (Technically an invalid URI; expect those bytes to be preserved) 1183 given = b'%A2\xd8ab%FF' 1184 expect = b'\xa2\xd8ab\xff' 1185 result = urllib.parse.unquote_to_bytes(given) 1186 self.assertEqual(expect, result, 1187 "using unquote_to_bytes(): %r != %r" 1188 % (expect, result)) 1189 1190 def test_unquote_with_unicode(self): 1191 # Characters in the Latin-1 range, encoded with UTF-8 1192 given = 'br%C3%BCckner_sapporo_20050930.doc' 1193 expect = 'br\u00fcckner_sapporo_20050930.doc' 1194 result = urllib.parse.unquote(given) 1195 self.assertEqual(expect, result, 1196 "using unquote(): %r != %r" % (expect, result)) 1197 # Characters in the Latin-1 range, encoded with None (default) 1198 result = urllib.parse.unquote(given, encoding=None, errors=None) 1199 self.assertEqual(expect, result, 1200 "using unquote(): %r != %r" % (expect, result)) 1201 1202 # Characters in the Latin-1 range, encoded with Latin-1 1203 result = urllib.parse.unquote('br%FCckner_sapporo_20050930.doc', 1204 encoding="latin-1") 1205 expect = 'br\u00fcckner_sapporo_20050930.doc' 1206 self.assertEqual(expect, result, 1207 "using unquote(): %r != %r" % (expect, result)) 1208 1209 # Characters in BMP, encoded with UTF-8 1210 given = "%E6%BC%A2%E5%AD%97" 1211 expect = "\u6f22\u5b57" # "Kanji" 1212 result = urllib.parse.unquote(given) 1213 self.assertEqual(expect, result, 1214 "using unquote(): %r != %r" % (expect, result)) 1215 1216 # Decode with UTF-8, invalid sequence 1217 given = "%F3%B1" 1218 expect = "\ufffd" # Replacement character 1219 result = urllib.parse.unquote(given) 1220 self.assertEqual(expect, result, 1221 "using unquote(): %r != %r" % (expect, result)) 1222 1223 # Decode with UTF-8, invalid sequence, replace errors 1224 result = urllib.parse.unquote(given, errors="replace") 1225 self.assertEqual(expect, result, 1226 "using unquote(): %r != %r" % (expect, result)) 1227 1228 # Decode with UTF-8, invalid sequence, ignoring errors 1229 given = "%F3%B1" 1230 expect = "" 1231 result = urllib.parse.unquote(given, errors="ignore") 1232 self.assertEqual(expect, result, 1233 "using unquote(): %r != %r" % (expect, result)) 1234 1235 # A mix of non-ASCII and percent-encoded characters, UTF-8 1236 result = urllib.parse.unquote("\u6f22%C3%BC") 1237 expect = '\u6f22\u00fc' 1238 self.assertEqual(expect, result, 1239 "using unquote(): %r != %r" % (expect, result)) 1240 1241 # A mix of non-ASCII and percent-encoded characters, Latin-1 1242 # (Note, the string contains non-Latin-1-representable characters) 1243 result = urllib.parse.unquote("\u6f22%FC", encoding="latin-1") 1244 expect = '\u6f22\u00fc' 1245 self.assertEqual(expect, result, 1246 "using unquote(): %r != %r" % (expect, result)) 1247 1248 def test_unquoting_with_bytes_input(self): 1249 # Bytes not supported yet 1250 with self.assertRaisesRegex(TypeError, 'Expected str, got bytes'): 1251 given = b'bl\xc3\xa5b\xc3\xa6rsyltet\xc3\xb8y' 1252 urllib.parse.unquote(given) 1253 1254class urlencode_Tests(unittest.TestCase): 1255 """Tests for urlencode()""" 1256 1257 def help_inputtype(self, given, test_type): 1258 """Helper method for testing different input types. 1259 1260 'given' must lead to only the pairs: 1261 * 1st, 1 1262 * 2nd, 2 1263 * 3rd, 3 1264 1265 Test cannot assume anything about order. Docs make no guarantee and 1266 have possible dictionary input. 1267 1268 """ 1269 expect_somewhere = ["1st=1", "2nd=2", "3rd=3"] 1270 result = urllib.parse.urlencode(given) 1271 for expected in expect_somewhere: 1272 self.assertIn(expected, result, 1273 "testing %s: %s not found in %s" % 1274 (test_type, expected, result)) 1275 self.assertEqual(result.count('&'), 2, 1276 "testing %s: expected 2 '&'s; got %s" % 1277 (test_type, result.count('&'))) 1278 amp_location = result.index('&') 1279 on_amp_left = result[amp_location - 1] 1280 on_amp_right = result[amp_location + 1] 1281 self.assertTrue(on_amp_left.isdigit() and on_amp_right.isdigit(), 1282 "testing %s: '&' not located in proper place in %s" % 1283 (test_type, result)) 1284 self.assertEqual(len(result), (5 * 3) + 2, #5 chars per thing and amps 1285 "testing %s: " 1286 "unexpected number of characters: %s != %s" % 1287 (test_type, len(result), (5 * 3) + 2)) 1288 1289 def test_using_mapping(self): 1290 # Test passing in a mapping object as an argument. 1291 self.help_inputtype({"1st":'1', "2nd":'2', "3rd":'3'}, 1292 "using dict as input type") 1293 1294 def test_using_sequence(self): 1295 # Test passing in a sequence of two-item sequences as an argument. 1296 self.help_inputtype([('1st', '1'), ('2nd', '2'), ('3rd', '3')], 1297 "using sequence of two-item tuples as input") 1298 1299 def test_quoting(self): 1300 # Make sure keys and values are quoted using quote_plus() 1301 given = {"&":"="} 1302 expect = "%s=%s" % (hexescape('&'), hexescape('=')) 1303 result = urllib.parse.urlencode(given) 1304 self.assertEqual(expect, result) 1305 given = {"key name":"A bunch of pluses"} 1306 expect = "key+name=A+bunch+of+pluses" 1307 result = urllib.parse.urlencode(given) 1308 self.assertEqual(expect, result) 1309 1310 def test_doseq(self): 1311 # Test that passing True for 'doseq' parameter works correctly 1312 given = {'sequence':['1', '2', '3']} 1313 expect = "sequence=%s" % urllib.parse.quote_plus(str(['1', '2', '3'])) 1314 result = urllib.parse.urlencode(given) 1315 self.assertEqual(expect, result) 1316 result = urllib.parse.urlencode(given, True) 1317 for value in given["sequence"]: 1318 expect = "sequence=%s" % value 1319 self.assertIn(expect, result) 1320 self.assertEqual(result.count('&'), 2, 1321 "Expected 2 '&'s, got %s" % result.count('&')) 1322 1323 def test_empty_sequence(self): 1324 self.assertEqual("", urllib.parse.urlencode({})) 1325 self.assertEqual("", urllib.parse.urlencode([])) 1326 1327 def test_nonstring_values(self): 1328 self.assertEqual("a=1", urllib.parse.urlencode({"a": 1})) 1329 self.assertEqual("a=None", urllib.parse.urlencode({"a": None})) 1330 1331 def test_nonstring_seq_values(self): 1332 self.assertEqual("a=1&a=2", urllib.parse.urlencode({"a": [1, 2]}, True)) 1333 self.assertEqual("a=None&a=a", 1334 urllib.parse.urlencode({"a": [None, "a"]}, True)) 1335 data = collections.OrderedDict([("a", 1), ("b", 1)]) 1336 self.assertEqual("a=a&a=b", 1337 urllib.parse.urlencode({"a": data}, True)) 1338 1339 def test_urlencode_encoding(self): 1340 # ASCII encoding. Expect %3F with errors="replace' 1341 given = (('\u00a0', '\u00c1'),) 1342 expect = '%3F=%3F' 1343 result = urllib.parse.urlencode(given, encoding="ASCII", errors="replace") 1344 self.assertEqual(expect, result) 1345 1346 # Default is UTF-8 encoding. 1347 given = (('\u00a0', '\u00c1'),) 1348 expect = '%C2%A0=%C3%81' 1349 result = urllib.parse.urlencode(given) 1350 self.assertEqual(expect, result) 1351 1352 # Latin-1 encoding. 1353 given = (('\u00a0', '\u00c1'),) 1354 expect = '%A0=%C1' 1355 result = urllib.parse.urlencode(given, encoding="latin-1") 1356 self.assertEqual(expect, result) 1357 1358 def test_urlencode_encoding_doseq(self): 1359 # ASCII Encoding. Expect %3F with errors="replace' 1360 given = (('\u00a0', '\u00c1'),) 1361 expect = '%3F=%3F' 1362 result = urllib.parse.urlencode(given, doseq=True, 1363 encoding="ASCII", errors="replace") 1364 self.assertEqual(expect, result) 1365 1366 # ASCII Encoding. On a sequence of values. 1367 given = (("\u00a0", (1, "\u00c1")),) 1368 expect = '%3F=1&%3F=%3F' 1369 result = urllib.parse.urlencode(given, True, 1370 encoding="ASCII", errors="replace") 1371 self.assertEqual(expect, result) 1372 1373 # Utf-8 1374 given = (("\u00a0", "\u00c1"),) 1375 expect = '%C2%A0=%C3%81' 1376 result = urllib.parse.urlencode(given, True) 1377 self.assertEqual(expect, result) 1378 1379 given = (("\u00a0", (42, "\u00c1")),) 1380 expect = '%C2%A0=42&%C2%A0=%C3%81' 1381 result = urllib.parse.urlencode(given, True) 1382 self.assertEqual(expect, result) 1383 1384 # latin-1 1385 given = (("\u00a0", "\u00c1"),) 1386 expect = '%A0=%C1' 1387 result = urllib.parse.urlencode(given, True, encoding="latin-1") 1388 self.assertEqual(expect, result) 1389 1390 given = (("\u00a0", (42, "\u00c1")),) 1391 expect = '%A0=42&%A0=%C1' 1392 result = urllib.parse.urlencode(given, True, encoding="latin-1") 1393 self.assertEqual(expect, result) 1394 1395 def test_urlencode_bytes(self): 1396 given = ((b'\xa0\x24', b'\xc1\x24'),) 1397 expect = '%A0%24=%C1%24' 1398 result = urllib.parse.urlencode(given) 1399 self.assertEqual(expect, result) 1400 result = urllib.parse.urlencode(given, True) 1401 self.assertEqual(expect, result) 1402 1403 # Sequence of values 1404 given = ((b'\xa0\x24', (42, b'\xc1\x24')),) 1405 expect = '%A0%24=42&%A0%24=%C1%24' 1406 result = urllib.parse.urlencode(given, True) 1407 self.assertEqual(expect, result) 1408 1409 def test_urlencode_encoding_safe_parameter(self): 1410 1411 # Send '$' (\x24) as safe character 1412 # Default utf-8 encoding 1413 1414 given = ((b'\xa0\x24', b'\xc1\x24'),) 1415 result = urllib.parse.urlencode(given, safe=":$") 1416 expect = '%A0$=%C1$' 1417 self.assertEqual(expect, result) 1418 1419 given = ((b'\xa0\x24', b'\xc1\x24'),) 1420 result = urllib.parse.urlencode(given, doseq=True, safe=":$") 1421 expect = '%A0$=%C1$' 1422 self.assertEqual(expect, result) 1423 1424 # Safe parameter in sequence 1425 given = ((b'\xa0\x24', (b'\xc1\x24', 0xd, 42)),) 1426 expect = '%A0$=%C1$&%A0$=13&%A0$=42' 1427 result = urllib.parse.urlencode(given, True, safe=":$") 1428 self.assertEqual(expect, result) 1429 1430 # Test all above in latin-1 encoding 1431 1432 given = ((b'\xa0\x24', b'\xc1\x24'),) 1433 result = urllib.parse.urlencode(given, safe=":$", 1434 encoding="latin-1") 1435 expect = '%A0$=%C1$' 1436 self.assertEqual(expect, result) 1437 1438 given = ((b'\xa0\x24', b'\xc1\x24'),) 1439 expect = '%A0$=%C1$' 1440 result = urllib.parse.urlencode(given, doseq=True, safe=":$", 1441 encoding="latin-1") 1442 1443 given = ((b'\xa0\x24', (b'\xc1\x24', 0xd, 42)),) 1444 expect = '%A0$=%C1$&%A0$=13&%A0$=42' 1445 result = urllib.parse.urlencode(given, True, safe=":$", 1446 encoding="latin-1") 1447 self.assertEqual(expect, result) 1448 1449class Pathname_Tests(unittest.TestCase): 1450 """Test pathname2url() and url2pathname()""" 1451 1452 def test_basic(self): 1453 # Make sure simple tests pass 1454 expected_path = os.path.join("parts", "of", "a", "path") 1455 expected_url = "parts/of/a/path" 1456 result = urllib.request.pathname2url(expected_path) 1457 self.assertEqual(expected_url, result, 1458 "pathname2url() failed; %s != %s" % 1459 (result, expected_url)) 1460 result = urllib.request.url2pathname(expected_url) 1461 self.assertEqual(expected_path, result, 1462 "url2pathame() failed; %s != %s" % 1463 (result, expected_path)) 1464 1465 def test_quoting(self): 1466 # Test automatic quoting and unquoting works for pathnam2url() and 1467 # url2pathname() respectively 1468 given = os.path.join("needs", "quot=ing", "here") 1469 expect = "needs/%s/here" % urllib.parse.quote("quot=ing") 1470 result = urllib.request.pathname2url(given) 1471 self.assertEqual(expect, result, 1472 "pathname2url() failed; %s != %s" % 1473 (expect, result)) 1474 expect = given 1475 result = urllib.request.url2pathname(result) 1476 self.assertEqual(expect, result, 1477 "url2pathname() failed; %s != %s" % 1478 (expect, result)) 1479 given = os.path.join("make sure", "using_quote") 1480 expect = "%s/using_quote" % urllib.parse.quote("make sure") 1481 result = urllib.request.pathname2url(given) 1482 self.assertEqual(expect, result, 1483 "pathname2url() failed; %s != %s" % 1484 (expect, result)) 1485 given = "make+sure/using_unquote" 1486 expect = os.path.join("make+sure", "using_unquote") 1487 result = urllib.request.url2pathname(given) 1488 self.assertEqual(expect, result, 1489 "url2pathname() failed; %s != %s" % 1490 (expect, result)) 1491 1492 @unittest.skipUnless(sys.platform == 'win32', 1493 'test specific to the nturl2path functions.') 1494 def test_prefixes(self): 1495 # Test special prefixes are correctly handled in pathname2url() 1496 given = '\\\\?\\C:\\dir' 1497 expect = '///C:/dir' 1498 result = urllib.request.pathname2url(given) 1499 self.assertEqual(expect, result, 1500 "pathname2url() failed; %s != %s" % 1501 (expect, result)) 1502 given = '\\\\?\\unc\\server\\share\\dir' 1503 expect = '/server/share/dir' 1504 result = urllib.request.pathname2url(given) 1505 self.assertEqual(expect, result, 1506 "pathname2url() failed; %s != %s" % 1507 (expect, result)) 1508 1509 1510 @unittest.skipUnless(sys.platform == 'win32', 1511 'test specific to the urllib.url2path function.') 1512 def test_ntpath(self): 1513 given = ('/C:/', '///C:/', '/C|//') 1514 expect = 'C:\\' 1515 for url in given: 1516 result = urllib.request.url2pathname(url) 1517 self.assertEqual(expect, result, 1518 'urllib.request..url2pathname() failed; %s != %s' % 1519 (expect, result)) 1520 given = '///C|/path' 1521 expect = 'C:\\path' 1522 result = urllib.request.url2pathname(given) 1523 self.assertEqual(expect, result, 1524 'urllib.request.url2pathname() failed; %s != %s' % 1525 (expect, result)) 1526 1527class Utility_Tests(unittest.TestCase): 1528 """Testcase to test the various utility functions in the urllib.""" 1529 1530 def test_thishost(self): 1531 """Test the urllib.request.thishost utility function returns a tuple""" 1532 self.assertIsInstance(urllib.request.thishost(), tuple) 1533 1534 1535class URLopener_Tests(FakeHTTPMixin, unittest.TestCase): 1536 """Testcase to test the open method of URLopener class.""" 1537 1538 def test_quoted_open(self): 1539 class DummyURLopener(urllib.request.URLopener): 1540 def open_spam(self, url): 1541 return url 1542 with support.check_warnings( 1543 ('DummyURLopener style of invoking requests is deprecated.', 1544 DeprecationWarning)): 1545 self.assertEqual(DummyURLopener().open( 1546 'spam://example/ /'),'//example/%20/') 1547 1548 # test the safe characters are not quoted by urlopen 1549 self.assertEqual(DummyURLopener().open( 1550 "spam://c:|windows%/:=&?~#+!$,;'@()*[]|/path/"), 1551 "//c:|windows%/:=&?~#+!$,;'@()*[]|/path/") 1552 1553 @support.ignore_warnings(category=DeprecationWarning) 1554 def test_urlopener_retrieve_file(self): 1555 with support.temp_dir() as tmpdir: 1556 fd, tmpfile = tempfile.mkstemp(dir=tmpdir) 1557 os.close(fd) 1558 fileurl = "file:" + urllib.request.pathname2url(tmpfile) 1559 filename, _ = urllib.request.URLopener().retrieve(fileurl) 1560 # Some buildbots have TEMP folder that uses a lowercase drive letter. 1561 self.assertEqual(os.path.normcase(filename), os.path.normcase(tmpfile)) 1562 1563 @support.ignore_warnings(category=DeprecationWarning) 1564 def test_urlopener_retrieve_remote(self): 1565 url = "http://www.python.org/file.txt" 1566 self.fakehttp(b"HTTP/1.1 200 OK\r\n\r\nHello!") 1567 self.addCleanup(self.unfakehttp) 1568 filename, _ = urllib.request.URLopener().retrieve(url) 1569 self.assertEqual(os.path.splitext(filename)[1], ".txt") 1570 1571 @support.ignore_warnings(category=DeprecationWarning) 1572 def test_local_file_open(self): 1573 # bpo-35907, CVE-2019-9948: urllib must reject local_file:// scheme 1574 class DummyURLopener(urllib.request.URLopener): 1575 def open_local_file(self, url): 1576 return url 1577 for url in ('local_file://example', 'local-file://example'): 1578 self.assertRaises(OSError, urllib.request.urlopen, url) 1579 self.assertRaises(OSError, urllib.request.URLopener().open, url) 1580 self.assertRaises(OSError, urllib.request.URLopener().retrieve, url) 1581 self.assertRaises(OSError, DummyURLopener().open, url) 1582 self.assertRaises(OSError, DummyURLopener().retrieve, url) 1583 1584 1585# Just commented them out. 1586# Can't really tell why keep failing in windows and sparc. 1587# Everywhere else they work ok, but on those machines, sometimes 1588# fail in one of the tests, sometimes in other. I have a linux, and 1589# the tests go ok. 1590# If anybody has one of the problematic environments, please help! 1591# . Facundo 1592# 1593# def server(evt): 1594# import socket, time 1595# serv = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 1596# serv.settimeout(3) 1597# serv.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 1598# serv.bind(("", 9093)) 1599# serv.listen() 1600# try: 1601# conn, addr = serv.accept() 1602# conn.send("1 Hola mundo\n") 1603# cantdata = 0 1604# while cantdata < 13: 1605# data = conn.recv(13-cantdata) 1606# cantdata += len(data) 1607# time.sleep(.3) 1608# conn.send("2 No more lines\n") 1609# conn.close() 1610# except socket.timeout: 1611# pass 1612# finally: 1613# serv.close() 1614# evt.set() 1615# 1616# class FTPWrapperTests(unittest.TestCase): 1617# 1618# def setUp(self): 1619# import ftplib, time, threading 1620# ftplib.FTP.port = 9093 1621# self.evt = threading.Event() 1622# threading.Thread(target=server, args=(self.evt,)).start() 1623# time.sleep(.1) 1624# 1625# def tearDown(self): 1626# self.evt.wait() 1627# 1628# def testBasic(self): 1629# # connects 1630# ftp = urllib.ftpwrapper("myuser", "mypass", "localhost", 9093, []) 1631# ftp.close() 1632# 1633# def testTimeoutNone(self): 1634# # global default timeout is ignored 1635# import socket 1636# self.assertIsNone(socket.getdefaulttimeout()) 1637# socket.setdefaulttimeout(30) 1638# try: 1639# ftp = urllib.ftpwrapper("myuser", "mypass", "localhost", 9093, []) 1640# finally: 1641# socket.setdefaulttimeout(None) 1642# self.assertEqual(ftp.ftp.sock.gettimeout(), 30) 1643# ftp.close() 1644# 1645# def testTimeoutDefault(self): 1646# # global default timeout is used 1647# import socket 1648# self.assertIsNone(socket.getdefaulttimeout()) 1649# socket.setdefaulttimeout(30) 1650# try: 1651# ftp = urllib.ftpwrapper("myuser", "mypass", "localhost", 9093, []) 1652# finally: 1653# socket.setdefaulttimeout(None) 1654# self.assertEqual(ftp.ftp.sock.gettimeout(), 30) 1655# ftp.close() 1656# 1657# def testTimeoutValue(self): 1658# ftp = urllib.ftpwrapper("myuser", "mypass", "localhost", 9093, [], 1659# timeout=30) 1660# self.assertEqual(ftp.ftp.sock.gettimeout(), 30) 1661# ftp.close() 1662 1663 1664class RequestTests(unittest.TestCase): 1665 """Unit tests for urllib.request.Request.""" 1666 1667 def test_default_values(self): 1668 Request = urllib.request.Request 1669 request = Request("http://www.python.org") 1670 self.assertEqual(request.get_method(), 'GET') 1671 request = Request("http://www.python.org", {}) 1672 self.assertEqual(request.get_method(), 'POST') 1673 1674 def test_with_method_arg(self): 1675 Request = urllib.request.Request 1676 request = Request("http://www.python.org", method='HEAD') 1677 self.assertEqual(request.method, 'HEAD') 1678 self.assertEqual(request.get_method(), 'HEAD') 1679 request = Request("http://www.python.org", {}, method='HEAD') 1680 self.assertEqual(request.method, 'HEAD') 1681 self.assertEqual(request.get_method(), 'HEAD') 1682 request = Request("http://www.python.org", method='GET') 1683 self.assertEqual(request.get_method(), 'GET') 1684 request.method = 'HEAD' 1685 self.assertEqual(request.get_method(), 'HEAD') 1686 1687 1688class URL2PathNameTests(unittest.TestCase): 1689 1690 def test_converting_drive_letter(self): 1691 self.assertEqual(url2pathname("///C|"), 'C:') 1692 self.assertEqual(url2pathname("///C:"), 'C:') 1693 self.assertEqual(url2pathname("///C|/"), 'C:\\') 1694 1695 def test_converting_when_no_drive_letter(self): 1696 # cannot end a raw string in \ 1697 self.assertEqual(url2pathname("///C/test/"), r'\\\C\test' '\\') 1698 self.assertEqual(url2pathname("////C/test/"), r'\\C\test' '\\') 1699 1700 def test_simple_compare(self): 1701 self.assertEqual(url2pathname("///C|/foo/bar/spam.foo"), 1702 r'C:\foo\bar\spam.foo') 1703 1704 def test_non_ascii_drive_letter(self): 1705 self.assertRaises(IOError, url2pathname, "///\u00e8|/") 1706 1707 def test_roundtrip_url2pathname(self): 1708 list_of_paths = ['C:', 1709 r'\\\C\test\\', 1710 r'C:\foo\bar\spam.foo' 1711 ] 1712 for path in list_of_paths: 1713 self.assertEqual(url2pathname(pathname2url(path)), path) 1714 1715class PathName2URLTests(unittest.TestCase): 1716 1717 def test_converting_drive_letter(self): 1718 self.assertEqual(pathname2url("C:"), '///C:') 1719 self.assertEqual(pathname2url("C:\\"), '///C:') 1720 1721 def test_converting_when_no_drive_letter(self): 1722 self.assertEqual(pathname2url(r"\\\folder\test" "\\"), 1723 '/////folder/test/') 1724 self.assertEqual(pathname2url(r"\\folder\test" "\\"), 1725 '////folder/test/') 1726 self.assertEqual(pathname2url(r"\folder\test" "\\"), 1727 '/folder/test/') 1728 1729 def test_simple_compare(self): 1730 self.assertEqual(pathname2url(r'C:\foo\bar\spam.foo'), 1731 "///C:/foo/bar/spam.foo" ) 1732 1733 def test_long_drive_letter(self): 1734 self.assertRaises(IOError, pathname2url, "XX:\\") 1735 1736 def test_roundtrip_pathname2url(self): 1737 list_of_paths = ['///C:', 1738 '/////folder/test/', 1739 '///C:/foo/bar/spam.foo'] 1740 for path in list_of_paths: 1741 self.assertEqual(pathname2url(url2pathname(path)), path) 1742 1743if __name__ == '__main__': 1744 unittest.main() 1745