8

现代 JavaScript 中更安全的 URL 读写

 1 year ago
source link: https://blog.p2hp.com/archives/10257
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

您可能在不知不觉中以不安全的方式编写 URL

你能发现这段代码中的错误吗?

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

const res = await fetch(url)

至少有三个!

我们将在下面分解它们:

常见问题 #1:不正确的分隔符

带有额外 `?` 的 URL 字符串

哎呀!这当然是一个新手错误,但很容易错过,即使经过 10 年的 JS 开发,我也在我自己的代码中发现了这个错误。

根据我的经验,一个常见的罪魁祸首是在编辑或移动代码之后。例如,您有一个结构正确的 URL,然后将一个片段从一个片段复制到另一个片段,然后错过了参数分隔符的错误排序。

连接时也会发生这种情况。例如:

url = url   '?foo=bar'

但是等等,原来的url可能有一个查询参数。好的,所以这应该是:

url = url   '&foo=bar'

但是等等,如果原来url 没有查询参数那么现在这是错误的。啊。

常见问题 #2:忘记编码

带有参数但未编码的 URL 字符串

啊。model并且locale可能不需要编码,因为它们是 URL 安全值,但我并没有停下来思考text可以是所有类型的文本,包括空格和特殊字符,这会给我们带来问题。

因此,也许我们会矫枉过正,让事情变得更加安全:

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

但事情感觉有点......丑陋。

常见问题 #3:意外的空白字符

带有意外空白字符的 URL 字符串

钱币。为了将这个长 URL 分成多行,我们不小心在 URL 中包含了换行符和额外的空格,这将导致无法按预期进行抓取。

我们现在可以正确地分解字符串,但我们变得更加混乱和难以阅读:

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

仅仅为了使构建一个 URL 正确就需要很多。下一次我们是否会记住这一切,尤其是在截止日期即将到来且我们需要尽快发布新功能或修复时?

一定有更好的方法。

乔伊的动图,朋友们说“一定有更好的方法!”

URL营救的构造函数

解决上述挑战的更清洁、更安全的解决方案是使用URL 构造函数

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())

这为我们解决了几件事:

  • 分隔符总是正确的(?对于第一个参数,以及之后的参数)。
  • 所有参数都自动编码。
  • 为长 URL 跨多行时没有额外空白字符的风险。

对于我们正在修改 URL 但不知道当前状态的情况,它也非常有用。

例如,而不是有这个问题:

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

我们可以改为:

// Assuming `url` is a URL
url.searchParams.set('foo', 'bar')

// Or if URL is a string
const structuredUrl = new URL(url)
structuredUrl.searchParams.set('foo', 'bar')
url = structuredUrl.toString()

同样,你也可以写URL的其他部分:

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

读取 URL 值

现在,“我只想在没有库的情况下从当前 URL 读取查询参数”这个由来已久的问题得到了解决。

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

或者例如更新当前 URL:

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

但这不仅限于浏览器。它也可以在 Node.js 中使用

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

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

以及 Deno:

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

要知道的 URL 属性

URL 实例支持您已经在浏览器中使用的所有属性,例如 onwindow.location或 anchor 元素,所有这些您都可以读写

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

或者,一目了然:

URL 和指向每个段的箭头的图表,例如“主机名”与“哈希”等≠的位置。

URLSearchParams方法要知道

URLSearchParams对象可在URL实例上访问,url.searchParams支持许多方便的方法:

searchParams.has(name)

检查搜索参数是否包含给定名称:

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

searchParams.get(name)

获取给定参数的值:

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

searchParams.getAll(name)

获取为参数提供的所有值。如果您允许同名的多个值,这很方便,例如&page=1&page=2

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

searchParams.set(name, value)

设置参数的值:

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

searchParams.append(name, value)

附加一个参数——如果你可能多次支持同一个参数,这很有用,比如&page=1&page=2

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

searchParams.delete(name)

从 URL 中完全删除一个参数:

url.searchParams.delete('page')

要知道的一大陷阱是传递给 URL 构造函数的所有 URL 都必须是绝对的。

例如,这将引发错误:

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

您可以通过提供原点作为第二个参数来解决这个问题,如下所示:

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

或者,如果您真的只需要使用 URL 部分,  如果您只需要使用相对 URL 的查询参数,则可以直接使用URLSearchParams :

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

URLSearchParams 还有一个优点,那就是它也可以将键值对的对象作为其输入:

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

浏览器和运行时支持

new URL支持所有现代浏览器,以及 Node.js 和 Deno!(来源

浏览器支持表 - 您可以在上面的“源”链接中找到它。

使用您的组件进行可视化构建

Builder.io是一个无头 CMS,可让您直接 在现有站点拖放 组件。

// Dynamically render your components
export function MyPage({ json }) {
  return 
}

registerComponents([MyHero, MyProducts])

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK