Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 34 additions & 3 deletions src/web/uvcpp_http_compress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ http_compress_method http_compress::parse_accept_encoding(
struct candidate { http_compress_method method; double q; };
candidate best = { http_compress_method::NONE, 0.0 };

// `q=0` 是"明确不可接受"。这两个标志记住哪些编码被显式拒过 —— 因为它们
// 必须能挡住 `*`(见下面 wildcard 那一支的注释)。
bool gzip_ok = true, deflate_ok = true;

std::string tok;
std::istringstream ss(header);
while (std::getline(ss, tok, ',')) {
Expand All @@ -50,15 +54,26 @@ http_compress_method http_compress::parse_accept_encoding(
while (!enc.empty() && enc.back() == ' ') enc.pop_back();
}

if (q == 0.0) continue; // "not acceptable"
if (q == 0.0) {
// "not acceptable" —— 记下来,别只是跳过这一项。
if (enc == "gzip" || enc == "x-gzip") gzip_ok = false;
else if (enc == "deflate" || enc == "x-deflate") deflate_ok = false;
continue;
}

http_compress_method method = http_compress_method::NONE;
if (enc == "gzip" || enc == "x-gzip")
method = http_compress_method::GZIP;
else if (enc == "deflate" || enc == "x-deflate")
method = http_compress_method::DEFLATE;
else if (enc == "*")
method = http_compress_method::GZIP; // wildcard → prefer gzip
else if (enc == "*") {
// RFC 9110 §12.5.3:`*` 只匹配**没有显式列出**的编码。所以选择时要把
// 被 `q=0` 显式拒掉的排掉 —— 否则 `*, gzip;q=0` 会选回 gzip,服务端
// 于是发了一个客户端刚刚声明过收不了的编码。
method = gzip_ok ? http_compress_method::GZIP
: (deflate_ok ? http_compress_method::DEFLATE
: http_compress_method::NONE);
}

if (method != http_compress_method::NONE) {
if (q > best.q ||
Expand All @@ -69,6 +84,22 @@ http_compress_method http_compress::parse_accept_encoding(
}
}

// 收尾一道:上面按 q 值取最大值,而 `q=0` 那支是"直接跳过",所以一条
// `gzip;q=0, gzip;q=0.5` 这种自相矛盾的输入仍会把 gzip 选回来。显式拒绝
// 优先 —— 少发一层编码只是没优化,发错一层编码是客户端解不开。
if ((best.method == http_compress_method::GZIP && !gzip_ok) ||
(best.method == http_compress_method::DEFLATE && !deflate_ok)) {
const bool was_gzip = (best.method == http_compress_method::GZIP);
if (was_gzip && deflate_ok) {
best.method = http_compress_method::DEFLATE;
} else if (!was_gzip && gzip_ok) {
best.method = http_compress_method::GZIP;
} else {
// 两条路都被拒(或另一条 q 更低但可用)—— 退回未压缩。
best.method = http_compress_method::NONE;
}
}

return best.method;
}

Expand Down
19 changes: 19 additions & 0 deletions tests/functional/web_http_client_func.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,25 @@ static bool test_accept_encoding_parse() {
// q=0 should skip
if (http_compress::parse_accept_encoding("gzip;q=0, deflate") != http_compress_method::DEFLATE)
return false;

// ── `*` 不得把被显式拒掉的编码选回来 ────────────────────────────────
// RFC 9110 §12.5.3:`*` 只匹配**没有显式列出**的编码。上面那条
// `gzip;q=0, deflate` 之所以通过,靠的是"deflate 显式出现且 q 更高",
// 一旦两侧都只由 `*` 覆盖,原来的实现就会把 gzip 选回来 —— 也就是发一个
// 客户端刚声明过收不了的编码。下面四条钉住这一点。
if (http_compress::parse_accept_encoding("*, gzip;q=0") != http_compress_method::DEFLATE)
return false;
if (http_compress::parse_accept_encoding("gzip;q=0, *") != http_compress_method::DEFLATE)
return false;
if (http_compress::parse_accept_encoding("*, deflate;q=0") != http_compress_method::GZIP)
return false;
if (http_compress::parse_accept_encoding("*, gzip;q=0, deflate;q=0") !=
http_compress_method::NONE)
return false;
// 注:这里**没有**钉"显式项比 `*` 的 q 更高时该听谁的"(例如
// `*, gzip;q=0.5` 里 gzip 到底是 1.0 还是 0.5)。按 RFC 该由显式项说了算,
// 但当前实现不区分这两者 —— 那是一条独立的、尚未处理的偏差,不在这里
// 顺手固化下来。
return true;
}

Expand Down
Loading