Repository: ckan/ckan

Issue:
Redirecting to same page in non-root hosted ckan adds extra root_path to url 
### CKAN Version if known (or site URL)
2.6.0, 2.6.1, master

### Please describe the expected behaviour
In ckanext-harvest reharvest and clear buttons and in ckanext-report button redirect to the same page, if ckan is hosted in for example /data, I expect the redirection to /data/harvest/admin/someharvestname.

### Please describe the actual behaviour

Resulting url is /data/data/harvest/admin/someharvestname

### What steps can be taken to reproduce the issue? 

Clear or reharvest non-root hosted ckan.

### Some additional info

https://github.com/ckan/ckan/pull/2998 is probably the culprit since redirection works in ckan 2.3.5.
Will supply PR which should be validated for not breaking anything else.




Discussion:
@fanjinfei would you look at this when you have some time?
for a none root such as `/data/{{LANG}}`, `h.url_for('/data/user/login')` is wrong (`/data/data/user/login`).  `h.url_for(controller='user', action='login')` is correct. 
h.redirect_to() should have the same problem.
Yes, this issue can circumvented by modifying the extensions to redirect to controller actions instead of urls.

For ckanext-harvest https://github.com/ckan/ckanext-harvest/blob/master/ckanext/harvest/controllers/view.py#L77 and for ckanext-report https://github.com/datagovuk/ckanext-report/blob/master/ckanext/report/controllers.py#L46

But since the helper functions accept urls as parameters and the issue does not manifest itself when ckan is hosted in root path, I suspect there is a bug in ckan core with url amending which should be fixed or at least prohibit urls as parameters.
When passing urls to redirect_to I agree with @Zharktas, we should never add the root path. That way existing code that does `redirect_to(url_for(...))` will work, `redirect_to('http://some.other.place/..')` will work and `redirect_to('/user/login')` won't work because it should be written with the controller and action parameters as @fanjinfei suggests. We should also document this on redirect_to.
This issue might manifest itself with ckan 2.7 as there is now new handler [helpers.py](https://github.com/ckan/ckan/blob/ckan-2.7.0/ckan/lib/helpers.py#L162]) if the path starts with /-character.

If the root path is set, both ckan 2.6 and 2.7 will generate urls incorrectly with url_for:

    h.url_for('http://example.com')
    '/datahttp://example.com'

When calling redirect_to with 'http://example.com', 2.6 redirects just fine, but 2.7 adds site_url to the final url and redirection no longer works. I'll take a look if this is actually caused by the same issue and try fix it. If it's not, I'll create separate issue.  


Resolving patch:
diff --git a/ckan/controllers/user.py b/ckan/controllers/user.py
--- a/ckan/controllers/user.py
+++ b/ckan/controllers/user.py
@@ -397,8 +397,7 @@ def login(self, error=None):
         if not c.user:
             came_from = request.params.get('came_from')
             if not came_from:
-                came_from = h.url_for(controller='user', action='logged_in',
-                                      __ckan_no_root=True)
+                came_from = h.url_for(controller='user', action='logged_in')
             c.login_handler = h.url_for(
                 self._get_repoze_handler('login_handler_path'),
                 came_from=came_from)
@@ -436,10 +435,9 @@ def logout(self):
         # Do any plugin logout stuff
         for item in p.PluginImplementations(p.IAuthenticator):
             item.logout()
-        url = h.url_for(controller='user', action='logged_out_page',
-                        __ckan_no_root=True)
+        url = h.url_for(controller='user', action='logged_out_page')
         h.redirect_to(self._get_repoze_handler('logout_handler_path') +
-                      '?came_from=' + url)
+                      '?came_from=' + url, parse_url=True)
 
     def logged_out(self):
         # redirect if needed
diff --git a/ckan/lib/helpers.py b/ckan/lib/helpers.py
--- a/ckan/lib/helpers.py
+++ b/ckan/lib/helpers.py
@@ -152,6 +152,12 @@ def redirect_to(*args, **kw):
 
         toolkit.redirect_to('dataset_read', id='changed')
 
+    If given a single string as argument, this redirects without url parsing
+
+        toolkit.redirect_to('http://example.com')
+        toolkit.redirect_to('/dataset')
+        toolkit.redirect_to('/some/other/path')
+
     '''
     if are_there_flash_messages():
         kw['__no_cache__'] = True
@@ -159,7 +165,19 @@ def redirect_to(*args, **kw):
     # Routes router doesn't like unicode args
     uargs = map(lambda arg: str(arg) if isinstance(arg, unicode) else arg,
                 args)
-    _url = url_for(*uargs, **kw)
+
+    _url = ''
+    skip_url_parsing = False
+    parse_url = kw.pop('parse_url', False)
+    if uargs and len(uargs) is 1 and isinstance(uargs[0], basestring) \
+            and (uargs[0].startswith('/') or is_url(uargs[0])) \
+            and parse_url is False:
+        skip_url_parsing = True
+        _url = uargs[0]
+
+    if skip_url_parsing is False:
+        _url = url_for(*uargs, **kw)
+
     if _url.startswith('/'):
         _url = str(config['ckan.site_url'].rstrip('/') + _url)
 


Test patch: