Skip to content

Commit

Permalink
1.6.1dev: fix incorrect handling WSGI "bytes-as-unicode" string of th…
Browse files Browse the repository at this point in the history
…e REMOTE_USER variable (closes #13702)

git-svn-id: http://trac.edgewall.org/intertrac/log:/branches/1.6-stable@17789 af82e41b-90c4-0310-8c96-b1721e28e2e2
  • Loading branch information
jomae committed Apr 29, 2024
1 parent a557413 commit 7d21fb1
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 17 deletions.
1 change: 1 addition & 0 deletions trac/tests/functional/testcases.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ def runTest(self):
# are used and the req.perm has no permissions.
tc.notfind(internal_error)
tc.notfind("You don't have the required permissions")
tc.find('>logged in as <span class="trac-author-user">joé</span>')
self._tester.logout()
# finally restore expected 'admin' login
self._tester.login('admin')
Expand Down
44 changes: 33 additions & 11 deletions trac/web/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,33 @@ def arg_list_to_args(arg_list):
return args


if hasattr(str, 'isascii'):
_isascii = lambda value: value.isascii()
else:
_is_non_ascii_re = re.compile(r'[^\x00-\x7f]')
_isascii = lambda value: not _is_non_ascii_re.search(value)


def wsgi_string_decode(value):
"""Convert from a WSGI "bytes-as-unicode" string to an unicode string.
"""
if not isinstance(value, str):
raise TypeError('Must a str instance rather than %s' % type(value))
if not _isascii(value):
value = value.encode('iso-8859-1').decode('utf-8')
return value


def wsgi_string_encode(value):
"""Convert from an unicode string to a WSGI "bytes-as-unicode" string.
"""
if not isinstance(value, str):
raise TypeError('Must a str instance rather than %s' % type(value))
if not _isascii(value):
value = value.encode('utf-8').decode('iso-8859-1')
return value


def _raise_if_null_bytes(value):
if value and '\x00' in value:
raise HTTPBadRequest(_("Invalid request arguments."))
Expand Down Expand Up @@ -666,7 +693,7 @@ def __getattr__(self, name):

def __repr__(self):
uri = self.environ.get('PATH_INFO', '')
qs = self.query_string
qs = self.environ.get('QUERY_STRING', '')
if qs:
uri += '?' + qs
return '<%s "%s %r">' % (self.__class__.__name__, self.method, uri)
Expand Down Expand Up @@ -698,21 +725,16 @@ def method(self):
def path_info(self):
"""Path inside the application"""
path_info = self.environ.get('PATH_INFO', '')
if isinstance(path_info, str):
# According to PEP 3333, the value is decoded by iso-8859-1
# encoding when it is a unicode string. However, we need
# decoded unicode string by utf-8 encoding.
path_info = path_info.encode('iso-8859-1')
try:
return str(path_info, 'utf-8')
except UnicodeDecodeError:
return wsgi_string_decode(path_info)
except UnicodeError:
raise HTTPNotFound(_("Invalid URL encoding (was %(path_info)r)",
path_info=path_info))

@property
def query_string(self):
"""Query part of the request"""
return self.environ.get('QUERY_STRING', '')
return wsgi_string_decode(self.environ.get('QUERY_STRING', ''))

@property
def remote_addr(self):
Expand All @@ -727,7 +749,7 @@ def remote_user(self):
"""
user = self.environ.get('REMOTE_USER')
if user is not None:
return to_unicode(user)
return wsgi_string_decode(user)

@property
def request_path(self):
Expand All @@ -745,7 +767,7 @@ def scheme(self):
@property
def base_path(self):
"""The root path of the application"""
return self.environ.get('SCRIPT_NAME', '')
return wsgi_string_decode(self.environ.get('SCRIPT_NAME', ''))

@property
def server_name(self):
Expand Down
11 changes: 6 additions & 5 deletions trac/web/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@
HTTPInternalServerError, HTTPNotFound, IAuthenticator, \
IRequestFilter, IRequestHandler, Request, \
RequestDone, TracNotImplementedError, \
is_valid_default_handler, parse_header
is_valid_default_handler, parse_header, \
wsgi_string_decode, wsgi_string_encode
from trac.web.chrome import Chrome, ITemplateProvider, add_notice, \
add_stylesheet, add_warning
from trac.web.href import Href
Expand Down Expand Up @@ -535,14 +536,14 @@ def dispatch_request(environ, start_response):
# the remaining path in the `PATH_INFO` variable.
script_name = environ.get('SCRIPT_NAME', '')
try:
if isinstance(script_name, str):
script_name = script_name.encode('iso-8859-1') # PEP 3333
script_name = str(script_name, 'utf-8')
script_name = wsgi_string_decode(script_name)
env_name = wsgi_string_decode(env_name)
except UnicodeDecodeError:
errmsg = 'Invalid URL encoding (was %r)' % script_name
else:
# (as Href expects unicode parameters)
environ['SCRIPT_NAME'] = Href(script_name)(env_name)
script_name = wsgi_string_encode(Href(script_name)(env_name))
environ['SCRIPT_NAME'] = script_name
environ['PATH_INFO'] = '/' + '/'.join(path_info)

if env_parent_dir:
Expand Down
3 changes: 2 additions & 1 deletion trac/web/standalone.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
from trac import __version__ as VERSION
from trac.util import autoreload, daemon
from trac.util.text import printerr
from trac.web.api import wsgi_string_encode
from trac.web.auth import BasicAuthentication, DigestAuthentication
from trac.web.main import dispatch_request
from trac.web.wsgi import WSGIServer, WSGIRequestHandler
Expand Down Expand Up @@ -59,7 +60,7 @@ def __call__(self, environ, start_response):
remote_user = auth.do_auth(environ, start_response)
if not remote_user:
return []
environ['REMOTE_USER'] = remote_user
environ['REMOTE_USER'] = wsgi_string_encode(remote_user)
return self.application(environ, start_response)


Expand Down
42 changes: 42 additions & 0 deletions trac/web/tests/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,48 @@ def test_check_modified_if_none_match(self):
req.send(b'')
self.assertEqual(etag, req.headers_sent['ETag'])

def test_path_info(self):

def test(expected, value):
environ = _make_environ(PATH_INFO=value)
self.assertEqual(expected, _make_req(environ).path_info)

test('', '')
test('/wiki/WikiStart', '/wiki/WikiStart')
test('/wiki/TæstPäge', '/wiki/T\xc3\xa6stP\xc3\xa4ge')

def test_query_string(self):

def test(expected, value):
environ = _make_environ(QUERY_STRING=value)
self.assertEqual(expected, _make_req(environ).query_string)

test('', '')
test('status=defect&milestone=milestone1',
'status=defect&milestone=milestone1')
test('status=defećt&milestóne=milestone1',
'status=defe\xc4\x87t&milest\xc3\xb3ne=milestone1')

def test_base_path(self):

def test(expected, value):
environ = _make_environ(SCRIPT_NAME=value)
self.assertEqual(expected, _make_req(environ).base_path)

test('', '')
test('/1.6-stable', '/1.6-stable')
test('/Prøjeçt-42', '/Pr\xc3\xb8je\xc3\xa7t-42')

def test_remote_user(self):

def test(expected, value):
environ = _make_environ(REMOTE_USER=value)
self.assertEqual(expected, _make_req(environ).remote_user)

test('', '')
test('joe', 'joe')
test('jöhn', 'j\xc3\xb6hn')


class RequestSendFileTestCase(unittest.TestCase):

Expand Down

0 comments on commit 7d21fb1

Please sign in to comment.