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"     # "&#28450;&#23383;"
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