Can I use require("path").join to safely concatenate urls?

node.jsUrlString Concatenation

node.js Problem Overview


Is this safe to use require("path").join to concatenate URLs, for example:

require("path").join("http://example.com", "ok"); 
//returns 'http://example.com/ok'

require("path").join("http://example.com/", "ok"); 
//returns 'http://example.com/ok'

If not, what way would you suggest for doing this without writing code full of ifs?

node.js Solutions


Solution 1 - node.js

No. path.join() will return incorrect values when used with URLs.

It sounds like you want new URL(). From the WHATWG URL Standard:

new URL('/one', 'http://example.com/').href    // 'http://example.com/one'
new URL('/two', 'http://example.com/one').href // 'http://example.com/two'

Note that url.resolve is now marked as deprecated in the Node docs.

As Andreas correctly points out in a comment, url.resolve (also deprecated) would only help if the problem is as simple as the example. url.parse also applies to this question because it returns consistently and predictably formatted fields via the URL object that reduces the need for "code full of ifs". However, new URL() is also the replacement for url.parse.

Solution 2 - node.js

No, you should not use path.join() to join URL elements.

There's a package for doing that now. So rather than reinvent the wheel, write all your own tests, find bugs, fix them, write more tests, find an edge case where it doesn't work, etc., you could use this package.

url-join

https://github.com/jfromaniello/url-join

Install

npm install url-join

Usage

var urljoin = require('url-join');

var fullUrl = urljoin('http://www.google.com', 'a', '/b/cd', '?foo=123');

console.log(fullUrl);

Prints:

'http://www.google.com/a/b/cd?foo=123';

Solution 3 - node.js

This can be accomplished by a combination of Node's path and URL:

  1. Require the packages:
const nodeUrl = require('url')
const nodePath = require('path')
  1. Start by making a URL object to work with:
> const myUrl = new nodeUrl.URL('https://example.com')
  1. Use pathname= and path.join to construct any possible combination:
> myUrl.pathname = nodePath.join('/search', 'for', '/something/')
'/search/for/something/'

(you can see how liberal path.join is with arguments)

  1. At this point your URL reflects the ultimate desired result:
> myUrl.toString()
'https://example.com/search/for/something/'

Why this approach?

This technique uses built-in libraries. The less third-party dependencies the better, when it comes to CVEs, maintenance, etc.

Nothing will be more proven or better tested than standard libs.

PS: Never manipulate URLs as strings!

When I review code I'm adamant about never manipulating URLs as strings manually. For one, look how complicated the spec is.

Secondly, the absence/presence of a trailing/prefixed slash (/) should not cause everything to break! You should never do:

const url = `${baseUrl}/${somePath}`

and especially not:

uri: host + '/' + SAT_SERVICE + '/' + CONSTELLATION + '/',

Of which I have seen.

Solution 4 - node.js

Axios has a helper function that can combine URLs.

function combineURLs(baseURL, relativeURL) {
  return relativeURL
    ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '')
    : baseURL;
}

> Source: > https://github.com/axios/axios/blob/fe7d09bb08fa1c0e414956b7fc760c80459b0a43/lib/helpers/combineURLs.js

Solution 5 - node.js

The WHATWG URL object constructor has a (input, base) version, and the input can be relative using /, ./, ../. Combine this with path.posix.join and you can do anything:

const {posix} = require ("path");
const withSlash = new URL("https://example.com:8443/something/");
new URL(posix.join("a", "b", "c"), withSlash).toString(); // 'https://example.com:8443/something/a/b/c'
new URL(posix.join("./a", "b", "c"), withSlash).toString(); // 'https://example.com:8443/something/a/b/c'
new URL(posix.join("/a", "b", "c"), withSlash).toString(); // 'https://example.com:8443/a/b/c'
new URL(posix.join("../a", "b", "c"), withSlash).toString(); // 'https://example.com:8443/a/b/c'
const noSlash = new URL("https://example.com:8443/something");
new URL(posix.join("./a", "b", "c"), noSlash).toString(); // 'https://example.com:8443/a/b/c'

Solution 6 - node.js

When I tried PATH for concatenating url parts I run into problems. PATH.join stripes '//' down to '/' and this way invalidates an absolute url (eg. http://... -> http:/...). For me a quick fix was:

baseurl.replace(/\/$/,"") + '/' + path.replace(/^\//,"") )

or with the solution posted by Colonel Panic:

[pathA.replace(/^\/|\/$/g,""),pathB.replace(/^\/|\/$/g,"")].join("/")

Solution 7 - node.js

No! On Windows path.join will join with backslashes. HTTP urls are always forward slashes.

How about

> ["posts", "2013"].join("/")
'posts/2013'

Solution 8 - node.js

We do it like this:

var _ = require('lodash');

function urlJoin(a, b) {
  return _.trimEnd(a, '/') + '/' + _.trimStart(b, '/');
}

Solution 9 - node.js

If you're using lodash, you can use this simple oneliner:

// returns part1/part2/part3
['part1/', '/part2', '/part3/'].map((s) => _.trim(s, '/')).join('/')

inspired by @Peter Dotchev's answer

Solution 10 - node.js

If you use Angular, you can use Location:

import { Location } from '@angular/common';
// ...
Location.joinWithSlash('beginning', 'end');

Works only on 2 arguments though, so you have to chain calls or write a helper function to do that if needed.

Solution 11 - node.js

This is what I use:

function joinUrlElements() {
  var re1 = new RegExp('^\\/|\\/$','g'),
      elts = Array.prototype.slice.call(arguments);
  return elts.map(function(element){return element.replace(re1,""); }).join('/');
}

example:

url = joinUrlElements(config.mgmtServer, '/v1/o/', config.org, '/apps');

Solution 12 - node.js

There are other working answers, but I went with the following. A little path.join/URL combo.

const path = require('path');
//
const baseUrl = 'http://ejemplo.mx';
// making odd shaped path pieces to see how they're handled.
const pieces = ['way//', '//over/', 'there/'];
//
console.log(new URL(path.join(...pieces), baseUrl).href);
// http://ejemplo.mx/way/over/there/

// path.join expects strings. Just an example how to ensure your pieces are Strings.
const allString = ['down', 'yonder', 20000].map(String);
console.log(new URL(path.join(...allString), baseUrl).href);
// http://ejemplo.mx/down/yonder/20000

Solution 13 - node.js

By the time posting this answer url.resolve() is deprecated;

I did following to join to path in Nodejs:

const path = require('path');
const url = require('url');


let myUrl = new URL('http://ignore.com');
myUrl.pathname=path.join(firstpath, secondpath);
console.log(myUrl.pathname)

This approach logs correct url path and it works for my case.

What is your opinion about this approach?

Thanks

Solution 14 - node.js

Typescript custom solution:

export function pathJoin(parts: string[], sep: string) {
  return parts
    .map(part => {
      const part2 = part.endsWith(sep) ? part.substring(0, part.length - 1) : part;
      return part2.startsWith(sep) ? part2.substr(1) : part2;
    })
    .join(sep);
}

expect(pathJoin(['a', 'b', 'c', 'd'], '/')).toEqual('a/b/c/d');
expect(pathJoin(['a/', '/b/', 'c/', 'd'], '/')).toEqual('a/b/c/d');
expect(pathJoin(['http://abc.de', 'users/login'], '/')).toEqual('http://abc.de/users/login');

Solution 15 - node.js

My solution

path.join(SERVER_URL, imageAbsolutePath).replace(':/','://');

Edit: if you want to support windows enviroments

path.join(SERVER_URL, imageAbsolutePath).replace(/\\/g,'/').replace(':/','://');

The second solution will replace all the backslashes, so url parts like querystring and hash may be altered too, but the topic is joining just the url path, so I don't consider it an issue.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionRenato GamaView Question on Stackoverflow
Solution 1 - node.jsMatthew BakaitisView Answer on Stackoverflow
Solution 2 - node.jsstoneView Answer on Stackoverflow
Solution 3 - node.jsSean Patrick MurphyView Answer on Stackoverflow
Solution 4 - node.jsIkbelView Answer on Stackoverflow
Solution 5 - node.jsCodererView Answer on Stackoverflow
Solution 6 - node.jsPeterView Answer on Stackoverflow
Solution 7 - node.jsColonel PanicView Answer on Stackoverflow
Solution 8 - node.jsPeter DotchevView Answer on Stackoverflow
Solution 9 - node.jsM KView Answer on Stackoverflow
Solution 10 - node.jsQortexView Answer on Stackoverflow
Solution 11 - node.jsCheesoView Answer on Stackoverflow
Solution 12 - node.jsNeil Gaetano LindbergView Answer on Stackoverflow
Solution 13 - node.jsTornike ShavishviliView Answer on Stackoverflow
Solution 14 - node.jsPatrick WozniakView Answer on Stackoverflow
Solution 15 - node.jsKillyView Answer on Stackoverflow