blob: 80dbfa6caa84af1df3ed2c19884b8eec9bf5f2f0 [file]
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/public/cpp/headers_matcher.h"
#include <algorithm>
#include "base/strings/string_util.h"
namespace network {
namespace {
// Normalize the headers:
// - Sort the headers to ignore differences in headers' ordering.
// - Lower-case the header names, as header names should be case-insensitive.
net::HttpRequestHeaders::HeaderVector GetNormalizedHeaders(
const net::HttpRequestHeaders& headers) {
net::HttpRequestHeaders::HeaderVector normalized_headers(
headers.GetHeaderVector());
for (auto& header : normalized_headers) {
header.key = base::ToLowerASCII(header.key);
}
auto cmp = [](const net::HttpRequestHeaders::HeaderKeyValuePair& a,
const net::HttpRequestHeaders::HeaderKeyValuePair& b) {
return a.key < b.key;
};
std::sort(normalized_headers.begin(), normalized_headers.end(), cmp);
return normalized_headers;
}
bool AreValuesMatching(const std::string& expected_value,
const std::string& actual_value,
MatchHttpRequestHeadersValueOption value_match_option) {
switch (value_match_option) {
case MatchHttpRequestHeadersValueOption::kEquals:
return expected_value == actual_value;
case MatchHttpRequestHeadersValueOption::kEqualsCaseInsensitiveASCII:
return base::EqualsCaseInsensitiveASCII(expected_value, actual_value);
}
}
} // namespace
MismatchedHttpRequestHeader::MismatchedHttpRequestHeader(
std::string lowered_key,
std::optional<std::string> expected_value,
std::optional<std::string> actual_value)
: lowered_key(std::move(lowered_key)),
expected_value(std::move(expected_value)),
actual_value(std::move(actual_value)) {}
MismatchedHttpRequestHeader::~MismatchedHttpRequestHeader() = default;
MismatchedHttpRequestHeader::MismatchedHttpRequestHeader(
const MismatchedHttpRequestHeader& other) = default;
MismatchedHttpRequestHeader& MismatchedHttpRequestHeader::operator=(
const MismatchedHttpRequestHeader& other) = default;
MismatchedHttpRequestHeader::MismatchedHttpRequestHeader(
MismatchedHttpRequestHeader&& other) = default;
MismatchedHttpRequestHeader& MismatchedHttpRequestHeader::operator=(
MismatchedHttpRequestHeader&& other) = default;
bool MismatchedHttpRequestHeader::EqualsForTesting( // IN-TEST
const MismatchedHttpRequestHeader& other) const {
return lowered_key == other.lowered_key &&
expected_value == other.expected_value &&
actual_value == other.actual_value;
}
std::vector<MismatchedHttpRequestHeader> MatchHttpRequestHeaders(
const net::HttpRequestHeaders& expected,
const net::HttpRequestHeaders& actual,
MatchHttpRequestHeadersValueOption value_match_option,
base::FunctionRef<bool(const std::string&)> should_ignore) {
const net::HttpRequestHeaders::HeaderVector expected_headers =
GetNormalizedHeaders(expected);
const net::HttpRequestHeaders::HeaderVector actual_headers =
GetNormalizedHeaders(actual);
std::vector<MismatchedHttpRequestHeader> mismatched_headers;
auto add_mismatch_if_not_ignored =
[&](std::string lowered_key, std::optional<std::string> expected_value,
std::optional<std::string> actual_value) {
if (!should_ignore(lowered_key)) {
mismatched_headers.emplace_back(std::move(lowered_key),
std::move(expected_value),
std::move(actual_value));
}
};
auto expected_it = expected_headers.begin();
auto actual_it = actual_headers.begin();
while (expected_it != expected_headers.end() &&
actual_it != actual_headers.end()) {
const auto& expected_key = expected_it->key;
const auto& expected_value = expected_it->value;
const auto& actual_key = actual_it->key;
const auto& actual_value = actual_it->value;
if (expected_key == actual_key) {
if (!AreValuesMatching(expected_value, actual_value,
value_match_option)) {
add_mismatch_if_not_ignored(expected_key, expected_value, actual_value);
}
++actual_it;
++expected_it;
} else if (actual_key < expected_key) {
add_mismatch_if_not_ignored(actual_key, /*expected_value=*/std::nullopt,
actual_value);
++actual_it;
} else {
add_mismatch_if_not_ignored(expected_key, expected_value,
/*actual_value=*/std::nullopt);
++expected_it;
}
}
while (actual_it != actual_headers.end()) {
add_mismatch_if_not_ignored(actual_it->key, /*expected_value=*/std::nullopt,
actual_it->value);
++actual_it;
}
while (expected_it != expected_headers.end()) {
add_mismatch_if_not_ignored(expected_it->key, expected_it->value,
/*actual_value=*/std::nullopt);
++expected_it;
}
return mismatched_headers;
}
} // namespace network