Skip to content

Commit 7d21fb1

Browse files
author
jomae
committed
1.6.1dev: fix incorrect handling WSGI "bytes-as-unicode" string of the REMOTE_USER variable (closes #13702)
git-svn-id: http://trac.edgewall.org/intertrac/log:/branches/1.6-stable@17789 af82e41b-90c4-0310-8c96-b1721e28e2e2
1 parent a557413 commit 7d21fb1

File tree

5 files changed

+84
-17
lines changed

5 files changed

+84
-17
lines changed

trac/tests/functional/testcases.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ def runTest(self):
304304
# are used and the req.perm has no permissions.
305305
tc.notfind(internal_error)
306306
tc.notfind("You don't have the required permissions")
307+
tc.find('>logged in as <span class="trac-author-user">joé</span>')
307308
self._tester.logout()
308309
# finally restore expected 'admin' login
309310
self._tester.login('admin')

trac/web/api.py

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,33 @@ def arg_list_to_args(arg_list):
470470
return args
471471

472472

473+
if hasattr(str, 'isascii'):
474+
_isascii = lambda value: value.isascii()
475+
else:
476+
_is_non_ascii_re = re.compile(r'[^\x00-\x7f]')
477+
_isascii = lambda value: not _is_non_ascii_re.search(value)
478+
479+
480+
def wsgi_string_decode(value):
481+
"""Convert from a WSGI "bytes-as-unicode" string to an unicode string.
482+
"""
483+
if not isinstance(value, str):
484+
raise TypeError('Must a str instance rather than %s' % type(value))
485+
if not _isascii(value):
486+
value = value.encode('iso-8859-1').decode('utf-8')
487+
return value
488+
489+
490+
def wsgi_string_encode(value):
491+
"""Convert from an unicode string to a WSGI "bytes-as-unicode" string.
492+
"""
493+
if not isinstance(value, str):
494+
raise TypeError('Must a str instance rather than %s' % type(value))
495+
if not _isascii(value):
496+
value = value.encode('utf-8').decode('iso-8859-1')
497+
return value
498+
499+
473500
def _raise_if_null_bytes(value):
474501
if value and '\x00' in value:
475502
raise HTTPBadRequest(_("Invalid request arguments."))
@@ -666,7 +693,7 @@ def __getattr__(self, name):
666693

667694
def __repr__(self):
668695
uri = self.environ.get('PATH_INFO', '')
669-
qs = self.query_string
696+
qs = self.environ.get('QUERY_STRING', '')
670697
if qs:
671698
uri += '?' + qs
672699
return '<%s "%s %r">' % (self.__class__.__name__, self.method, uri)
@@ -698,21 +725,16 @@ def method(self):
698725
def path_info(self):
699726
"""Path inside the application"""
700727
path_info = self.environ.get('PATH_INFO', '')
701-
if isinstance(path_info, str):
702-
# According to PEP 3333, the value is decoded by iso-8859-1
703-
# encoding when it is a unicode string. However, we need
704-
# decoded unicode string by utf-8 encoding.
705-
path_info = path_info.encode('iso-8859-1')
706728
try:
707-
return str(path_info, 'utf-8')
708-
except UnicodeDecodeError:
729+
return wsgi_string_decode(path_info)
730+
except UnicodeError:
709731
raise HTTPNotFound(_("Invalid URL encoding (was %(path_info)r)",
710732
path_info=path_info))
711733

712734
@property
713735
def query_string(self):
714736
"""Query part of the request"""
715-
return self.environ.get('QUERY_STRING', '')
737+
return wsgi_string_decode(self.environ.get('QUERY_STRING', ''))
716738

717739
@property
718740
def remote_addr(self):
@@ -727,7 +749,7 @@ def remote_user(self):
727749
"""
728750
user = self.environ.get('REMOTE_USER')
729751
if user is not None:
730-
return to_unicode(user)
752+
return wsgi_string_decode(user)
731753

732754
@property
733755
def request_path(self):
@@ -745,7 +767,7 @@ def scheme(self):
745767
@property
746768
def base_path(self):
747769
"""The root path of the application"""
748-
return self.environ.get('SCRIPT_NAME', '')
770+
return wsgi_string_decode(self.environ.get('SCRIPT_NAME', ''))
749771

750772
@property
751773
def server_name(self):

trac/web/main.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@
5353
HTTPInternalServerError, HTTPNotFound, IAuthenticator, \
5454
IRequestFilter, IRequestHandler, Request, \
5555
RequestDone, TracNotImplementedError, \
56-
is_valid_default_handler, parse_header
56+
is_valid_default_handler, parse_header, \
57+
wsgi_string_decode, wsgi_string_encode
5758
from trac.web.chrome import Chrome, ITemplateProvider, add_notice, \
5859
add_stylesheet, add_warning
5960
from trac.web.href import Href
@@ -535,14 +536,14 @@ def dispatch_request(environ, start_response):
535536
# the remaining path in the `PATH_INFO` variable.
536537
script_name = environ.get('SCRIPT_NAME', '')
537538
try:
538-
if isinstance(script_name, str):
539-
script_name = script_name.encode('iso-8859-1') # PEP 3333
540-
script_name = str(script_name, 'utf-8')
539+
script_name = wsgi_string_decode(script_name)
540+
env_name = wsgi_string_decode(env_name)
541541
except UnicodeDecodeError:
542542
errmsg = 'Invalid URL encoding (was %r)' % script_name
543543
else:
544544
# (as Href expects unicode parameters)
545-
environ['SCRIPT_NAME'] = Href(script_name)(env_name)
545+
script_name = wsgi_string_encode(Href(script_name)(env_name))
546+
environ['SCRIPT_NAME'] = script_name
546547
environ['PATH_INFO'] = '/' + '/'.join(path_info)
547548

548549
if env_parent_dir:

trac/web/standalone.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
from trac import __version__ as VERSION
3333
from trac.util import autoreload, daemon
3434
from trac.util.text import printerr
35+
from trac.web.api import wsgi_string_encode
3536
from trac.web.auth import BasicAuthentication, DigestAuthentication
3637
from trac.web.main import dispatch_request
3738
from trac.web.wsgi import WSGIServer, WSGIRequestHandler
@@ -59,7 +60,7 @@ def __call__(self, environ, start_response):
5960
remote_user = auth.do_auth(environ, start_response)
6061
if not remote_user:
6162
return []
62-
environ['REMOTE_USER'] = remote_user
63+
environ['REMOTE_USER'] = wsgi_string_encode(remote_user)
6364
return self.application(environ, start_response)
6465

6566

trac/web/tests/api.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -702,6 +702,48 @@ def test_check_modified_if_none_match(self):
702702
req.send(b'')
703703
self.assertEqual(etag, req.headers_sent['ETag'])
704704

705+
def test_path_info(self):
706+
707+
def test(expected, value):
708+
environ = _make_environ(PATH_INFO=value)
709+
self.assertEqual(expected, _make_req(environ).path_info)
710+
711+
test('', '')
712+
test('/wiki/WikiStart', '/wiki/WikiStart')
713+
test('/wiki/TæstPäge', '/wiki/T\xc3\xa6stP\xc3\xa4ge')
714+
715+
def test_query_string(self):
716+
717+
def test(expected, value):
718+
environ = _make_environ(QUERY_STRING=value)
719+
self.assertEqual(expected, _make_req(environ).query_string)
720+
721+
test('', '')
722+
test('status=defect&milestone=milestone1',
723+
'status=defect&milestone=milestone1')
724+
test('status=defećt&milestóne=milestone1',
725+
'status=defe\xc4\x87t&milest\xc3\xb3ne=milestone1')
726+
727+
def test_base_path(self):
728+
729+
def test(expected, value):
730+
environ = _make_environ(SCRIPT_NAME=value)
731+
self.assertEqual(expected, _make_req(environ).base_path)
732+
733+
test('', '')
734+
test('/1.6-stable', '/1.6-stable')
735+
test('/Prøjeçt-42', '/Pr\xc3\xb8je\xc3\xa7t-42')
736+
737+
def test_remote_user(self):
738+
739+
def test(expected, value):
740+
environ = _make_environ(REMOTE_USER=value)
741+
self.assertEqual(expected, _make_req(environ).remote_user)
742+
743+
test('', '')
744+
test('joe', 'joe')
745+
test('jöhn', 'j\xc3\xb6hn')
746+
705747

706748
class RequestSendFileTestCase(unittest.TestCase):
707749

0 commit comments

Comments
 (0)