You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by ke...@apache.org on 2020/06/03 16:05:30 UTC

[allura] 08/12: [#8361] referer fixes (comes in as bytes, we want to use it as a string)

This is an automated email from the ASF dual-hosted git repository.

kentontaylor pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/allura.git

commit e659af303306f2dcc80491e5cd57ee75f0e13561
Author: Dave Brondsema <da...@brondsema.net>
AuthorDate: Mon May 18 12:47:07 2020 -0400

    [#8361] referer fixes (comes in as bytes, we want to use it as a string)
---
 Allura/allura/app.py                               |  6 +++---
 Allura/allura/controllers/attachments.py           |  4 +++-
 Allura/allura/controllers/auth.py                  | 16 +++++++++-------
 Allura/allura/controllers/discuss.py               | 22 +++++++++++-----------
 Allura/allura/controllers/project.py               |  8 ++++----
 Allura/allura/controllers/repository.py            |  4 ++--
 Allura/allura/controllers/site_admin.py            | 12 ++++++------
 Allura/allura/ext/admin/admin_main.py              |  5 +++--
 Allura/allura/ext/user_profile/user_main.py        |  9 +++++----
 Allura/allura/lib/repository.py                    |  8 +++++---
 Allura/allura/lib/utils.py                         |  2 +-
 Allura/allura/tests/functional/test_discuss.py     |  2 +-
 Allura/allura/tests/unit/patches.py                |  7 +++++--
 ForgeBlog/forgeblog/main.py                        |  4 ++--
 ForgeChat/forgechat/main.py                        |  3 ++-
 .../forgediscussion/controllers/root.py            |  6 ++++--
 ForgeDiscussion/forgediscussion/forum_main.py      |  2 +-
 ForgeSVN/forgesvn/svn_main.py                      |  4 +++-
 ForgeShortUrl/forgeshorturl/main.py                | 10 ++++++----
 ForgeTracker/forgetracker/tracker_main.py          | 12 ++++++------
 ForgeWiki/forgewiki/wiki_main.py                   |  8 +++++---
 21 files changed, 87 insertions(+), 67 deletions(-)

diff --git a/Allura/allura/app.py b/Allura/allura/app.py
index a631fb7..ef751ef 100644
--- a/Allura/allura/app.py
+++ b/Allura/allura/app.py
@@ -918,7 +918,7 @@ class DefaultAdminController(BaseController, AdminControllerMixin):
         require_access(self.app, 'configure')
         self.app.config.options['mount_label'] = mount_label
         g.post_event('project_menu_updated')
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @expose('jinja:allura:templates/app_admin_options.html')
     def options(self):
@@ -973,7 +973,7 @@ class DefaultAdminController(BaseController, AdminControllerMixin):
                          + self.app.config.options.mount_point
                          + '/')
             else:
-                redirect(request.referer or '/')
+                redirect(six.ensure_text(request.referer or '/'))
 
     @without_trailing_slash
     @expose()
@@ -1026,7 +1026,7 @@ class DefaultAdminController(BaseController, AdminControllerMixin):
                 if (ace.permission == perm) and (ace.access == model.ACE.DENY):
                     self.app.config.acl.append(ace)
         g.post_event('project_menu_updated')  # since 'read' permission changes can affect what is visible in menu
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
 
 class WebhooksLookup(BaseController, AdminControllerMixin):
diff --git a/Allura/allura/controllers/attachments.py b/Allura/allura/controllers/attachments.py
index 427a329..f93c684 100644
--- a/Allura/allura/controllers/attachments.py
+++ b/Allura/allura/controllers/attachments.py
@@ -17,6 +17,8 @@
 
 from __future__ import unicode_literals
 from __future__ import absolute_import
+
+import six
 from six.moves.urllib.parse import unquote
 from webob import exc
 
@@ -117,7 +119,7 @@ class AttachmentController(BaseController):
             self.handle_post(delete, **kw)
             if is_ajax(request):
                 return
-            redirect(request.referer or '/')
+            redirect(six.ensure_text(request.referer or '/'))
         if self.artifact.deleted:
             raise exc.HTTPNotFound
         embed = False
diff --git a/Allura/allura/controllers/auth.py b/Allura/allura/controllers/auth.py
index 5c457b6..f70cfb7 100644
--- a/Allura/allura/controllers/auth.py
+++ b/Allura/allura/controllers/auth.py
@@ -137,10 +137,12 @@ class AuthController(BaseController):
         elif orig_request:
             return_to = orig_request.url
         else:
-            if request.referer is not None and request.referer.split('/')[-1] == 'neighborhood':
+            if request.referer is not None and six.ensure_text(request.referer).split('/')[-1] == 'neighborhood':
                 return_to = '/'
+            elif request.referer:
+                return_to = six.ensure_text(request.referer)
             else:
-                return_to = request.referer
+                return_to = None
         c.form = F.login_form
         return dict(return_to=return_to)
 
@@ -282,7 +284,7 @@ class AuthController(BaseController):
             flash('Verification link sent')
         else:
             flash('No such address', 'error')
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     def _verify_addr(self, addr, do_auth_check=True):
         confirmed_by_other = M.EmailAddress.find(dict(email=addr.email, confirmed=True)).all() if addr else []
@@ -386,7 +388,7 @@ class AuthController(BaseController):
 
         if 'multifactor-username' not in session:
             tg.flash('Your multifactor login was disrupted, please start over.', 'error')
-            redirect('/auth/', return_to=kwargs.get('return_to', ''))
+            redirect('/auth/', {'return_to': kwargs.get('return_to', '')})
 
         user = M.User.by_username(session['multifactor-username'])
         try:
@@ -729,7 +731,7 @@ class PreferencesController(BaseController):
     @require_post()
     def user_message(self, allow_user_messages=False):
         c.user.set_pref('disable_user_messages', not allow_user_messages)
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @expose('jinja:allura:templates/user_totp.html')
     @without_trailing_slash
@@ -1252,7 +1254,7 @@ class SubscriptionsController(BaseController):
         if email_format:
             c.user.set_pref('email_format', email_format)
 
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @expose()
     @require_post()
@@ -1263,7 +1265,7 @@ class SubscriptionsController(BaseController):
             h.auditlog_user('User mention notifications are enabled')
         else:
             h.auditlog_user('User mention notifications are disabled')
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
 
 class OAuthController(BaseController):
diff --git a/Allura/allura/controllers/discuss.py b/Allura/allura/controllers/discuss.py
index d6fa61e..c6f18e7 100644
--- a/Allura/allura/controllers/discuss.py
+++ b/Allura/allura/controllers/discuss.py
@@ -100,7 +100,7 @@ class DiscussionController(BaseController, FeedController):
             self.moderate = ModerationController(self)
 
     def error_handler(self, *args, **kwargs):
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @h.vardec
     @expose()
@@ -115,7 +115,7 @@ class DiscussionController(BaseController, FeedController):
                 thread.unsubscribe()
             session(self.M.Thread)._get().skip_mod_date = True
             session(self.M.Thread)._get().skip_last_updated = True
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     def get_feed(self, project, app, user):
         """Return a :class:`allura.controllers.feed.FeedArgs` object describing
@@ -215,7 +215,7 @@ class ThreadController(six.with_metaclass(h.ProxiedAttrMeta, BaseController, Fee
                     show_moderate=kw.get('show_moderate'))
 
     def error_handler(self, *args, **kwargs):
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @memorable_forget()
     @h.vardec
@@ -236,7 +236,7 @@ class ThreadController(six.with_metaclass(h.ProxiedAttrMeta, BaseController, Fee
         if self.thread.ref:
             require_access(self.thread.ref.artifact, 'post')
         self.thread.labels = labels.split(',')
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @expose()
     def flag_as_spam(self, **kw):
@@ -260,14 +260,14 @@ class ThreadController(six.with_metaclass(h.ProxiedAttrMeta, BaseController, Fee
 
 def handle_post_or_reply(thread, edit_widget, rate_limit, kw, parent_post_id=None):
     require_access(thread, 'post')
-    rate_limit(M.Post, "Comment", redir=request.referrer)
+    rate_limit(M.Post, "Comment", redir=six.ensure_text(request.referer or '/'))
     if thread.ref:
         require_access(thread.ref.artifact, 'post')
     kw = edit_widget.to_python(kw, None)  # could raise Invalid, but doesn't seem like it ever does
     if not kw['text']:
         flash('Your post was not saved. You must provide content.',
               'error')
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
     file_info = kw.get('file_info', None)
     p = thread.add_post(parent_id=parent_post_id, **kw)
     p.add_multiple_attachments(file_info)
@@ -275,7 +275,7 @@ def handle_post_or_reply(thread, edit_widget, rate_limit, kw, parent_post_id=Non
         thread.artifact.mod_date = datetime.utcnow()
     flash('Message posted')
     notification_tasks.send_usermentions_notification.post(p.index_id(), kw['text'])
-    redirect(request.referer or '/')
+    redirect(six.ensure_text(request.referer or '/'))
 
 
 class PostController(six.with_metaclass(h.ProxiedAttrMeta, BaseController)):
@@ -332,7 +332,7 @@ class PostController(six.with_metaclass(h.ProxiedAttrMeta, BaseController)):
                                        target=self.post.thread.artifact or self.post.thread,
                                        related_nodes=[self.post.app_config.project],
                                        tags=['comment'])
-            redirect(request.referer or '/')
+            redirect(six.ensure_text(request.referer or '/'))
         elif request.method == 'GET':
             if self.post.deleted:
                 raise exc.HTTPNotFound
@@ -406,7 +406,7 @@ class PostController(six.with_metaclass(h.ProxiedAttrMeta, BaseController)):
         return dict(status=status, counts=self.post.react_counts)
 
     def error_handler(self, *args, **kwargs):
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @memorable_forget()
     @h.vardec
@@ -450,7 +450,7 @@ class PostController(six.with_metaclass(h.ProxiedAttrMeta, BaseController)):
     def attach(self, file_info=None):
         require_access(self.post, 'moderate')
         self.post.add_multiple_attachments(file_info)
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @expose()
     def _lookup(self, id, *remainder):
@@ -564,7 +564,7 @@ class ModerationController(six.with_metaclass(h.ProxiedAttrMeta, BaseController)
                     posted.thread.post_to_feed(posted)
         flash('{} {}'.format(h.text.plural(count, 'post', 'posts'),
                               'deleted' if delete else 'marked as spam' if spam else 'approved'))
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @expose()
     @require_post()
diff --git a/Allura/allura/controllers/project.py b/Allura/allura/controllers/project.py
index 4240042..77fea7e 100644
--- a/Allura/allura/controllers/project.py
+++ b/Allura/allura/controllers/project.py
@@ -874,7 +874,7 @@ class NeighborhoodAwardsController(object):
                     icon.filename, icon.file, content_type=icon.type,
                     square=True, thumbnail_size=(48, 48),
                     thumbnail_meta=dict(award_id=award._id))
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @expose()
     @require_post()
@@ -895,7 +895,7 @@ class NeighborhoodAwardsController(object):
             award.comment = comment
             with h.push_context(recipient_q._id):
                 g.post_event('project_updated')
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
 
 class AwardController(object):
@@ -963,7 +963,7 @@ class AwardController(object):
                     g.post_event('project_updated')
             M.AwardFile.query.remove(dict(award_id=self.award._id))
             self.award.delete()
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
 
 class GrantController(object):
@@ -1005,7 +1005,7 @@ class GrantController(object):
         self.grant.delete()
         with h.push_context(self.project._id):
             g.post_event('project_updated')
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
 
 class ProjectImporterController(object):
diff --git a/Allura/allura/controllers/repository.py b/Allura/allura/controllers/repository.py
index 08fcde0..4959af7 100644
--- a/Allura/allura/controllers/repository.py
+++ b/Allura/allura/controllers/repository.py
@@ -116,7 +116,7 @@ class RepoRootController(BaseController, FeedController):
         allura.tasks.repo_tasks.refresh.post()
         if request.referer:
             flash('Repository is being refreshed')
-            redirect(request.referer or '/')
+            redirect(six.ensure_text(request.referer or '/'))
         else:
             return '%r refresh queued.\n' % c.app.repo
 
@@ -157,7 +157,7 @@ class RepoRootController(BaseController, FeedController):
                     raise
                 except Exception as ex:
                     flash(str(ex), 'error')
-                    redirect(request.referer or '/')
+                    redirect(six.ensure_text(request.referer or '/'))
 
     @property
     def mr_widget(self):
diff --git a/Allura/allura/controllers/site_admin.py b/Allura/allura/controllers/site_admin.py
index 4fa7ffc..7def94e 100644
--- a/Allura/allura/controllers/site_admin.py
+++ b/Allura/allura/controllers/site_admin.py
@@ -513,7 +513,7 @@ class SiteNotificationController(object):
     def delete(self):
         self.note.delete()
         ThreadLocalORMSession().flush_all()
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
 
 class TaskManagerController(object):
@@ -696,7 +696,7 @@ class AdminUserDetailsController(object):
             flash('Comment added', 'ok')
         else:
             flash('Can not add comment "%s" for user %s' % (comment, user))
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @expose()
     @require_post()
@@ -715,7 +715,7 @@ class AdminUserDetailsController(object):
             AuthenticationProvider.get(request).deactivate_user(user)
             AuthenticationProvider.get(request).enable_user(user, audit=False)
             flash('Set user status to pending')
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @expose()
     @require_post()
@@ -727,7 +727,7 @@ class AdminUserDetailsController(object):
         AuthenticationProvider.get(request).set_password(user, None, pwd)
         h.auditlog_user('Set random password', user=user)
         flash('Password is set', 'ok')
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @expose()
     @require_post()
@@ -740,7 +740,7 @@ class AdminUserDetailsController(object):
             allura.controllers.auth.AuthController().password_recovery_hash(email)
         except HTTPFound:
             pass  # catch redirect to '/'
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @expose()
     @require_post()
@@ -759,7 +759,7 @@ class AdminUserDetailsController(object):
         if not user or user.is_anonymous():
             raise HTTPNotFound()
         allura.controllers.auth.PreferencesController()._update_emails(user, admin=True, form_params=kw)
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
 
 class StatsSiteAdminExtension(SiteAdminExtension):
diff --git a/Allura/allura/ext/admin/admin_main.py b/Allura/allura/ext/admin/admin_main.py
index 3930a94..4fd8623 100644
--- a/Allura/allura/ext/admin/admin_main.py
+++ b/Allura/allura/ext/admin/admin_main.py
@@ -645,10 +645,11 @@ class ProjectAdminController(BaseController):
             flash('%s: %s' % (exc.__class__.__name__, exc.args[0]),
                   'error')
         if request.referer is not None and tool is not None and 'delete' in tool[0] and \
-            re.search(c.project.url() + r'(admin\/|)' + tool[0]['mount_point']+ r'\/*', request.referer):
+            re.search(c.project.url() + r'(admin\/|)' + tool[0]['mount_point']+ r'\/*',
+                      six.ensure_text(request.referer)):
             # Redirect to root when deleting currect module
             redirect('../')
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @expose('jinja:allura.ext.admin:templates/export.html')
     def export(self, tools=None, with_attachments=False):
diff --git a/Allura/allura/ext/user_profile/user_main.py b/Allura/allura/ext/user_profile/user_main.py
index bf96096..2125605 100644
--- a/Allura/allura/ext/user_profile/user_main.py
+++ b/Allura/allura/ext/user_profile/user_main.py
@@ -22,6 +22,7 @@ import logging
 import pkg_resources
 from datetime import datetime
 
+import six
 from formencode import validators
 from tg import request
 from tg import tmpl_context as c, app_globals as g
@@ -128,21 +129,21 @@ class UserProfileController(BaseController, FeedController):
     def _check_can_message(self, from_user, to_user):
         if from_user is User.anonymous():
             flash('You must be logged in to send user messages.', 'info')
-            redirect(request.referer or '/')
+            redirect(six.ensure_text(request.referer or '/'))
 
         if not (from_user and from_user.get_pref('email_address')):
             flash('In order to send messages, you must have an email address '
                   'associated with your account.', 'info')
-            redirect(request.referer or '/')
+            redirect(six.ensure_text(request.referer or '/'))
 
         if not (to_user and to_user.get_pref('email_address')):
             flash('This user can not receive messages because they do not have '
                   'an email address associated with their account.', 'info')
-            redirect(request.referer or '/')
+            redirect(six.ensure_text(request.referer or '/'))
 
         if to_user.get_pref('disable_user_messages'):
             flash('This user has disabled direct email messages', 'info')
-            redirect(request.referer or '/')
+            redirect(six.ensure_text(request.referer or '/'))
 
     @expose('jinja:allura.ext.user_profile:templates/user_index.html')
     def index(self, **kw):
diff --git a/Allura/allura/lib/repository.py b/Allura/allura/lib/repository.py
index af88d9b..be49df1 100644
--- a/Allura/allura/lib/repository.py
+++ b/Allura/allura/lib/repository.py
@@ -18,6 +18,8 @@
 from __future__ import unicode_literals
 from __future__ import absolute_import
 import logging
+
+import six
 from six.moves.urllib.parse import quote
 
 from tg import tmpl_context as c, app_globals as g
@@ -269,14 +271,14 @@ class RepoAdminController(DefaultAdminController):
     @require_post()
     def set_extensions(self, **post_data):
         self.repo.additional_viewable_extensions = post_data['additional_viewable_extensions']
-        redirect(request.referer)
+        redirect(six.ensure_text(request.referer or '/'))
 
     @without_trailing_slash
     @expose('jinja:allura:templates/repo/default_branch.html')
     def set_default_branch_name(self, branch_name=None, **kw):
         if (request.method == 'POST') and branch_name:
             self.repo.set_default_branch(branch_name)
-            redirect(request.referer or '/')
+            redirect(six.ensure_text(request.referer or '/'))
         else:
             return dict(app=self.app,
                         default_branch_name=self.app.default_branch_name)
@@ -312,7 +314,7 @@ class RepoAdminController(DefaultAdminController):
             flash(message,
                   'error' if 'Invalid' in message else 'ok')
 
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
 
 class RepoAdminRestController(BaseController):
diff --git a/Allura/allura/lib/utils.py b/Allura/allura/lib/utils.py
index a16fa91..a6f782e 100644
--- a/Allura/allura/lib/utils.py
+++ b/Allura/allura/lib/utils.py
@@ -383,7 +383,7 @@ class AntiSpam(object):
                 else:
                     # regular antispam failure handling
                     tg.flash(error_msg, 'error')
-                    redirect(error_url or tg.request.referer or '.')
+                    redirect(error_url or six.ensure_text(tg.request.referer or '.'))
         return before_validate(antispam_hook)
 
 
diff --git a/Allura/allura/tests/functional/test_discuss.py b/Allura/allura/tests/functional/test_discuss.py
index 2849946..80b8633 100644
--- a/Allura/allura/tests/functional/test_discuss.py
+++ b/Allura/allura/tests/functional/test_discuss.py
@@ -84,7 +84,7 @@ class TestDiscuss(TestDiscussBase):
                 params[field['name']] = field.get('value') or ''
         params[f.find('textarea')['name']] = text
         r = self.app.post(f['action'], params=params,
-                          headers={str('Referer'): str(thread_link.encode("utf-8"))},
+                          headers={str('Referer'): str(thread_link)},
                           extra_environ=dict(username=str('root')))
         r = r.follow()
         return r
diff --git a/Allura/allura/tests/unit/patches.py b/Allura/allura/tests/unit/patches.py
index f8818be..02897dc 100644
--- a/Allura/allura/tests/unit/patches.py
+++ b/Allura/allura/tests/unit/patches.py
@@ -17,7 +17,7 @@
 
 from __future__ import unicode_literals
 from __future__ import absolute_import
-from mock import Mock, patch
+from mock import Mock, patch, MagicMock
 from tg import tmpl_context as c
 
 from allura.tests.unit.factories import (
@@ -60,4 +60,7 @@ def fake_redirect_patch(test_case):
 
 
 def fake_request_patch(test_case):
-    return patch('allura.controllers.discuss.request')
+    return patch('allura.controllers.discuss.request',
+                 MagicMock(
+                     referer='.'
+                 ))
diff --git a/ForgeBlog/forgeblog/main.py b/ForgeBlog/forgeblog/main.py
index 52a01b7..c27ed0b 100644
--- a/ForgeBlog/forgeblog/main.py
+++ b/ForgeBlog/forgeblog/main.py
@@ -526,7 +526,7 @@ class BlogAdminController(DefaultAdminController):
         self.app.config.options[
             'AllowEmailPosting'] = allow_email_posting and True or False
         flash('Blog options updated')
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @without_trailing_slash
     @expose('jinja:forgeblog:templates/blog/admin_exfeed.html')
@@ -568,7 +568,7 @@ class BlogAdminController(DefaultAdminController):
             flash('Invalid link(s): %s' %
                   ','.join(link for link in invalid_list), 'error')
 
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
 
 class RootRestController(BaseController, AppRestControllerMixin):
diff --git a/ForgeChat/forgechat/main.py b/ForgeChat/forgechat/main.py
index e3b4765..92dc696 100644
--- a/ForgeChat/forgechat/main.py
+++ b/ForgeChat/forgechat/main.py
@@ -24,6 +24,7 @@ import logging
 from datetime import date, time, datetime, timedelta
 
 # Non-stdlib imports
+import six
 from tg import expose, validate, redirect, flash
 from tg.decorators import with_trailing_slash
 from tg import tmpl_context as c, request
@@ -127,7 +128,7 @@ class AdminController(DefaultAdminController):
 
     @with_trailing_slash
     def index(self, **kw):
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @expose()
     @require_post()
diff --git a/ForgeDiscussion/forgediscussion/controllers/root.py b/ForgeDiscussion/forgediscussion/controllers/root.py
index 0356f30..b636f7c 100644
--- a/ForgeDiscussion/forgediscussion/controllers/root.py
+++ b/ForgeDiscussion/forgediscussion/controllers/root.py
@@ -19,6 +19,8 @@ from __future__ import unicode_literals
 from __future__ import absolute_import
 import json
 import logging
+
+import six
 from six.moves.urllib.parse import unquote
 from datetime import date, datetime, timedelta, time
 import calendar
@@ -133,13 +135,13 @@ class RootController(BaseController, DispatchIndex, FeedController):
     @validate(W.new_topic, error_handler=create_topic)
     @AntiSpam.validate('Spambot protection engaged', error_url='create_topic')
     def save_new_topic(self, subject=None, text=None, forum=None, subscribe=False, **kw):
-        self.rate_limit(model.ForumPost, 'Topic creation', request.referer)
+        self.rate_limit(model.ForumPost, 'Topic creation', six.ensure_text(request.referer or '/'))
         discussion = model.Forum.query.get(
             app_config_id=c.app.config._id,
             shortname=forum)
         if discussion.deleted and not has_access(c.app, 'configure')():
             flash('This forum has been removed.')
-            redirect(request.referer or '/')
+            redirect(six.ensure_text(request.referer or '/'))
         require_access(discussion, 'post')
         thd = discussion.get_discussion_thread(dict(
             headers=dict(Subject=subject)))[0]
diff --git a/ForgeDiscussion/forgediscussion/forum_main.py b/ForgeDiscussion/forgediscussion/forum_main.py
index e481c23..5c6b19d 100644
--- a/ForgeDiscussion/forgediscussion/forum_main.py
+++ b/ForgeDiscussion/forgediscussion/forum_main.py
@@ -331,7 +331,7 @@ class ForumAdminController(DefaultAdminController):
                 else:
                     forum.acl = []
         flash('Forums updated')
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @h.vardec
     @expose()
diff --git a/ForgeSVN/forgesvn/svn_main.py b/ForgeSVN/forgesvn/svn_main.py
index 5fafbf7..315d910 100644
--- a/ForgeSVN/forgesvn/svn_main.py
+++ b/ForgeSVN/forgesvn/svn_main.py
@@ -19,6 +19,8 @@
 from __future__ import unicode_literals
 from __future__ import absolute_import
 import logging
+
+import six
 from tg import tmpl_context as c, request
 
 # Non-stdlib imports
@@ -184,7 +186,7 @@ class SVNImportController(BaseController, AdminControllerMixin):
             M.Notification.post_user(
                 c.user, self.app.repo, 'error',
                 text="Can't import into non empty repository.")
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
 
 class SVNCommitBrowserController(BaseController):
diff --git a/ForgeShortUrl/forgeshorturl/main.py b/ForgeShortUrl/forgeshorturl/main.py
index a471983..f03b9b6 100644
--- a/ForgeShortUrl/forgeshorturl/main.py
+++ b/ForgeShortUrl/forgeshorturl/main.py
@@ -17,6 +17,8 @@
 
 from __future__ import unicode_literals
 from __future__ import absolute_import
+
+import six
 from tg import expose, validate, redirect, flash, request
 from tg.decorators import without_trailing_slash
 
@@ -203,7 +205,7 @@ class ShortURLAdminController(DefaultAdminController):
 
     @expose()
     def index(self, **kw):
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @without_trailing_slash
     @expose('json:')
@@ -231,7 +233,7 @@ class ShortURLAdminController(DefaultAdminController):
                     names = {'short_url': 'Short url', 'full_url': 'Full URL'}
                     error_msg += '%s: %s ' % (names[msg], c.form_errors[msg])
                     flash(error_msg, 'error')
-                redirect(request.referer or '/')
+                redirect(six.ensure_text(request.referer or '/'))
 
             shorturl = ShortUrl.query.find({
                 'app_config_id': self.app.config._id,
@@ -240,7 +242,7 @@ class ShortURLAdminController(DefaultAdminController):
             if shorturl is not None:
                 if not update:
                     flash('Short url %s already exists' % short_url, 'error')
-                    redirect(request.referer or '/')
+                    redirect(six.ensure_text(request.referer or '/'))
                 else:
                     msg = ('update short url %s from %s to %s'
                            % (short_url, shorturl.full_url, full_url))
@@ -261,7 +263,7 @@ class ShortURLAdminController(DefaultAdminController):
             shorturl.last_updated = datetime.utcnow()
 
             M.AuditLog.log(msg)
-            redirect(request.referer or '/')
+            redirect(six.ensure_text(request.referer or '/'))
         return dict(
             app=self.app,
             url_len=len(ShortUrl.build_short_url(c.app, short_name='')))
diff --git a/ForgeTracker/forgetracker/tracker_main.py b/ForgeTracker/forgetracker/tracker_main.py
index 28c7727..2aa2b0e 100644
--- a/ForgeTracker/forgetracker/tracker_main.py
+++ b/ForgeTracker/forgetracker/tracker_main.py
@@ -1605,16 +1605,16 @@ class TicketController(BaseController, FeedController):
             tracker = M.AppConfig.query.get(_id=t_id)
             if tracker is None:
                 flash('Select valid tracker', 'error')
-                redirect(request.referer or '/')
+                redirect(six.ensure_text(request.referer or '/'))
 
             if tracker == self.ticket.app.config:
                 flash('Ticket already in a selected tracker', 'info')
-                redirect(request.referer or '/')
+                redirect(six.ensure_text(request.referer or '/'))
 
             if not has_access(tracker, 'admin')():
                 flash('You should have admin access to destination tracker',
                       'error')
-                redirect(request.referer or '/')
+                redirect(six.ensure_text(request.referer or '/'))
 
             new_ticket = self.ticket.move(tracker)
             c.app.globals.invalidate_bin_counts()
@@ -1702,7 +1702,7 @@ class TrackerAdminController(DefaultAdminController):
         for k, val in six.iteritems(kw):
             self.app.config.options[k] = val
         flash('Options updated')
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @expose()
     @require_post()
@@ -1712,7 +1712,7 @@ class TrackerAdminController(DefaultAdminController):
                 self.app.globals['show_in_search'][column] = True
             else:
                 self.app.globals['show_in_search'][column] = False
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @expose()
     def update_tickets(self, **post_data):
@@ -1805,7 +1805,7 @@ class TrackerAdminController(DefaultAdminController):
 
         self.app.globals.custom_fields = custom_fields
         flash('Fields updated')
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
 
 class RootRestController(BaseController, AppRestControllerMixin):
diff --git a/ForgeWiki/forgewiki/wiki_main.py b/ForgeWiki/forgewiki/wiki_main.py
index d635222..c30eb8e 100644
--- a/ForgeWiki/forgewiki/wiki_main.py
+++ b/ForgeWiki/forgewiki/wiki_main.py
@@ -22,6 +22,8 @@ import json
 import logging
 import os
 from pprint import pformat
+
+import six
 from six.moves.urllib.parse import unquote
 
 # Non-stdlib imports
@@ -521,7 +523,7 @@ class RootController(BaseController, DispatchIndex, FeedController):
             M.Mailbox.subscribe(type='direct')
         elif unsubscribe:
             M.Mailbox.unsubscribe()
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
 
 class PageController(BaseController, FeedController):
@@ -793,7 +795,7 @@ class PageController(BaseController, FeedController):
         self.page.add_multiple_attachments(file_info)
         if is_ajax(request):
             return
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))
 
     @expose('json:')
     @require_post()
@@ -940,4 +942,4 @@ class WikiAdminController(DefaultAdminController):
         self.app.show_right_bar = show_right_bar
         self.app.allow_email_posting = allow_email_posting
         flash('Wiki options updated')
-        redirect(request.referer or '/')
+        redirect(six.ensure_text(request.referer or '/'))