概念定义
全新 QUIC 或 TLS 1.3 握手要花一个往返客户端才能发请求字节。0-RTT 用先前签发的票据立即派生密钥,第一包就携带加密请求。服务器只有在反重放状态与策略允许时才会处理它。
工作机制
上一条连接的票据携带恢复密钥;客户端派生早期密钥,把数据与握手其余部分一起发出。服务器验证票据、查询重放缓存,或处理早期数据,或拒绝并回退完整握手。后续飞行一律使用全新密钥。
使用场景
高往返路径上的回访延迟:移动应用重复调用 API、浏览器访问已知源、服务网格重连。收益是一个 RTT,在 150 毫秒路径上就是首字节时间里肉眼可见的一截。
常见误区
加密不等于防重放:攻击者可以原样复制第 0 飞行。0-RTT 应限于幂等读操作,或叠加每端点反重放与幂等键。负载均衡器必须共享重放状态或保持路由一致,否则会静默扩大重放窗口。拒绝 0-RTT 是设计内的正常回退,不是错误路径。
代码示例
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.animate-fade {
animation: fadeIn 0.3s ease-out forwards;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
export function parseContentDisposition(header) {
if (typeof header !== "string" || header.trim() === "") {
throw new TypeError("header must be a non-empty string");
}
const segments = splitParameters(header);
const type = segments.shift().trim().toLowerCase();
if (type !== "inline" && type !== "attachment") {
throw new Error(`unsupported disposition type "${type}"`);
}
const parameters = new Map();
for (const segment of segments) {
const equals = segment.indexOf("=");
if (equals === -1) continue;
const name = segment.slice(0, equals).trim().toLowerCase();
if (name === "") continue;
parameters.set(name, unquote(segment.slice(equals + 1).trim()));
}
return { type, filename: resolveFilename(parameters), parameters };
}
function splitParameters(header) {
const segments = [];
let current = "";
let quoted = false;
for (const character of header) {
if (character === '"') quoted = !quoted;
if (character === ";" && !quoted) {
segments.push(current);
current = "";
continue;
}
current += character;
}
segments.push(current);
return segments;
}
function unquote(value) {
if (value.length >= 2 && value.startsWith('"') && value.endsWith('"')) {
return value.slice(1, -1).replace(/\\(.)/g, "$1");
}
return value;
}
function resolveFilename(parameters) {
const extended = parameters.get("filename*");
if (extended !== undefined) {
const match = /^([\w-]+)'([\w-]*)'(.+)$/.exec(extended);
if (match !== null) {
try {
return sanitize(decodeURIComponent(match[3]));
} catch {
return sanitize(extended);
}
}
}
const plain = parameters.get("filename");
return plain === undefined ? null : sanitize(plain);
}
function sanitize(filename) {
return filename.split(/[\\/]/).pop() || null;
}
console.log(parseContentDisposition('attachment; filename="report.pdf"'));
console.log(parseContentDisposition("attachment; filename*=UTF-8''%E4%B8%AD%E6%96%87.pdf"));
console.log(parseContentDisposition('inline; filename="../../etc/passwd"'));