安全

H3 安全工具。

认证

basicAuth(opts)

创建一个基本认证中间件。

示例:

import { H3, serve, basicAuth } from "h3";
const auth = basicAuth({ password: "test" });
app.get("/", (event) => `Hello ${event.context.basicAuth?.username}!`, [auth]);
serve(app, { port: 3000 });

requireBasicAuth(event, opts)

为当前请求应用基本认证。

示例:

import { defineHandler, requireBasicAuth } from "h3";
export default defineHandler(async (event) => {
  await requireBasicAuth(event, { password: "test" });
  return `Hello, ${event.context.basicAuth.username}!`;
});

会话

clearSession(event, config)

清除当前请求的会话数据。

getSession(event, config)

获取当前请求的会话。

sealSession(event, config)

加密并签名当前请求的会话数据。

unsealSession(_event, config, sealed)

解密并验证当前请求的会话数据。

updateSession(event, config, update?)

更新当前请求的会话数据。

useSession(event, config)

为当前请求创建一个会话管理器。

指纹

getRequestFingerprint(event, opts)

获取传入请求的唯一指纹。

跨域资源共享(CORS)

appendCorsHeaders(event, options)

向响应中添加 CORS 头。

appendCorsPreflightHeaders(event, options)

向响应中添加 CORS 预检请求头。

handleCors(event, options)

处理传入请求的 CORS。

如果传入请求是 CORS 预检请求,将添加 CORS 预检请求头并发送 204 响应。

如果返回值不是 false,表示请求已被处理,无需进一步操作。

示例:

const app = new H3();
app.all("/", async (event) => {
  const corsRes = handleCors(event, {
    origin: "*",
    preflight: {
      statusCode: 204,
    },
    methods: "*",
  });
  if (corsRes !== false) {
    return corsRes;
  }
  // 你的代码在此
});

isCorsOriginAllowed(origin, options)

检查来源是否被允许。

isPreflightRequest(event)

检查传入请求是否为 CORS 预检请求。

路径

isCanonicalPath(path, opts?)

判断 pathopts 下是否已经是规范形式——也就是说,{@link resolveDotSegments} 对其解析后会原样返回。两个方向都完全一致:当且仅当 resolveDotSegments(path, opts) === path 时返回 true

这是解析器自身的快速路径守卫。将其导出,是为了让在热路径上进行规范化的调用方(例如每请求作用域或规则匹配)可以跳过该调用,以及由此产生的所有工作,而无需自行维护一份解析器解码逻辑的副本。这样的副本会在不知不觉中过时,而在作用域检查中遗漏规范化属于绕过问题,而不是性能问题。

传入与后续 {@link resolveDotSegments} 调用相同的选项,或更严格的选项:decodeSlashes/mergeSlashes 只会增加触发条件,因此在两者都启用时得到的 true,意味着在所有模式下都为 true。在一种模式下检查,却在另一种模式下解析,会使这一保证失效。

接收一个不带其他部分的路径名。与解析器一样,它不识别查询字符串或哈希,而是将它们当作路径进行扫描,因此 /a?next=/../b 会被报告为非规范形式(并且会解析为 /b)。

resolveDotSegments(path, opts?)

解析路径中的 ... 段,并且绝不会超出根目录 /。结果始终是一个带单个前导 / 的绝对路径,因此它不可能是协议相对形式(//host)。

同时还会在任意 %25 嵌套深度下解码百分号编码的点段(%2e%252e、...),并将 \ 规范化为 /,因此编码形式或基于反斜杠的路径穿越(例如 %2e%2e/..\..\)会与字面量 ../ 一样被捕获。

默认情况下,%2f/%5c(编码的路径分隔符)会保持不变——参见 {@link ResolveDotSegmentsOptions.decodeSlashes}。

只有 ./.. 解析以及上述解码操作会改变字符串;其他所有百分号编码(%20、非 ASCII 字符、%3A,以及任何不构成完整段的 %2e)都会保持不变,因此结果会与 event.url.pathname 保持相同的表示形式,并能一致地匹配路由和规则。末尾的 ./.. 会解析为目录,并保留其末尾斜杠(/a/b/.. -> /a//a/. -> /a/),这符合 RFC 3986 §5.2.4,也与 WHATWG/nginx 下游的解析结果一致——因此作用域检查看到的是目录形式,而不是与其对应的文件形式。内部的空段会被保留(/a//b 仍为 /a//b)——与 WHATWG 一样,这里不会合并斜杠,因此空段会保留,而不会被折叠。唯一的例外是开头连续出现的斜杠:它始终会被限制为单个 /(WHATWG 会保留 //host),因此只有开头的斜杠能够保证是单个斜杠,而执行精确前缀匹配的使用方应当以相同方式规范化其允许列表。要同时折叠内部连续的斜杠(即采用合并斜杠的下游解析方式),请参见 {@link ResolveDotSegmentsOptions.mergeSlashes}。

路由参数

路由参数会以其在 URL 路径中的形式传递给处理程序——即百分号编码形式。getRouterParams(event, { decode: true })(以及使用相同选项的 getValidatedRouterParams)会执行一次解码,而不是完整的规范化:

  • 编码的路径分隔符(%2f%5c,以及任意 %25 嵌套深度下的形式:%252f%25252f、……)永远不会被解码。被路由器匹配为单个段的参数中不可能出现原始 /\,因此参数不会悄然获得路由和中间件从未看到的路径边界。
  • 其他所有转义序列都会精确解码一层。由于 %25 本身就是一个转义序列,%25XX 会解码为字面文本 %XX——因此结果中仍可能包含百分号转义序列。
app.get("/files/**:rest", (event) => {
  // GET /files/%252e%252e/x
  getRouterParams(event); // { rest: "%252e%252e/x" }
  getRouterParams(event, { decode: true }); // { rest: "%2e%2e/x" }

  // GET /files/%2500
  getRouterParams(event, { decode: true }); // { rest: "%00" }

  // GET /files/a%252fb  — separators stay encoded at every depth
  getRouterParams(event, { decode: true }); // { rest: "a%252fb" }
});
不要再次解码返回的值。第二次调用 decodeURIComponent 会将 %2e%2e/x 转换为 ../x,并将 %00 转换为 NUL 字节——这些路径穿越和控制字符对路由或任何基于路径名的中间件而言都是不可见的。请按返回时的形式验证该值;如果要将其用作文件系统路径或上游路径,请使用 resolveDotSegments 解析,而不是继续解码。