Leitura e gravação de URL mais seguras em JavaScript moderno
Tags: webdev, javascript, node, braziliandevs
Published: 2023-01-25 09:20:56

> Você pode, sem saber, estar escrevendo URLs de maneira insegura

Você consegue identificar o bug neste código?

```javascript
const url = `https://builder.io/api/v2/content
  ?model=${model}&locale=${locale}?query.text=${text}`

const res = await fetch(url)
```

Há _pelo menos_ três!

Vamos desconstrui-los a seguir:

## Número 1: Caracteres separadores incorretos

![](https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2Fc6854e9c731345b49a5d57aa49cf9d66)
Ops! Este é certamente um erro de novato, mas tão fácil de passar batido que percebi isso em meu próprio código, mesmo após 10 anos de desenvolvimento de JS.

Um culpado comum disso, em minha experiência, é depois de editar ou mover o código. Por exemplo, você tem uma URL estruturada corretamente, depois copia uma parte de uma para outra e, em seguida, não percebe que o separador de parâmetros foi ordenado incorretamente.

Isso também pode acontecer durante a concatenação. Por exemplo:

```javascript
url = url + '?foo=bar'
```

Mas espere, o original `url` pode ter um parâmetro de consulta nele. Ok, então isso deve ser:

```javascript
url = url + '&foo=bar'
```

Mas espere, se o original `url` _não_ tiver parâmetros de consulta, isso agora está errado. Argh.

## Número 2: Esquecer de encodar a URL

![](https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F316c6281fdf046d79a99498cd031a5e1)
Gah. `model` e `locale` provavelmente não precisam ser encodados, pois são valores seguros para URL, mas não parei para pensar que `text` pode ser todo tipo de texto, incluindo espaços em branco e caracteres especiais, o que nos causará problemas.

Então, talvez possamos corrigir até demais e jogar com mais segurança:

```javascript
const url = `https://builder.io/api/v2/content
  ?model=${
    encodeURIComponent(model)
  }&locale=${
    encodeURIComponent(locale)
  }&query.text=${
    encodeURIComponent(text)
  }`
```

Mas as coisas estão um pouco... feias.

## Número 3: Caracteres de espaço em branco acidentais

![](https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F8aebf9b5f0e14784a21a1e712f986efa)
Rapaz. Para dividir essa URL longa em várias linhas, incluímos acidentalmente o caractere de nova linha e espaços extras nela, o que fará com que a busca não funcione mais como esperado.

Podemos quebrar a string corretamente agora, mas está ficando ainda mais confuso e difícil de ler:

```javascript
const url = `https://builder.io/api/v2/content`
  + `?model=${
    encodeURIComponent(model)
  }&locale=${
    encodeURIComponent(locale)
  }&query.text=${
    encodeURIComponent(text)
  }`
```

Isso tudo apenas para construir uma URL correta. E vamos nos lembrar de tudo isso da próxima vez, especialmente porque o prazo está se aproximando rapidamente e precisamos lançar esse novo recurso ou consertar o mais rápido possível?

Tem que haver uma maneira melhor.

![](https://cdn.builder.io/o/assets%2FYJIGb4i01jvw0SRdL5Bt%2F581b6ab4fe124a5bb6793f3d55e72b9f%2Fcompressed?apiKey=YJIGb4i01jvw0SRdL5Bt&token=581b6ab4fe124a5bb6793f3d55e72b9f&alt=media)
## O construtor `URL` para o resgate

Uma solução mais limpa e segura para o desafio acima é usar o [construtor de URL](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL):

```javascript
const url = new URL('https://builder.io/api/v2/content')

url.searchParams.set('model', model)
url.searchParams.set('locale', locale)
url.searchParams.set('text', text)
  
const res = await fetch(url.toString())
```

Isso resolve várias coisas para nós:

-   Os caracteres separadores estão sempre corretos ( `?` para o primeiro parâmetro e depois).
-   Todos os parâmetros são codificados automaticamente.
-   Sem risco de caracteres de espaço em branco adicionais ao quebrar várias linhas para URLs longos.

## Modificando URLs

Também é incrivelmente útil para situações em que estamos modificando uma URL, mas não sabemos o estado atual.

Por exemplo, em vez de ter este problema:

```javascript
url += (url.includes('?') ? '&' : '?') + 'foo=bar'
```

Podemos fazer:

```javascript
// Assumindo que `url` é uma URL
url.searchParams.set('foo', 'bar')

// Ou se URL for uma string
const structuredUrl = new URL(url)
structuredUrl.searchParams.set('foo', 'bar')
url = structuredUrl.toString()
```

Da mesma forma, você também pode escrever outras partes da URL:

```javascript
const url = new URL('https://builder.io')

url.pathname = '/blog'      // Update the path
url.hash = '#featured'      // Update the hash
url.host = 'www.builder.io' // Update the host

url.toString()              // https://www.builder.io/blog#featured
```

## Lendo valores de uma URL

Agora, o antigo problema de “eu só quero ler um parâmetro de consulta da URL atual sem uma biblioteca”, foi resolvido:

```javascript
const pageParam = new URL(location.href).searchParams.get('page')
```

Ou, por exemplo, atualizar a URL atual com:

```javascript
const url = new URL(location.href)
const currentPage = Number(url.searchParams.get('page'))
url.searchParams.set('page', String(currentPage + 1))
location.href = url.toString()
```

Mas isso não se limita apenas ao navegador. Também pode ser usado em Node.js

```javascript
const http = require('node:http');

const server = http.createServer((req, res) => {
  const url = new URL(req.url, `https://${req.headers.host}`)
  // Leia o caminho, query, etc...
});
```

Assim como Deno:

```javascript
import { serve } from "https://deno.land/std/http/mod.ts";
async function reqHandler(req: Request) {
  const url = new URL(req.url)
  // Leia o caminho, query, etc...
  return new Response();
}
serve(reqHandler, { port: 8000 });
```

## Propriedades para saber da URL

As instâncias de URL suportam todas as propriedades com as quais você já está acostumado no navegador, como `window.location` ou elementos âncora, todos os quais você pode ler _e_ escrever:

```javascript
const url = new URL('https://builder.io/blog?page=1');

url.protocol // https:
url.host     // builder.io
url.pathname // /blog
url.search   // ?page=1
url.href     // https://builder.io/blog?page=1
url.origin   // https://builder.io
url.searchParams.get('page') // 1
```

Ou, de relance:

![](https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F09be949f4a4843c7abde1b25c0c2636c)
## Métodos URLSearchParams para conhecer

O objeto `URLSearchParams`, acessível em uma instância `URL`, `url.searchParams` suporta vários métodos úteis.

### `searchParams.has(name)`

Verifique se os parâmetros de pesquisa contêm um determinado nome:

```javascript
url.searchParams.has('page') // true
```

### `searchParams.get(name)`

Obtenha o valor de um determinado parâmetro:

```javascript
url.searchParams.get('page') // '1'
```

### `searchParams.getAll(name)`

Obtenha todos os valores fornecidos para um parâmetro. Isso é útil se você permitir vários valores com o mesmo nome, como `&page=1&page=2`:

```javascript
url.searchParams.getAll('page') // ['1']
```

### `searchParams.set(name, value)`

Defina o valor de um parâmetro:

```javascript
url.searchParams.set('page', '1')
```

### `searchParams.append(name, value)`

Anexa um parâmetro — útil se você potencialmente oferecer suporte ao mesmo parâmetro várias vezes, como `&page=1&page=2`:

```javascript
url.searchParams.append('page', '2')
```

### `searchParams.delete(name)`

Remova totalmente um parâmetro da URL:

```javascript
url.searchParams.delete('page')
```

## Armadilhas

A única grande armadilha a saber é que todas as URLs passadas para o construtor de URL devem ser absolutas.

Por exemplo, isso lançará um erro:

```javascript
new URL('/blog') // ERROR!
```

Você pode resolver isso, fornecendo uma origem como o segundo argumento, assim:

```javascript
new URL('/blog', 'https://builder.io')
```

Ou, se você realmente precisa trabalhar apenas com partes da URL, como alternativa, use [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) diretamente:

```javascript
const params = new URLSearchParams('page=1')
params.set('page=2')
params.toString()
```

`URLSearchParams` também tem uma outra sutileza, que é que ele também pode receber um objeto de pares de valores-chave como entrada:

```javascript
const params = new URLSearchParams({
  page: 1,
  text: 'foobar',
})
params.set('page=2')
params.toString()
```

## Suporte a navegador e tempo de execução

`new URL` é suportado em todos os navegadores modernos, bem como Node.js e Deno! ( [fonte](https://developer.mozilla.org/en-US/docs/Web/API/URL/URL)).

![](https://cdn.builder.io/api/v1/image/assets%2FYJIGb4i01jvw0SRdL5Bt%2F4ed6b279ae1d4f8aa4a790fe5484517c)
# Créditos
- Escrito originalmente por [Steve Sewell](https://twitter.com/steve8708) em [Safer URL reading and writing in modern JavaScript](https://www.builder.io/blog/new-url).