Posso usar join ("caminho") para concatenar URLs com segurança?

128

É seguro usar require("path").joinpara concatenar URLs, por exemplo:

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

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

Caso contrário, de que maneira você sugeriria isso sem escrever um código cheio de ifs?

Renato Gama
fonte
3
Veja também github.com/joyent/node/issues/2216
Coronel Pânico
5
No caso de alguém quiser usar path.join, mas questões Evitar no Windows: path.posix.join('/one/two/three', 'four') // '/one/two/three/four, path.posix.join('/one/two/three/', 'four') // '/one/two/three/four,path.posix.join('/one/two/three/', '/four') // '/one/two/three/four
Timothy Zorn
5
@TimothyZorn O problema é que se você fizer algo parecido com isso path.posix.join('http://localhost:9887/one/two/three/', '/four'), a junção se livrar de uma das barras duplas nahttp://
Max Alexander
Ahh, sim - bom argumento. Nesses cenários, você iria querer fazer algo assim 'http://localhost:9887/one/two/three/'.replace(/^\/+|\/+$/, '') + '/' + '/four'.replace(/^\/+|\/+$/, '')e você poderia fazer String.prototype.trimSlashes = function() { return this.replace(/^\/+|\/+$/, ''); }se você não deseja digitar a expressão regular e outra vez. stackoverflow.com/a/22387870/2537258
Timothy Zorn
ou['http://localhost:9887/one/two/three/', '/four'].map((part) => part. replace(/^\/+|\/+$/, '')).join('/')
Timothy Zorn

Respostas:

141

No. path.join()retornará valores incorretos quando usado com URLs.

Parece que você quer url.resolve. Nos documentos do Node :

url.resolve('/one/two/three', 'four')         // '/one/two/four'
url.resolve('http://example.com/', '/one')    // 'http://example.com/one'
url.resolve('http://example.com/one', '/two') // 'http://example.com/two'

Editar: como Andreas corretamente indica em um comentário, url.resolvesó ajudaria se o problema fosse tão simples quanto o exemplo. url.parsetambém se aplica a essa pergunta porque retorna campos formatados de forma consistente e previsível por meio do URLobjeto que reduz a necessidade de "código cheio de ifs".

Matthew Bakaitis
fonte
1
Embora não seja exatamente o que eu estava procurando, isso também resolve meu problema. Obrigado por ajudar!
Renato Gama #
6
@AndreasHultgren o primeiro comentário está correto. Se o exemplo fosse url.resolve('/one/two/three/', 'four'), a saída seria 'one/two/three/four'.
tavnab
3
No caso de alguém quiser usar path.join, mas questões Evitar no Windows: path.posix.join('/one/two/three', 'four') // '/one/two/three/four, path.posix.join('/one/two/three/', 'four') // '/one/two/three/four,path.posix.join('/one/two/three/', '/four') // '/one/two/three/four
Timothy Zorn
2
Os comentários estão incorretos, url.resolve('/one/two/three', 'four') // '/one/two/four'a resposta está correta
Jonathan.
2
Lembre-se também de usar url.resolve()apenas 2 argumentos, enquanto que path.join()qualquer número. Então, dependendo do que você está fazendo você pode precisar de chamadas ninho, por exemplo ..url.resolve(url.resolve(SERVER_URL, pagePath), queryString)
Molomby
47

Não, você não deve usar path.join()para associar elementos de URL.

Há um pacote para fazer isso agora. Então, em vez de reinventar a roda, escreva todos os seus próprios testes, encontre bugs, corrija-os, escreva mais testes, encontre um caso de borda onde não funcione etc., você pode usar este pacote.

junção de URL

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

Instalar

npm install url-join

Uso

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

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

console.log(fullUrl);

Impressões:

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

pedra
fonte
1
Este. Isto é fantástico. Obrigado.
Dudewad
6

Quando tentei o PATH para concatenar partes de URL, me deparei com problemas. PATH.joinlistras '//' até '/' e, dessa forma, invalida um URL absoluto (por exemplo, http: // ... -> http: / ...). Para mim, uma solução rápida foi:

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

ou com a solução postada pelo coronel Panic:

[pathA.replace(/^\/|\/$/g,""),pathB.replace(/^\/|\/$/g,"")].join("/")
Peter
fonte
E se eu estiver tentando criar um URL relativo à raiz como este /assets/foo:? Isso resultará em URL relativo ao caminho atual assets/foo.
Andrey Mikhaylov - lolmaus
5

Não! No Windows path.joinse juntará às barras invertidas. URLs HTTP sempre são barras.

E se

> ["posts", "2013"].join("/")
'posts/2013'
Coronel Panic
fonte
Boa ideia, mas e se o primeiro argumento já tiver uma barra no final? por exemplo ["posts/", "2013"].join("/"):?
Renato Gama
1
@RenatoGama, posts//2013ainda é um URL válido.
Goodwine
2
^ que não funcionará em todos os domínios, mesmo que seja um URI válido.
BingeBoy
2
Especificamente, o Node's Express não ignora barras estranhas para roteamento.
Perseids
1
No caso de alguém quiser usar path.join, mas questões Evitar no Windows: path.posix.join('/one/two/three', 'four') // '/one/two/three/four, path.posix.join('/one/two/three/', 'four') // '/one/two/three/four,path.posix.join('/one/two/three/', '/four') // '/one/two/three/four
Timothy Zorn
5

Fazemos assim:

var _ = require('lodash');

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

Se você estiver usando o lodash , poderá usar este oneliner simples:

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

inspirado na resposta de @Peter Dotchev

MK
fonte
3

Isto é o que eu uso:

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

exemplo:

url = joinUrlElements(config.mgmtServer, '/v1/o/', config.org, '/apps');
Cheeso
fonte
E se eu estiver tentando criar um URL relativo à raiz como este /assets/foo:? Isso resultará em URL relativo ao caminho atual assets/foo.
Andrey Mikhaylov - lolmaus
1
acrescentar uma barra? Quero dizer, é uma verificação simples; você pode adicioná-lo você mesmo.
Cheeso
3
É assim que tudo começa ... depois, você sabe que gastou mais de 8 horas encontrando casos extremos que não funcionam e corrigindo-os ao longo de seu projeto.
stone
3

Se você usa Angular, pode usar Local :

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

Funciona apenas com 2 argumentos, portanto, você deve encadear chamadas ou escrever uma função auxiliar para fazer isso, se necessário.

Qortex
fonte
2

O objeto WHATWG URL construtor tem uma (input, base)versão eo inputpode ser relativo usando /, ./, ../. Combine isso com path.posix.joine você pode fazer qualquer coisa:

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'
Coderer
fonte
0

Solução personalizada datilografada:

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');
Patrick Wozniak
fonte
0

Minha solução

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

Editar: se você deseja oferecer suporte ao ambiente do Windows

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

A segunda solução substituirá todas as barras invertidas; portanto, partes de URL, como querystring e hash, também podem ser alteradas, mas o tópico está se unindo apenas ao caminho da URL, por isso não considero um problema.

Killy
fonte
0

Existem outras respostas úteis, mas eu fui com o seguinte. Uma pequena combinação path.join / URL.

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
Neil Guy Lindberg
fonte
0

Isso pode ser realizado por uma combinação do caminho e da URL do nó :

  1. Exija os pacotes:
const nodeUrl = require('url')
const nodePath = require('path')
  1. Comece criando um objeto de URL para trabalhar:
> const myUrl = new nodeUrl.URL('https://example.com')
  1. Use pathname=e path.joinpara construir qualquer combinação possível:
> myUrl.pathname = nodePath.join('/search', 'for', '/something/')
'/search/for/something/'

(você pode ver o quão liberal path.joiné com argumentos)

  1. Nesse ponto, seu URL reflete o resultado final desejado:
> myUrl.toString()
'https://example.com/search/for/something/'

Por que essa abordagem?

Essa técnica usa bibliotecas internas. Quanto menos dependências de terceiros, melhor, quando se trata de CVEs, manutenção etc.

PS: nunca manipule URLs como strings!

Ao revisar o código, sou inflexível sobre nunca manipular URLs como sequências manualmente . Por um lado, veja como a especificação é complicada .

Em segundo lugar, a ausência / presença de uma barra final / prefixada ( /) não deve fazer com que tudo quebre! Você nunca deve fazer:

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

e especialmente não:

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

Dos quais eu acabei de encontrar em uma base de código.

Sean Patrick Murphy
fonte