blob: 06c14a1e4568b7d628882c98efd20d946a083117 [file]
/** @file
Unit Tests for HttpTransactHeaders (copy_header_fields)
@section license License
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
#include <string_view>
using namespace std::string_view_literals;
#include "tscore/Diags.h"
#include "tsutil/PostScript.h"
#include "tscore/ink_memory.h"
#include "proxy/http/HttpTransactHeaders.h"
#include "proxy/hdrs/HTTP.h"
#include "proxy/hdrs/MIME.h"
#include <catch2/catch_test_macros.hpp>
TEST_CASE("HttpTransactHeaders::copy_header_fields", "[http]")
{
url_init();
mime_init();
http_init();
// 1) Basic dynamic removal: Connection names X-Custom; X-Custom removed; Host kept; Connection absent
SECTION("basic dynamic removal")
{
HTTPHdr src;
HTTPHdr dst;
ts::PostScript src_defer([&]() -> void { src.destroy(); });
ts::PostScript dst_defer([&]() -> void { dst.destroy(); });
MIMEField *field;
src.create(HTTPType::REQUEST);
// Host should be preserved
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_HOST));
src.field_attach(field);
src.field_value_set(field, "example.com"sv);
// Connection: X-Custom
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_CONNECTION));
src.field_attach(field);
src.field_value_set(field, "X-Custom"sv);
// X-Custom: should be removed after copy
field = src.field_create("X-Custom"sv);
src.field_attach(field);
src.field_value_set(field, "secret"sv);
HttpTransactHeaders::copy_header_fields(&src, &dst, false, 0);
// Host remains
auto *h = dst.field_find(static_cast<std::string_view>(MIME_FIELD_HOST));
REQUIRE(h != nullptr);
CHECK(std::string_view{h->value_get()} == "example.com"sv);
// Connection header should be removed
CHECK(dst.field_find(static_cast<std::string_view>(MIME_FIELD_CONNECTION)) == nullptr);
// X-Custom should be removed (expected to fail before fix)
CHECK(dst.field_find("X-Custom"sv) == nullptr);
}
// 2) Multiple Connection tokens: both named headers removed; Host kept
SECTION("multiple Connection tokens")
{
HTTPHdr src;
HTTPHdr dst;
ts::PostScript src_defer([&]() -> void { src.destroy(); });
ts::PostScript dst_defer([&]() -> void { dst.destroy(); });
MIMEField *field;
src.create(HTTPType::REQUEST);
// Host should be preserved
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_HOST));
src.field_attach(field);
src.field_value_set(field, "example.com"sv);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_CONNECTION));
src.field_attach(field);
src.field_value_set(field, "X-Foo, X-Bar"sv);
field = src.field_create("X-Foo"sv);
src.field_attach(field);
src.field_value_set(field, "a"sv);
field = src.field_create("X-Bar"sv);
src.field_attach(field);
src.field_value_set(field, "b"sv);
HttpTransactHeaders::copy_header_fields(&src, &dst, false, 0);
// Host remains
auto *h = dst.field_find(static_cast<std::string_view>(MIME_FIELD_HOST));
REQUIRE(h != nullptr);
CHECK(std::string_view{h->value_get()} == "example.com"sv);
// X-Foo and X-Bar should be removed (expected to fail before fix)
CHECK(dst.field_find("X-Foo"sv) == nullptr);
CHECK(dst.field_find("X-Bar"sv) == nullptr);
}
// 3) Connection: TE with TE: trailers preserved
SECTION("TE: trailers preserved when Connection names TE")
{
HTTPHdr src;
HTTPHdr dst;
ts::PostScript src_defer([&]() -> void { src.destroy(); });
ts::PostScript dst_defer([&]() -> void { dst.destroy(); });
MIMEField *field;
src.create(HTTPType::REQUEST);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_HOST));
src.field_attach(field);
src.field_value_set(field, "example.com"sv);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_CONNECTION));
src.field_attach(field);
src.field_value_set(field, "TE"sv);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_TE));
src.field_attach(field);
src.field_value_set(field, "trailers"sv);
HttpTransactHeaders::copy_header_fields(&src, &dst, false, 0);
// TE: trailers must be preserved
auto *te = dst.field_find(static_cast<std::string_view>(MIME_FIELD_TE));
REQUIRE(te != nullptr);
CHECK(std::string_view{te->value_get()} == "trailers"sv);
}
// 4) Nonexistent token name: no crash/assert, Host preserved
SECTION("nonexistent token header is harmless")
{
HTTPHdr src;
HTTPHdr dst;
ts::PostScript src_defer([&]() -> void { src.destroy(); });
ts::PostScript dst_defer([&]() -> void { dst.destroy(); });
MIMEField *field;
src.create(HTTPType::REQUEST);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_HOST));
src.field_attach(field);
src.field_value_set(field, "example.com"sv);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_CONNECTION));
src.field_attach(field);
src.field_value_set(field, "X-Nonexistent"sv);
HttpTransactHeaders::copy_header_fields(&src, &dst, false, 0);
// Host should remain
auto *h = dst.field_find(static_cast<std::string_view>(MIME_FIELD_HOST));
REQUIRE(h != nullptr);
CHECK(std::string_view{h->value_get()} == "example.com"sv);
}
// 5) Whitespace around tokens: tokenized names removed correctly
SECTION("whitespace-trimmed token handling")
{
HTTPHdr src;
HTTPHdr dst;
ts::PostScript src_defer([&]() -> void { src.destroy(); });
ts::PostScript dst_defer([&]() -> void { dst.destroy(); });
MIMEField *field;
src.create(HTTPType::REQUEST);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_HOST));
src.field_attach(field);
src.field_value_set(field, "example.com"sv);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_CONNECTION));
src.field_attach(field);
src.field_value_set(field, " X-Foo , X-Bar "sv);
field = src.field_create("X-Foo"sv);
src.field_attach(field);
src.field_value_set(field, "a"sv);
field = src.field_create("X-Bar"sv);
src.field_attach(field);
src.field_value_set(field, "b"sv);
HttpTransactHeaders::copy_header_fields(&src, &dst, false, 0);
CHECK(dst.field_find("X-Foo"sv) == nullptr);
CHECK(dst.field_find("X-Bar"sv) == nullptr);
}
// 6) @-prefix protection: @TCPInfo preserved while normal token removed
SECTION("@-prefix token protection while normal token is stripped")
{
HTTPHdr src;
HTTPHdr dst;
ts::PostScript src_defer([&]() -> void { src.destroy(); });
ts::PostScript dst_defer([&]() -> void { dst.destroy(); });
MIMEField *field;
src.create(HTTPType::REQUEST);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_HOST));
src.field_attach(field);
src.field_value_set(field, "example.com"sv);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_CONNECTION));
src.field_attach(field);
src.field_value_set(field, "@TCPInfo, X-Custom"sv);
field = src.field_create("@TCPInfo"sv);
src.field_attach(field);
src.field_value_set(field, "TS; data"sv);
field = src.field_create("X-Custom"sv);
src.field_attach(field);
src.field_value_set(field, "secret"sv);
HttpTransactHeaders::copy_header_fields(&src, &dst, false, 0);
REQUIRE(dst.field_find("Host") != nullptr);
// @TCPInfo should remain
auto *sfield = dst.field_find("@TCPInfo"sv);
REQUIRE(sfield != nullptr);
CHECK(std::string_view{sfield->value_get()} == "TS; data"sv);
// X-Custom should be removed
CHECK(dst.field_find("X-Custom"sv) == nullptr);
}
// 7) retain_proxy_auth_hdrs=true: Proxy-Authorization preserved when listed in Connection
SECTION("retain_proxy_auth_hdrs preserves Proxy-Authorization listed in Connection")
{
HTTPHdr src;
HTTPHdr dst;
ts::PostScript src_defer([&]() -> void { src.destroy(); });
ts::PostScript dst_defer([&]() -> void { dst.destroy(); });
MIMEField *field;
src.create(HTTPType::REQUEST);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_HOST));
src.field_attach(field);
src.field_value_set(field, "example.com"sv);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_CONNECTION));
src.field_attach(field);
src.field_value_set(field, "Proxy-Authorization, X-Custom"sv);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_PROXY_AUTHORIZATION));
src.field_attach(field);
src.field_value_set(field, "Basic dXNlcjpwYXNz"sv);
field = src.field_create("X-Custom"sv);
src.field_attach(field);
src.field_value_set(field, "secret"sv);
HttpTransactHeaders::copy_header_fields(&src, &dst, true, 0);
// Proxy-Authorization preserved because retain_proxy_auth_hdrs=true
auto *pa = dst.field_find(static_cast<std::string_view>(MIME_FIELD_PROXY_AUTHORIZATION));
REQUIRE(pa != nullptr);
CHECK(std::string_view{pa->value_get()} == "Basic dXNlcjpwYXNz"sv);
// X-Custom still stripped
CHECK(dst.field_find("X-Custom"sv) == nullptr);
}
// 8) retain_proxy_auth_hdrs=false: Proxy-Authorization stripped when listed in Connection
SECTION("Proxy-Authorization stripped when retain_proxy_auth_hdrs is false")
{
HTTPHdr src;
HTTPHdr dst;
ts::PostScript src_defer([&]() -> void { src.destroy(); });
ts::PostScript dst_defer([&]() -> void { dst.destroy(); });
MIMEField *field;
src.create(HTTPType::REQUEST);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_HOST));
src.field_attach(field);
src.field_value_set(field, "example.com"sv);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_CONNECTION));
src.field_attach(field);
src.field_value_set(field, "Proxy-Authorization"sv);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_PROXY_AUTHORIZATION));
src.field_attach(field);
src.field_value_set(field, "Basic dXNlcjpwYXNz"sv);
HttpTransactHeaders::copy_header_fields(&src, &dst, false, 0);
// Proxy-Authorization stripped because retain_proxy_auth_hdrs=false
CHECK(dst.field_find(static_cast<std::string_view>(MIME_FIELD_PROXY_AUTHORIZATION)) == nullptr);
}
// 9) retain_proxy_auth_hdrs=true: Proxy-Authenticate preserved when listed in Connection
SECTION("retain_proxy_auth_hdrs preserves Proxy-Authenticate listed in Connection")
{
HTTPHdr src;
HTTPHdr dst;
ts::PostScript src_defer([&]() -> void { src.destroy(); });
ts::PostScript dst_defer([&]() -> void { dst.destroy(); });
MIMEField *field;
src.create(HTTPType::REQUEST);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_HOST));
src.field_attach(field);
src.field_value_set(field, "example.com"sv);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_CONNECTION));
src.field_attach(field);
src.field_value_set(field, "Proxy-Authenticate"sv);
field = src.field_create(static_cast<std::string_view>(MIME_FIELD_PROXY_AUTHENTICATE));
src.field_attach(field);
src.field_value_set(field, "Basic realm=\"proxy\""sv);
HttpTransactHeaders::copy_header_fields(&src, &dst, true, 0);
// Proxy-Authenticate preserved because retain_proxy_auth_hdrs=true
auto *pa = dst.field_find(static_cast<std::string_view>(MIME_FIELD_PROXY_AUTHENTICATE));
REQUIRE(pa != nullptr);
CHECK(std::string_view{pa->value_get()} == "Basic realm=\"proxy\""sv);
}
}