mirror of
				https://github.com/python/cpython.git
				synced 2025-10-31 13:41:24 +00:00 
			
		
		
		
	Issue #6815: os.path.expandvars() now supports non-ASCII environment
variables names and values.
This commit is contained in:
		
							parent
							
								
									61e2493b83
								
							
						
					
					
						commit
						dbb101909d
					
				
					 5 changed files with 96 additions and 42 deletions
				
			
		|  | @ -362,6 +362,7 @@ def expandvars(path): | ||||||
|         percent = b'%' |         percent = b'%' | ||||||
|         brace = b'{' |         brace = b'{' | ||||||
|         dollar = b'$' |         dollar = b'$' | ||||||
|  |         environ = getattr(os, 'environb', None) | ||||||
|     else: |     else: | ||||||
|         if '$' not in path and '%' not in path: |         if '$' not in path and '%' not in path: | ||||||
|             return path |             return path | ||||||
|  | @ -371,6 +372,7 @@ def expandvars(path): | ||||||
|         percent = '%' |         percent = '%' | ||||||
|         brace = '{' |         brace = '{' | ||||||
|         dollar = '$' |         dollar = '$' | ||||||
|  |         environ = os.environ | ||||||
|     res = path[:0] |     res = path[:0] | ||||||
|     index = 0 |     index = 0 | ||||||
|     pathlen = len(path) |     pathlen = len(path) | ||||||
|  | @ -399,14 +401,13 @@ def expandvars(path): | ||||||
|                     index = pathlen - 1 |                     index = pathlen - 1 | ||||||
|                 else: |                 else: | ||||||
|                     var = path[:index] |                     var = path[:index] | ||||||
|                     if isinstance(path, bytes): |                     try: | ||||||
|                         var = var.decode('ascii') |                         if environ is None: | ||||||
|                     if var in os.environ: |                             value = os.fsencode(os.environ[os.fsdecode(var)]) | ||||||
|                         value = os.environ[var] |                         else: | ||||||
|                     else: |                             value = environ[var] | ||||||
|                         value = '%' + var + '%' |                     except KeyError: | ||||||
|                     if isinstance(path, bytes): |                         value = percent + var + percent | ||||||
|                         value = value.encode('ascii') |  | ||||||
|                     res += value |                     res += value | ||||||
|         elif c == dollar:  # variable or '$$' |         elif c == dollar:  # variable or '$$' | ||||||
|             if path[index + 1:index + 2] == dollar: |             if path[index + 1:index + 2] == dollar: | ||||||
|  | @ -420,39 +421,40 @@ def expandvars(path): | ||||||
|                         index = path.index(b'}') |                         index = path.index(b'}') | ||||||
|                     else: |                     else: | ||||||
|                         index = path.index('}') |                         index = path.index('}') | ||||||
|                     var = path[:index] |  | ||||||
|                     if isinstance(path, bytes): |  | ||||||
|                         var = var.decode('ascii') |  | ||||||
|                     if var in os.environ: |  | ||||||
|                         value = os.environ[var] |  | ||||||
|                     else: |  | ||||||
|                         value = '${' + var + '}' |  | ||||||
|                     if isinstance(path, bytes): |  | ||||||
|                         value = value.encode('ascii') |  | ||||||
|                     res += value |  | ||||||
|                 except ValueError: |                 except ValueError: | ||||||
|                     if isinstance(path, bytes): |                     if isinstance(path, bytes): | ||||||
|                         res += b'${' + path |                         res += b'${' + path | ||||||
|                     else: |                     else: | ||||||
|                         res += '${' + path |                         res += '${' + path | ||||||
|                     index = pathlen - 1 |                     index = pathlen - 1 | ||||||
|  |                 else: | ||||||
|  |                     var = path[:index] | ||||||
|  |                     try: | ||||||
|  |                         if environ is None: | ||||||
|  |                             value = os.fsencode(os.environ[os.fsdecode(var)]) | ||||||
|  |                         else: | ||||||
|  |                             value = environ[var] | ||||||
|  |                     except KeyError: | ||||||
|  |                         if isinstance(path, bytes): | ||||||
|  |                             value = b'${' + var + b'}' | ||||||
|  |                         else: | ||||||
|  |                             value = '${' + var + '}' | ||||||
|  |                     res += value | ||||||
|             else: |             else: | ||||||
|                 var = '' |                 var = path[:0] | ||||||
|                 index += 1 |                 index += 1 | ||||||
|                 c = path[index:index + 1] |                 c = path[index:index + 1] | ||||||
|                 while c and c in varchars: |                 while c and c in varchars: | ||||||
|                     if isinstance(path, bytes): |                     var += c | ||||||
|                         var += c.decode('ascii') |  | ||||||
|                     else: |  | ||||||
|                         var += c |  | ||||||
|                     index += 1 |                     index += 1 | ||||||
|                     c = path[index:index + 1] |                     c = path[index:index + 1] | ||||||
|                 if var in os.environ: |                 try: | ||||||
|                     value = os.environ[var] |                     if environ is None: | ||||||
|                 else: |                         value = os.fsencode(os.environ[os.fsdecode(var)]) | ||||||
|                     value = '$' + var |                     else: | ||||||
|                 if isinstance(path, bytes): |                         value = environ[var] | ||||||
|                     value = value.encode('ascii') |                 except KeyError: | ||||||
|  |                     value = dollar + var | ||||||
|                 res += value |                 res += value | ||||||
|                 if c: |                 if c: | ||||||
|                     index -= 1 |                     index -= 1 | ||||||
|  |  | ||||||
|  | @ -300,6 +300,7 @@ def expandvars(path): | ||||||
|         search = _varprogb.search |         search = _varprogb.search | ||||||
|         start = b'{' |         start = b'{' | ||||||
|         end = b'}' |         end = b'}' | ||||||
|  |         environ = getattr(os, 'environb', None) | ||||||
|     else: |     else: | ||||||
|         if '$' not in path: |         if '$' not in path: | ||||||
|             return path |             return path | ||||||
|  | @ -309,6 +310,7 @@ def expandvars(path): | ||||||
|         search = _varprog.search |         search = _varprog.search | ||||||
|         start = '{' |         start = '{' | ||||||
|         end = '}' |         end = '}' | ||||||
|  |         environ = os.environ | ||||||
|     i = 0 |     i = 0 | ||||||
|     while True: |     while True: | ||||||
|         m = search(path, i) |         m = search(path, i) | ||||||
|  | @ -318,18 +320,18 @@ def expandvars(path): | ||||||
|         name = m.group(1) |         name = m.group(1) | ||||||
|         if name.startswith(start) and name.endswith(end): |         if name.startswith(start) and name.endswith(end): | ||||||
|             name = name[1:-1] |             name = name[1:-1] | ||||||
|         if isinstance(name, bytes): |         try: | ||||||
|             name = str(name, 'ASCII') |             if environ is None: | ||||||
|         if name in os.environ: |                 value = os.fsencode(os.environ[os.fsdecode(var)]) | ||||||
|  |             else: | ||||||
|  |                 value = environ[name] | ||||||
|  |         except KeyError: | ||||||
|  |             i = j | ||||||
|  |         else: | ||||||
|             tail = path[j:] |             tail = path[j:] | ||||||
|             value = os.environ[name] |  | ||||||
|             if isinstance(path, bytes): |  | ||||||
|                 value = value.encode('ASCII') |  | ||||||
|             path = path[:i] + value |             path = path[:i] + value | ||||||
|             i = len(path) |             i = len(path) | ||||||
|             path += tail |             path += tail | ||||||
|         else: |  | ||||||
|             i = j |  | ||||||
|     return path |     return path | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -248,7 +248,6 @@ def test_expandvars(self): | ||||||
|             self.assertEqual(expandvars("$[foo]bar"), "$[foo]bar") |             self.assertEqual(expandvars("$[foo]bar"), "$[foo]bar") | ||||||
|             self.assertEqual(expandvars("$bar bar"), "$bar bar") |             self.assertEqual(expandvars("$bar bar"), "$bar bar") | ||||||
|             self.assertEqual(expandvars("$?bar"), "$?bar") |             self.assertEqual(expandvars("$?bar"), "$?bar") | ||||||
|             self.assertEqual(expandvars("${foo}bar"), "barbar") |  | ||||||
|             self.assertEqual(expandvars("$foo}bar"), "bar}bar") |             self.assertEqual(expandvars("$foo}bar"), "bar}bar") | ||||||
|             self.assertEqual(expandvars("${foo"), "${foo") |             self.assertEqual(expandvars("${foo"), "${foo") | ||||||
|             self.assertEqual(expandvars("${{foo}}"), "baz1}") |             self.assertEqual(expandvars("${{foo}}"), "baz1}") | ||||||
|  | @ -261,13 +260,40 @@ def test_expandvars(self): | ||||||
|             self.assertEqual(expandvars(b"$[foo]bar"), b"$[foo]bar") |             self.assertEqual(expandvars(b"$[foo]bar"), b"$[foo]bar") | ||||||
|             self.assertEqual(expandvars(b"$bar bar"), b"$bar bar") |             self.assertEqual(expandvars(b"$bar bar"), b"$bar bar") | ||||||
|             self.assertEqual(expandvars(b"$?bar"), b"$?bar") |             self.assertEqual(expandvars(b"$?bar"), b"$?bar") | ||||||
|             self.assertEqual(expandvars(b"${foo}bar"), b"barbar") |  | ||||||
|             self.assertEqual(expandvars(b"$foo}bar"), b"bar}bar") |             self.assertEqual(expandvars(b"$foo}bar"), b"bar}bar") | ||||||
|             self.assertEqual(expandvars(b"${foo"), b"${foo") |             self.assertEqual(expandvars(b"${foo"), b"${foo") | ||||||
|             self.assertEqual(expandvars(b"${{foo}}"), b"baz1}") |             self.assertEqual(expandvars(b"${{foo}}"), b"baz1}") | ||||||
|             self.assertEqual(expandvars(b"$foo$foo"), b"barbar") |             self.assertEqual(expandvars(b"$foo$foo"), b"barbar") | ||||||
|             self.assertEqual(expandvars(b"$bar$bar"), b"$bar$bar") |             self.assertEqual(expandvars(b"$bar$bar"), b"$bar$bar") | ||||||
| 
 | 
 | ||||||
|  |     @unittest.skipUnless(support.FS_NONASCII, 'need support.FS_NONASCII') | ||||||
|  |     def test_expandvars_nonascii(self): | ||||||
|  |         if self.pathmodule.__name__ == 'macpath': | ||||||
|  |             self.skipTest('macpath.expandvars is a stub') | ||||||
|  |         expandvars = self.pathmodule.expandvars | ||||||
|  |         def check(value, expected): | ||||||
|  |             self.assertEqual(expandvars(value), expected) | ||||||
|  |         with support.EnvironmentVarGuard() as env: | ||||||
|  |             env.clear() | ||||||
|  |             nonascii = support.FS_NONASCII | ||||||
|  |             env['spam'] = nonascii | ||||||
|  |             env[nonascii] = 'ham' + nonascii | ||||||
|  |             check(nonascii, nonascii) | ||||||
|  |             check('$spam bar', '%s bar' % nonascii) | ||||||
|  |             check('${spam}bar', '%sbar' % nonascii) | ||||||
|  |             check('${%s}bar' % nonascii, 'ham%sbar' % nonascii) | ||||||
|  |             check('$bar%s bar' % nonascii, '$bar%s bar' % nonascii) | ||||||
|  |             check('$spam}bar', '%s}bar' % nonascii) | ||||||
|  | 
 | ||||||
|  |             check(os.fsencode(nonascii), os.fsencode(nonascii)) | ||||||
|  |             check(b'$spam bar', os.fsencode('%s bar' % nonascii)) | ||||||
|  |             check(b'${spam}bar', os.fsencode('%sbar' % nonascii)) | ||||||
|  |             check(os.fsencode('${%s}bar' % nonascii), | ||||||
|  |                   os.fsencode('ham%sbar' % nonascii)) | ||||||
|  |             check(os.fsencode('$bar%s bar' % nonascii), | ||||||
|  |                   os.fsencode('$bar%s bar' % nonascii)) | ||||||
|  |             check(b'$spam}bar', os.fsencode('%s}bar' % nonascii)) | ||||||
|  | 
 | ||||||
|     def test_abspath(self): |     def test_abspath(self): | ||||||
|         self.assertIn("foo", self.pathmodule.abspath("foo")) |         self.assertIn("foo", self.pathmodule.abspath("foo")) | ||||||
|         with warnings.catch_warnings(): |         with warnings.catch_warnings(): | ||||||
|  |  | ||||||
|  | @ -22,13 +22,15 @@ def tester(fn, wantResult): | ||||||
|     fn = fn.replace('["', '[b"') |     fn = fn.replace('["', '[b"') | ||||||
|     fn = fn.replace(", '", ", b'") |     fn = fn.replace(", '", ", b'") | ||||||
|     fn = fn.replace(', "', ', b"') |     fn = fn.replace(', "', ', b"') | ||||||
|  |     fn = os.fsencode(fn).decode('latin1') | ||||||
|  |     fn = fn.encode('ascii', 'backslashreplace').decode('ascii') | ||||||
|     with warnings.catch_warnings(): |     with warnings.catch_warnings(): | ||||||
|         warnings.simplefilter("ignore", DeprecationWarning) |         warnings.simplefilter("ignore", DeprecationWarning) | ||||||
|         gotResult = eval(fn) |         gotResult = eval(fn) | ||||||
|     if isinstance(wantResult, str): |     if isinstance(wantResult, str): | ||||||
|         wantResult = wantResult.encode('ascii') |         wantResult = os.fsencode(wantResult) | ||||||
|     elif isinstance(wantResult, tuple): |     elif isinstance(wantResult, tuple): | ||||||
|         wantResult = tuple(r.encode('ascii') for r in wantResult) |         wantResult = tuple(os.fsencode(r) for r in wantResult) | ||||||
| 
 | 
 | ||||||
|     gotResult = eval(fn) |     gotResult = eval(fn) | ||||||
|     if wantResult != gotResult: |     if wantResult != gotResult: | ||||||
|  | @ -223,7 +225,6 @@ def test_expandvars(self): | ||||||
|             tester('ntpath.expandvars("$[foo]bar")', "$[foo]bar") |             tester('ntpath.expandvars("$[foo]bar")', "$[foo]bar") | ||||||
|             tester('ntpath.expandvars("$bar bar")', "$bar bar") |             tester('ntpath.expandvars("$bar bar")', "$bar bar") | ||||||
|             tester('ntpath.expandvars("$?bar")', "$?bar") |             tester('ntpath.expandvars("$?bar")', "$?bar") | ||||||
|             tester('ntpath.expandvars("${foo}bar")', "barbar") |  | ||||||
|             tester('ntpath.expandvars("$foo}bar")', "bar}bar") |             tester('ntpath.expandvars("$foo}bar")', "bar}bar") | ||||||
|             tester('ntpath.expandvars("${foo")', "${foo") |             tester('ntpath.expandvars("${foo")', "${foo") | ||||||
|             tester('ntpath.expandvars("${{foo}}")', "baz1}") |             tester('ntpath.expandvars("${{foo}}")', "baz1}") | ||||||
|  | @ -237,6 +238,26 @@ def test_expandvars(self): | ||||||
|             tester('ntpath.expandvars("%foo%%bar")', "bar%bar") |             tester('ntpath.expandvars("%foo%%bar")', "bar%bar") | ||||||
|             tester('ntpath.expandvars("\'%foo%\'%bar")', "\'%foo%\'%bar") |             tester('ntpath.expandvars("\'%foo%\'%bar")', "\'%foo%\'%bar") | ||||||
| 
 | 
 | ||||||
|  |     @unittest.skipUnless(support.FS_NONASCII, 'need support.FS_NONASCII') | ||||||
|  |     def test_expandvars_nonascii(self): | ||||||
|  |         def check(value, expected): | ||||||
|  |             tester('ntpath.expandvars(%r)' % value, expected) | ||||||
|  |         with support.EnvironmentVarGuard() as env: | ||||||
|  |             env.clear() | ||||||
|  |             nonascii = support.FS_NONASCII | ||||||
|  |             env['spam'] = nonascii | ||||||
|  |             env[nonascii] = 'ham' + nonascii | ||||||
|  |             check('$spam bar', '%s bar' % nonascii) | ||||||
|  |             check('$%s bar' % nonascii, '$%s bar' % nonascii) | ||||||
|  |             check('${spam}bar', '%sbar' % nonascii) | ||||||
|  |             check('${%s}bar' % nonascii, 'ham%sbar' % nonascii) | ||||||
|  |             check('$spam}bar', '%s}bar' % nonascii) | ||||||
|  |             check('$%s}bar' % nonascii, '$%s}bar' % nonascii) | ||||||
|  |             check('%spam% bar', '%s bar' % nonascii) | ||||||
|  |             check('%{}% bar'.format(nonascii), 'ham%s bar' % nonascii) | ||||||
|  |             check('%spam%bar', '%sbar' % nonascii) | ||||||
|  |             check('%{}%bar'.format(nonascii), 'ham%sbar' % nonascii) | ||||||
|  | 
 | ||||||
|     def test_abspath(self): |     def test_abspath(self): | ||||||
|         # ntpath.abspath() can only be used on a system with the "nt" module |         # ntpath.abspath() can only be used on a system with the "nt" module | ||||||
|         # (reasonably), so we protect this test with "import nt".  This allows |         # (reasonably), so we protect this test with "import nt".  This allows | ||||||
|  |  | ||||||
|  | @ -20,6 +20,9 @@ Core and Builtins | ||||||
| Library | Library | ||||||
| ------- | ------- | ||||||
| 
 | 
 | ||||||
|  | - Issue #6815: os.path.expandvars() now supports non-ASCII environment | ||||||
|  |   variables names and values. | ||||||
|  | 
 | ||||||
| - Issue #17671: Fixed a crash when use non-initialized io.BufferedRWPair. | - Issue #17671: Fixed a crash when use non-initialized io.BufferedRWPair. | ||||||
|   Based on patch by Stephen Tu. |   Based on patch by Stephen Tu. | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Serhiy Storchaka
						Serhiy Storchaka