97

There's a function, which gives me urls like:

./some.css
./extra/some.css
../../lib/slider/slider.css

It's always a relative path.

Let's think we know current path of the page, like http://site.com/stats/2012/, not sure how do I convert these relative paths to real ones?

We should get something like:

./some.css => http://site.com/stats/2012/some.css
./extra/some.css => http://site.com/stats/2012/extra/some.css
../../lib/slider/slider.css => http://site.com/lib/slider/slider.css

No jQuery, only vanilla javascript.

Ryan B
  • 3,364
  • 21
  • 35
Jasper
  • 5,090
  • 11
  • 34
  • 41

13 Answers13

187

The most simple, efficient and correct way to do so it to just use URL api.

new URL("http://www.stackoverflow.com?q=hello").href;
//=> "http://www.stackoverflow.com/?q=hello"

new URL("mypath","http://www.stackoverflow.com").href;
//=> "http://www.stackoverflow.com/mypath"

new URL("../mypath","http://www.stackoverflow.com/search").href
//=> "http://www.stackoverflow.com/mypath"

new URL("../mypath", document.baseURI).href
//=> "https://stackoverflow.com/questions/mypath"

Performance wise, this solution is on par with using string manipulation and twice as fast as creating a tag.

Cid
  • 14,968
  • 4
  • 30
  • 45
Elad
  • 19,079
  • 18
  • 62
  • 71
  • 3
    Love this solution, however didn't work with React Native due to lack or URL support. Instead I used [https://www.npmjs.com/package/url](https://www.npmjs.com/package/url), which worked well. – jsaven Aug 12 '18 at 00:05
  • 3
    @hex You're right that using `window.location.href` has this drawback. I've edited the answer to make it use `document.baseURI` instead. – Flimm Nov 08 '19 at 11:50
  • This will not be a generalised solution for development environment. – london_utku May 18 '20 at 21:03
  • How do I run this solution in ubuntu? –  Oct 31 '20 at 00:58
74

Javascript will do it for you. There's no need to create a function.

var link = document.createElement("a");
link.href = "../../lib/slider/slider.css";
alert(link.protocol+"//"+link.host+link.pathname+link.search+link.hash);

// Output will be "http://www.yoursite.com/lib/slider/slider.css"

But if you need it as a function:

var absolutePath = function(href) {
    var link = document.createElement("a");
    link.href = href;
    return (link.protocol+"//"+link.host+link.pathname+link.search+link.hash);
}

Update: Simpler version if you need the full absolute path:

var absolutePath = function(href) {
    var link = document.createElement("a");
    link.href = href;
    return link.href;
}
allenhwkim
  • 27,270
  • 18
  • 89
  • 122
  • 4
    This doesn't work in IE, not even IE11. See this for more details: http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue – Oran Dennison Jul 13 '15 at 21:41
  • 3
    Works great (in IE too)...I found that simply accessing `link.href` after being set returned the resolved URL (i.e., no need to manually rebuild the href). – Chris Baxter Aug 04 '15 at 18:03
  • @ChrisBaxter Same here. I'm a bit annoyed that this popular answer refers to protocol, host, etc... whereas href looks simpler and safer. Is there a reason for this? I would like to know... – philippe_b Mar 01 '18 at 13:06
  • It's actually a DOM solution – Lukasz Prus Jul 29 '18 at 11:25
51

This should do it:

function absolute(base, relative) {
    var stack = base.split("/"),
        parts = relative.split("/");
    stack.pop(); // remove current file name (or empty string)
                 // (omit if "base" is the current folder without trailing slash)
    for (var i=0; i<parts.length; i++) {
        if (parts[i] == ".")
            continue;
        if (parts[i] == "..")
            stack.pop();
        else
            stack.push(parts[i]);
    }
    return stack.join("/");
}
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • some sites have internal resources with `~`, `~/media/style.css`, ran into this the other day – Daniel Lizik Jun 14 '16 at 13:46
  • @Daniel_L: `~` typically means something else than `base`. And it's not exactly a relative path :-) If you need help with paths containing a tilde, please [ask a new question](http://stackoverflow.com/questions/ask) – Bergi Jun 14 '16 at 13:48
  • 2
    For anyone encountering this later, using document.location.href for base worked for me. – nixkuroi Mar 24 '17 at 20:48
  • 3
    This solution has a problem when the relative url starts with a `/` for example `/some.css`. A correct implementation would in such a case remove all of the items in the stack after the domain name. – Vineet May 15 '17 at 18:54
  • 1
    @Vineet Correct, however a path starting with `/` is not relative. My function only considers paths, not URIs. – Bergi May 15 '17 at 19:05
  • I didn’t deal with site-root relative issue, but I had what I”m guessing is a more common one: mix of relative and absolute URL links. That is easily fixed by checking the condition: – Robert Crooks Jul 08 '19 at 23:01
  • @nixkuroi It's better to use `document.baseURI` instead of `document.location.href`, as the former takes into account the `` HTML element. – Flimm Nov 08 '19 at 11:51
  • @Flimm Unfortunately, `document.baseURI` isn't valid for internet explorer. – Robert McKee Jul 06 '20 at 19:44
7

This from MDN is unbreakable!

/*\
|*|
|*|  :: translate relative paths to absolute paths ::
|*|
|*|  https://developer.mozilla.org/en-US/docs/Web/API/document.cookie
|*|
|*|  The following code is released under the GNU Public License, version 3 or later.
|*|  http://www.gnu.org/licenses/gpl-3.0-standalone.html
|*|
\*/

function relPathToAbs (sRelPath) {
  var nUpLn, sDir = "", sPath = location.pathname.replace(/[^\/]*$/, sRelPath.replace(/(\/|^)(?:\.?\/+)+/g, "$1"));
  for (var nEnd, nStart = 0; nEnd = sPath.indexOf("/../", nStart), nEnd > -1; nStart = nEnd + nUpLn) {
    nUpLn = /^\/(?:\.\.\/)*/.exec(sPath.slice(nEnd))[0].length;
    sDir = (sDir + sPath.substring(nStart, nEnd)).replace(new RegExp("(?:\\\/+[^\\\/]*){0," + ((nUpLn - 1) / 3) + "}$"), "/");
  }
  return sDir + sPath.substr(nStart);
}

Sample usage:

/* Let us be in /en-US/docs/Web/API/document.cookie */

alert(location.pathname);
// displays: /en-US/docs/Web/API/document.cookie

alert(relPathToAbs("./"));
// displays: /en-US/docs/Web/API/

alert(relPathToAbs("../Guide/API/DOM/Storage"));
// displays: /en-US/docs/Web/Guide/API/DOM/Storage

alert(relPathToAbs("../../Firefox"));
// displays: /en-US/docs/Firefox

alert(relPathToAbs("../Guide/././API/../../../Firefox"));
// displays: /en-US/docs/Firefox
madmurphy
  • 1,451
  • 11
  • 20
  • 2
    It doesn't handle relative paths with a leading `/`. Example: `relPathToAbs("/?foo=bar")` should return "/?foo=bar" instead it returns `/questions/14780350/?foo=bar` which means it's not properly detecting a leading `/` when sends the path back to the root. – Owen Allen Nov 08 '14 at 22:35
  • @Nucleon The _path_ is the part of the URL included between the _protocol_ segment (`http(s)://`) and the _search_ / _hash_ segments (`?`/`#`). **It is not** concern of that function to separate the _path_ part from the _search_ / _hash_ parts, also because Regular Expressions can make it very easy: `relPathToAbs("./?foo=bar#someHash".replace(/^[^\?#]*/, "$&"))` – madmurphy Nov 10 '14 at 17:10
  • 1
    @Nucleon Sorry, I had misunderstood your comment. A _path_ starting with `/` is **already** an _absolute_ path! Therefore it has no sense to put it as argument of that function! The string `"/?foo=bar"` **is** already an _absolute_ path. – madmurphy Nov 12 '14 at 12:17
  • Exactly, but run your function on this page's URL `relPathToAbs("/?foo=bar")`. It doesn't return `/?foo=bar` (because it's already absolute) it returns `/questions/14780350/?foo=bar`. – Owen Allen Nov 12 '14 at 18:03
  • 1
    @Nucleon The function expects you're giving a relative path. The string `/?foo=bar` is a **wrong** relative path (indeed it is an absolute path). A valid relative path must start with `/^(?:\.\.?\/)*[^\/]/`. E.g.: `/^(?:\.\.?\/)*[^\/]/.test("./hello/world")` --> `true`; `/^(?:\.\.?\/)*[^\/]/.test("../hi")` --> `true`; `/^(?:\.\.?\/)*[^\/]/.test("../././../foo")` --> `true`; `/^(?:\.\.?\/)*[^\/]/.test("/")` --> `false`; `/^(?:\.\.?\/)*[^\/]/.test("/?foo=bar")` --> `false`; – madmurphy Nov 17 '14 at 12:47
  • The only correct example is by @allenhwkim. Because it is the only code that correctly handle relative URLs such as `https:/path`. Yep, it is correct URL to the same host but with secure protocol. You may indifinitely talk about "what is *true relative* URL" but when you do a job you need working things, not an abstract essays. – Alexander Pravdin Jun 18 '15 at 04:07
  • BTW, the URL `/foo?bar` is *relative* to the current host. – Alexander Pravdin Jun 18 '15 at 04:08
  • The URL `//host/foo?bar` is relative to the current protocol. – Alexander Pravdin Jun 18 '15 at 04:25
  • It might be prudent to point out in the answer that this snipped is licensed under the terms of the very restrictive GPL license, lest people feel invited to copy-paste this into their closed-source codebases. – rdb Oct 26 '19 at 13:37
  • rdb, *"**Very** restrictive GPL license"*?? Restrictive as in "Do whatever you want with this code except hiding it from other people like you"? – madmurphy Oct 28 '19 at 19:13
6

If you want to make a relative-to-absolute conversion for a link from a custom webpage in your browser (not for the page that runs your script), you can use a more enhanced version of the function suggested by @Bergi:

var resolveURL=function resolve(url, base){
    if('string'!==typeof url || !url){
        return null; // wrong or empty url
    }
    else if(url.match(/^[a-z]+\:\/\//i)){ 
        return url; // url is absolute already 
    }
    else if(url.match(/^\/\//)){ 
        return 'http:'+url; // url is absolute already 
    }
    else if(url.match(/^[a-z]+\:/i)){ 
        return url; // data URI, mailto:, tel:, etc.
    }
    else if('string'!==typeof base){
        var a=document.createElement('a'); 
        a.href=url; // try to resolve url without base  
        if(!a.pathname){ 
            return null; // url not valid 
        }
        return 'http://'+url;
    }
    else{ 
        base=resolve(base); // check base
        if(base===null){
            return null; // wrong base
        }
    }
    var a=document.createElement('a'); 
    a.href=base;

    if(url[0]==='/'){ 
        base=[]; // rooted path
    }
    else{ 
        base=a.pathname.split('/'); // relative path
        base.pop(); 
    }
    url=url.split('/');
    for(var i=0; i<url.length; ++i){
        if(url[i]==='.'){ // current directory
            continue;
        }
        if(url[i]==='..'){ // parent directory
            if('undefined'===typeof base.pop() || base.length===0){ 
                return null; // wrong url accessing non-existing parent directories
            }
        }
        else{ // child directory
            base.push(url[i]); 
        }
    }
    return a.protocol+'//'+a.hostname+base.join('/');
}

It'll return null if something is wrong.

Usage:

resolveURL('./some.css', 'http://example.com/stats/2012/'); 
// returns http://example.com/stats/2012/some.css

resolveURL('extra/some.css', 'http://example.com/stats/2012/');
// returns http://example.com/stats/2012/extra/some.css

resolveURL('../../lib/slider/slider.css', 'http://example.com/stats/2012/');
// returns http://example.com/lib/slider/slider.css

resolveURL('/rootFolder/some.css', 'https://example.com/stats/2012/');
// returns https://example.com/rootFolder/some.css

resolveURL('localhost');
// returns http://localhost

resolveURL('../non_existing_file', 'example.com')
// returns null
optimizitor
  • 807
  • 13
  • 18
4
function canonicalize(url) {
    var div = document.createElement('div');
    div.innerHTML = "<a></a>";
    div.firstChild.href = url; // Ensures that the href is properly escaped
    div.innerHTML = div.innerHTML; // Run the current innerHTML back through the parser
    return div.firstChild.href;
}

This works on IE6 too, unlike some other solutions (see Getting an absolute URL from a relative one. (IE6 issue))

Community
  • 1
  • 1
Sebastien Lorber
  • 89,644
  • 67
  • 288
  • 419
2

The proposed and accepted solution does not support server relative URLs and does not work on absolute URLs. If my relative is /sites/folder1 it won't work for example.

Here is another function that supports full, server relative or relative URLs as well as ../ for one level up. It is not perfect but covers a lot of options. Use this when your base URL is not the current page URL, otherwise there are better alternatives.

    function relativeToAbsolute(base, relative) {
    //make sure base ends with /
    if (base[base.length - 1] != '/')
        base += '/';

    //base: https://server/relative/subfolder/
    //url: https://server
    let url = base.substr(0, base.indexOf('/', base.indexOf('//') + 2));
    //baseServerRelative: /relative/subfolder/
    let baseServerRelative = base.substr(base.indexOf('/', base.indexOf('//') + 2));
    if (relative.indexOf('/') === 0)//relative is server relative
        url += relative;
    else if (relative.indexOf("://") > 0)//relative is a full url, ignore base.
        url = relative;
    else {
        while (relative.indexOf('../') === 0) {
            //remove ../ from relative
            relative = relative.substring(3);
            //remove one part from baseServerRelative. /relative/subfolder/ -> /relative/
            if (baseServerRelative !== '/') {
                let lastPartIndex = baseServerRelative.lastIndexOf('/', baseServerRelative.length - 2);
                baseServerRelative = baseServerRelative.substring(0, lastPartIndex + 1);
            }
        }
        url += baseServerRelative + relative;//relative is a relative to base.
    }

    return url;
}

Hope this helps. It was really frustrating not to have this basic utility available in JavaScript.

Shai Petel
  • 188
  • 11
  • As far as I can tell, this wouldn't work with protocol-independent urls? Eg. `//www.example.com/page`. – Liam Gray Apr 04 '17 at 22:18
  • No, but it is easy to support if you just change this statement: else if (relative.indexOf("://") > 0) and remove the : but I keep it this way since it can take the protocol from the base, which I send dynamically anyway. – Shai Petel Jul 17 '17 at 19:23
2

I know this is a very old question, but you could do it with: (new URL(relativePath, location)).href.

illogicalapple
  • 732
  • 3
  • 16
1

The href solution only works once the document is loaded (at least in IE11). This worked for me:

link = link || document.createElement("a");
link.href =  document.baseURI + "/../" + href;
return link.href;

See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base

Corey Alix
  • 2,694
  • 2
  • 27
  • 38
  • 2
    I think you should use `document.baseURI` instead of `window.location.href` in order to take into account a possible `` element. – Flimm Nov 08 '19 at 11:53
0

I had to add a fix to the accepted solution because we can have slashes after # in our angularjs navigation.

function getAbsoluteUrl(base, relative) {
  // remove everything after #
  var hashPosition = base.indexOf('#');
  if (hashPosition > 0){
    base = base.slice(0, hashPosition);
  }

  // the rest of the function is taken from http://stackoverflow.com/a/14780463
  // http://stackoverflow.com/a/25833886 - this doesn't work in cordova
  // http://stackoverflow.com/a/14781678 - this doesn't work in cordova
  var stack = base.split("/"),
      parts = relative.split("/");
  stack.pop(); // remove current file name (or empty string)
               // (omit if "base" is the current folder without trailing slash)
  for (var i=0; i<parts.length; i++) {
    if (parts[i] == ".")
      continue;
    if (parts[i] == "..")
      stack.pop();
    else
      stack.push(parts[i]);
  }
  return stack.join("/");
}
Stanislav
  • 4,389
  • 2
  • 33
  • 35
  • I added `if (charAt(0) !== ".") return relative;` to handle the case where `relative` is already an absolute path, so I don't have to check if it's relative first, before calling this function. – mindplay.dk Dec 08 '16 at 12:21
0

This will work. but only when you open a page with it's file name. it will not work well when you open a link like this stackoverflow.com/page. it will work with stackoverflow.com/page/index.php

function reltoabs(link){
    let absLink = location.href.split("/");
    let relLink = link;
    let slashesNum = link.match(/[.]{2}\//g) ? link.match(/[.]{2}\//g).length : 0;
    for(let i = 0; i < slashesNum + 1; i++){
        relLink = relLink.replace("../", "");
        absLink.pop();
    }
    absLink = absLink.join("/");
    absLink += "/" + relLink;
    return absLink;
}
Mohamed hesham
  • 130
  • 1
  • 12
-1

I found a very simple solution to do this while still supporting IE 10 (IE doesn't support the URL-API) by using the History API (IE 10 or higher). This solution works without any string manipulation.

function resolveUrl(relativePath) {
    var originalUrl = document.location.href;
    history.replaceState(history.state, '', relativePath);
    var resolvedUrl = document.location.href;
    history.replaceState(history.state, '', originalUrl);
    return resolvedUrl;
}

history.replaceState() won't trigger browser navigation, but will still modify document.location and supports relative aswell as absolute paths.

The one drawback of this solution is that if you are already using the History-API and have set a custom state with a title, the current state's title is lost.

whY
  • 189
  • 1
  • 10
-1

Try:

/**
 * Convert relative paths to absolute paths
 * @author HaNdTriX
 * @param {string} html - HTML string
 * @param {string} baseUrl - base url to prepend to relative paths
 * @param  {string[]} [attributes] - attributes to convert
 * @returns {string}
 */
function absolutify(
  html,
  baseUrl,
  attributes = [
    "href",
    "src",
    "srcset",
    "cite",
    "background",
    "action",
    "formaction",
    "icon",
    "manifest",
    "code",
    "codebase",
  ]
) {
  // Build the regex to match the attributes.
  const regExp = new RegExp(
    `(?<attribute>${attributes.join(
      "|"
    )})=(?<quote>['"])(?<path>.*?)\\k<quote>`,
    "gi"
  );

  return html.replaceAll(regExp, (...args) => {
    // Get the matched groupes
    const { attribute, quote, path } = args[args.length - 1];

    // srcset may have multiple paths `<url> <descriptor>, <url> <descriptor>`
    if (attribute.toLowerCase() === "srcset") {
      const srcSetParts = path.split(",").map((dirtyPart) => {
        const part = dirtyPart.trim();
        const [path, size] = part.split(" ");
        return `${new URL(path.trim(), baseUrl).toString()} ${size || ""}`;
      });

      return `${attribute}=${quote}${srcSetParts.join(", ")}${quote}`;
    }

    const absoluteURL = new URL(path, baseUrl).href;
    return `${attribute}=${quote}${absoluteURL}${quote}`;
  });
}

console.log(
  absolutify("<img src='./fooo.png'>", "https://example.com")
)
HaNdTriX
  • 28,732
  • 11
  • 78
  • 85