Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

Issue when a query string is used on a base url (html5 mode) without a trailing slash #14018

Closed
cadilhac opened this issue Feb 12, 2016 · 23 comments

Comments

@cadilhac
Copy link

Using Angular 1.5 in an asp.net mvc site.

I have a base tag:

<base href="/fr/">

and html5mode is set.

The site works perfectly. I use ui-router... no issues.
But today I hit one and this is when I try to browse to this kind of url:

http://example.com/fr?arg=1

I get an error from angular:

url is undefined
trimEmptyHash@http://............/angular.js?tag=2016020919:12165:3

If I navigate to /fr/?arg=1 (with the trailing slash) the error goes away.

In this context, is this a bug? How to get the query string to work without a trailing slash? "Normal" people typing a url normally will never put a slash before the question mark...

Note: I don't know if this is a ui-router or an angularjs issue. I checked this and tried the 2 mentioned solutions but it didn't change anything (a breakpoint in the rule and I see the rule is not even called).

@cadilhac
Copy link
Author

I traced into angular code, especially $$parseLinkUrl in the LocationHtml5Url function.

  this.$$parseLinkUrl = function(url, relHref) {
    if (relHref && relHref[0] === '#') {
      // special case for links to hash fragments:
      // keep the old url and only replace the hash fragment
      this.hash(relHref.slice(1));
      return true;
    }
    var appUrl, prevAppUrl;
    var rewrittenUrl;

    if (isDefined(appUrl = beginsWith(appBase, url))) {
      prevAppUrl = appUrl;
      if (isDefined(appUrl = beginsWith(basePrefix, appUrl))) {
        rewrittenUrl = appBaseNoFile + (beginsWith('/', appUrl) || appUrl);
      } else {
        rewrittenUrl = appBase + prevAppUrl;
      }
    } else if (isDefined(appUrl = beginsWith(appBaseNoFile, url))) {
      rewrittenUrl = appBaseNoFile + appUrl;
    } else if (appBaseNoFile == url + '/') {
      rewrittenUrl = appBaseNoFile;
    }
    if (rewrittenUrl) {
      this.$$parse(rewrittenUrl);
    }
    return !!rewrittenUrl;
  };

For a url like /fr?arg, appBase has a trailing slash and url has not, so rewrittenUrl is never set.
For a url like /fr/about?arg, appBase with its trailing slash is contained in the url, so rewrittenUrl is set.

@cadilhac cadilhac changed the title Issue when a query string is used on url without trailing slash Issue when a query string is used on a base url (html5 mode) without a trailing slash Feb 12, 2016
@gkalpak
Copy link
Member

gkalpak commented Feb 13, 2016

I don't think you need a trailing / in your base URL. Have you tried removing that ?

@gkalpak
Copy link
Member

gkalpak commented Feb 13, 2016

Hm...omitting the trailing / will break relative URLs to assets, so it's not really useful.

One thing that seems to work is including the filename in baseHref (in fact including anything after the trailing /, such as xyz or even . seems to work - at least on Chrome, Firefox and Edge).
E.g. change <base href="/fr/" /> to:

  • <base href="/fr/index.html" /> or
  • <base href="/fr/xyz" /> or even
  • <base href="/fr/." />

@gkalpak gkalpak removed the type: bug label Feb 13, 2016
@cadilhac
Copy link
Author

No it doesn't work. And anyway, after tracing into $$parseLinkUrl again, I don't see how it could work.

@gkalpak
Copy link
Member

gkalpak commented Feb 13, 2016

@cadilhac, you mean appending something after the / doesn't work ?
(It worked for me in a plnkr 😕)

@cadilhac
Copy link
Author

Yes I mean that. Can you show me such a plunker?
Also remember that this issue happens when a user types the url in the address bar of the browser, not when following an internal link to the base url containing a query string. How can you achieve such a test with a plunker?

@gkalpak
Copy link
Member

gkalpak commented Feb 13, 2016

@cadilhac, you are right, I missed that. Would it work if you configured your server to rewrite /fr as /fr/(as a workaround).

I'm not sure if ignoring the trailing slash might break other usecases.
/cc @petebacondarwin (who knows his way around $location 😄)

@cadilhac
Copy link
Author

Well, I am surprised that nobody met this case yet. It seems pretty usual to see a query string just afer the base url, no? Does it look like a bug in angular? Why doesn't $$parseLinkUrl handle this normal case?
I tried the rewriting and of course it fixed my issue. I hope it is only a temporary workaround though...

@cadilhac
Copy link
Author

I should add that I'm not able to use my rewriting rule when in production because it tries to add a slash to everything. It breaks loading assets created by the Cassette minifier (asp.net MVC). It breaks ajax calls too. So, right now, I'm stuck.

@petebacondarwin petebacondarwin self-assigned this Feb 26, 2016
@petebacondarwin petebacondarwin modified the milestones: 1.5.x, Backlog Feb 26, 2016
@petebacondarwin
Copy link
Contributor

I will take a look at this during next week OK?

@cadilhac
Copy link
Author

@petebacondarwin of course this is ok 👍

@cadilhac
Copy link
Author

Hi @petebacondarwin. What is the status of this development?

@petebacondarwin
Copy link
Contributor

Tomorrow or Monday I promise. Sorry

@petebacondarwin
Copy link
Contributor

OK, so I have had a little play with this today...

The first thing I notice is that most http servers that provide a fallback rewrite for HTML5 history support will indeed redirect a request for /fr to /fr/, so I had to write a more simple HTTP server to actually recreate this problem.

The second thing, is that if your base href is /fr/ then this is equivalent to a base href of /fr/index.html, and that the URL of /fr is actually outside the base href. The URL specification makes no requirement that the two should be treated as equivalent. See http://tools.ietf.org/html/rfc3986#section-6.2.4

This is an unusual case, which most people do not hit because their servers are configured to redirect page requests that do not have a trailing slash to ones that do. I don't think that we can special case this in $location or even $browser since it could break URLs that do not expect to behave this way.

@cadilhac: The correct approach, I believe, is to set up redirects correctly for such URLs:

  • In the first place I would look into reconfiguring your server to do the redirect. You do not need to do it across all URLs, only those that are requesting pages that will be Angular apps.
  • If this is still not possible then you can try doing the redirect on the browser. Here is an example of what you could do:
<html>
<head>
  <base href="/fr/">
</head>
<body ng-app="app">
  <a href="/fr?arg=2">Link</a>

  <script>
    function absoluteUrl(url) {
      var urlParsingNode = document.createElement("a");
      urlParsingNode.setAttribute("href", url);
      return urlParsingNode.href;
    }

    function stripQuery(url) {
      return url.replace(/\?[^?]*/, '');
    }

    var baseElement = document.getElementsByTagName('base')[0];
    var baseUrl = absoluteUrl(baseElement ? baseElement.getAttribute('href') : '/');
    var currentUrl = stripQuery(location.href);

    console.log(baseUrl, currentUrl);

    if (currentUrl + '/' === baseUrl) {
      var newUrl = location.href.replace(currentUrl, baseUrl);
      console.log('do redirect to', newUrl);
      location.href = newUrl;
    }
  </script>


  <script src="https://code.angularjs.org/1.5.0/angular.js"></script>

  <script>
    angular.module('app', [])
    .config(function($locationProvider) {
      $locationProvider.html5Mode(true);
    })
    .run(function($location) {
      console.log('OK', $location.absUrl());
    });
  </script>
</body>
</html>

@cadilhac
Copy link
Author

I find it strange not to handle this in angular and to rely on the configuration of the web server. But this is your decision, of course. I will try what you suggest. Will it be in the doc for people hitting the same wall?

@petebacondarwin
Copy link
Contributor

The reason not to handle it in Angular is that adding the code I suggest could well break a lot of apps unnecessarily for this unusual use case - to put this in context, this is the behaviour since 2011 and you are the first person to report having the difficulty.

@keatkeat87
Copy link

I also face this problem , Google bring me here. Hope angular team consider this condition.

@shitala-cuelogic
Copy link

I have written $locationProvider.html5Mode(true) this in my config function and set base url with base href="/foldername/" My problem is that when I run my code in browser with ip and folder name i.e localhost/foldername, its working fine. But when i am reloading my page by hitting refresh button of browser, its not working. Could please tell me what is the issue with my code .

@petebacondarwin
Copy link
Contributor

@shitala-cuelogic have you configured your webserver to rewrite "deep" links back to the index.html?

@shitala-cuelogic
Copy link

@petebacondarwin thanks for your response. Is it mandatory to configured webserver to rewrite "deep" links back to the index.html ? is there is any other ways in which we can solve this problem?

@GunnerJnr
Copy link

target="_self"

ref: https://docs.angularjs.org/guide/$location#html-link-rewriting

@petebacondarwin
Copy link
Contributor

@shitala-cuelogic - if you want to use HTML5 deeplinking then you must indeed configure your server.

@michaelbudnik
Copy link

michaelbudnik commented Jan 4, 2017

That's a pain.
I was looking for the solution over a year ago:
http://stackoverflow.com/questions/34156213/angular-html5mode-prevents-links-outside-of-base-href-to-be-loaded

Had to use a url with two parts, so rather than having /myurl?param=value I had to use /myurl/app?param=value which is worse from seo perspective. I didn't want to have /myurl/?param=value which is just ugly.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.