| /** @file | |
| * | |
| * Unit tests for XDebug plugin utility functions. | |
| * | |
| * 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 "xdebug_utils.h" | |
| #include "xdebug_types.h" | |
| #include "xdebug_escape.h" | |
| #include <catch2/catch_test_macros.hpp> | |
| #include <catch2/generators/catch_generators.hpp> | |
| #include <catch2/generators/catch_generators_range.hpp> | |
| #include <sstream> | |
| #include <vector> | |
| TEST_CASE("xdebug::parse_probe_full_json_field_value basic functionality", "[xdebug][utils]") | |
| { | |
| xdebug::BodyEncoding_t encoding; | |
| SECTION("Basic probe-full-json without suffix") | |
| { | |
| REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json", encoding)); | |
| REQUIRE(encoding == xdebug::BodyEncoding_t::AUTO); | |
| } | |
| SECTION("Case insensitive matching") | |
| { | |
| REQUIRE(xdebug::parse_probe_full_json_field_value("PROBE-FULL-JSON", encoding)); | |
| REQUIRE(encoding == xdebug::BodyEncoding_t::AUTO); | |
| REQUIRE(xdebug::parse_probe_full_json_field_value("Probe-Full-Json", encoding)); | |
| REQUIRE(encoding == xdebug::BodyEncoding_t::AUTO); | |
| } | |
| SECTION("With whitespace around value") | |
| { | |
| REQUIRE(xdebug::parse_probe_full_json_field_value(" probe-full-json ", encoding)); | |
| REQUIRE(encoding == xdebug::BodyEncoding_t::AUTO); | |
| REQUIRE(xdebug::parse_probe_full_json_field_value("\t\nprobe-full-json\r\n ", encoding)); | |
| REQUIRE(encoding == xdebug::BodyEncoding_t::AUTO); | |
| } | |
| } | |
| TEST_CASE("xdebug::parse_probe_full_json_field_value with valid suffixes", "[xdebug][utils]") | |
| { | |
| xdebug::BodyEncoding_t encoding; | |
| SECTION("hex suffix") | |
| { | |
| REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json=hex", encoding)); | |
| REQUIRE(encoding == xdebug::BodyEncoding_t::HEX); | |
| } | |
| SECTION("escape suffix") | |
| { | |
| REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json=escape", encoding)); | |
| REQUIRE(encoding == xdebug::BodyEncoding_t::ESCAPE); | |
| } | |
| SECTION("nobody suffix") | |
| { | |
| REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json=nobody", encoding)); | |
| REQUIRE(encoding == xdebug::BodyEncoding_t::OMIT_BODY); | |
| } | |
| SECTION("Suffixes with whitespace") | |
| { | |
| REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json = hex", encoding)); | |
| REQUIRE(encoding == xdebug::BodyEncoding_t::HEX); | |
| REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json= escape ", encoding)); | |
| REQUIRE(encoding == xdebug::BodyEncoding_t::ESCAPE); | |
| REQUIRE(xdebug::parse_probe_full_json_field_value(" probe-full-json = nobody ", encoding)); | |
| REQUIRE(encoding == xdebug::BodyEncoding_t::OMIT_BODY); | |
| } | |
| SECTION("Case insensitive suffixes") | |
| { | |
| REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json=HEX", encoding)); | |
| REQUIRE(encoding == xdebug::BodyEncoding_t::HEX); | |
| REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json=ESCAPE", encoding)); | |
| REQUIRE(encoding == xdebug::BodyEncoding_t::ESCAPE); | |
| REQUIRE(xdebug::parse_probe_full_json_field_value("probe-full-json=Nobody", encoding)); | |
| REQUIRE(encoding == xdebug::BodyEncoding_t::OMIT_BODY); | |
| } | |
| } | |
| TEST_CASE("xdebug::parse_probe_full_json_field_value invalid cases", "[xdebug][utils]") | |
| { | |
| xdebug::BodyEncoding_t encoding; | |
| SECTION("Not probe-full-json") | |
| { | |
| REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe", encoding)); | |
| REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("full-json", encoding)); | |
| REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-json", encoding)); | |
| REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("x-cache", encoding)); | |
| REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("", encoding)); | |
| } | |
| SECTION("Invalid suffixes") | |
| { | |
| REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-json=invalid", encoding)); | |
| REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-json=base64", encoding)); | |
| REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-json=json", encoding)); | |
| REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-json=none", encoding)); | |
| } | |
| SECTION("Malformed syntax") | |
| { | |
| REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-json=", encoding)); | |
| REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-json==hex", encoding)); | |
| REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-json hex", encoding)); // Missing = | |
| REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-json+hex", encoding)); // Wrong separator | |
| } | |
| SECTION("Partial matches") | |
| { | |
| REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full", encoding)); | |
| REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("probe-full-js", encoding)); | |
| REQUIRE_FALSE(xdebug::parse_probe_full_json_field_value("robe-full-json", encoding)); | |
| } | |
| } | |
| TEST_CASE("xdebug::is_textual_content_type functionality", "[xdebug][utils]") | |
| { | |
| SECTION("Text content types") | |
| { | |
| REQUIRE(xdebug::is_textual_content_type("text/html")); | |
| REQUIRE(xdebug::is_textual_content_type("text/plain")); | |
| REQUIRE(xdebug::is_textual_content_type("text/css")); | |
| REQUIRE(xdebug::is_textual_content_type("text/javascript")); | |
| REQUIRE(xdebug::is_textual_content_type("text/xml")); | |
| } | |
| SECTION("JSON content types") | |
| { | |
| REQUIRE(xdebug::is_textual_content_type("application/json")); | |
| REQUIRE(xdebug::is_textual_content_type("application/ld+json")); | |
| REQUIRE(xdebug::is_textual_content_type("application/vnd.api+json")); | |
| } | |
| SECTION("XML content types") | |
| { | |
| REQUIRE(xdebug::is_textual_content_type("application/xml")); | |
| REQUIRE(xdebug::is_textual_content_type("application/rss+xml")); | |
| REQUIRE(xdebug::is_textual_content_type("application/atom+xml")); | |
| } | |
| SECTION("Other textual types") | |
| { | |
| REQUIRE(xdebug::is_textual_content_type("application/javascript")); | |
| REQUIRE(xdebug::is_textual_content_type("text/csv")); | |
| REQUIRE(xdebug::is_textual_content_type("text/html; charset=utf-8")); | |
| } | |
| SECTION("Non-textual content types") | |
| { | |
| REQUIRE_FALSE(xdebug::is_textual_content_type("application/octet-stream")); | |
| REQUIRE_FALSE(xdebug::is_textual_content_type("image/jpeg")); | |
| REQUIRE_FALSE(xdebug::is_textual_content_type("video/mp4")); | |
| REQUIRE_FALSE(xdebug::is_textual_content_type("audio/mpeg")); | |
| REQUIRE_FALSE(xdebug::is_textual_content_type("application/pdf")); | |
| REQUIRE_FALSE(xdebug::is_textual_content_type("application/zip")); | |
| } | |
| SECTION("Case insensitive matching") | |
| { | |
| REQUIRE(xdebug::is_textual_content_type("TEXT/HTML")); | |
| REQUIRE(xdebug::is_textual_content_type("Application/JSON")); | |
| REQUIRE(xdebug::is_textual_content_type("Application/XML")); | |
| } | |
| SECTION("Edge cases") | |
| { | |
| REQUIRE_FALSE(xdebug::is_textual_content_type("")); | |
| REQUIRE_FALSE(xdebug::is_textual_content_type("invalid")); | |
| REQUIRE(xdebug::is_textual_content_type("contains-json-somewhere")); | |
| REQUIRE(xdebug::is_textual_content_type("has-xml-in-name")); | |
| } | |
| } | |
| // Helper function to process a string through EscapeCharForJson. | |
| static std::string | |
| escape_string(std::string_view input, bool full_json) | |
| { | |
| xdebug::EscapeCharForJson escaper(full_json); | |
| std::stringstream ss; | |
| for (char c : input) { | |
| ss << escaper(c); | |
| } | |
| return ss.str(); | |
| } | |
| struct EscapeTestCase { | |
| std::string description; | |
| bool full_json; | |
| std::string input; | |
| std::string expected; | |
| }; | |
| TEST_CASE("xdebug::EscapeCharForJson escaping", "[xdebug][headers]") | |
| { | |
| // clang-format off | |
| static std::vector<EscapeTestCase> const tests = { | |
| // Single quotes are NOT escaped in either mode. | |
| {"full JSON: single quotes are not escaped", | |
| xdebug::FULL_JSON, | |
| R"('self')", | |
| R"('self')"}, | |
| {"full JSON: CSP header with multiple single-quoted directives", | |
| xdebug::FULL_JSON, | |
| R"(child-src blob: 'self'; connect-src 'self' 'unsafe-inline')", | |
| R"(child-src blob: 'self'; connect-src 'self' 'unsafe-inline')"}, | |
| {"legacy: single quotes are not escaped", | |
| !xdebug::FULL_JSON, | |
| R"('self')", | |
| R"('self')"}, | |
| {"legacy: CSP header with multiple single-quoted directives", | |
| !xdebug::FULL_JSON, | |
| R"(child-src blob: 'self'; connect-src 'self' 'unsafe-inline')", | |
| R"(child-src blob: 'self'; connect-src 'self' 'unsafe-inline')"}, | |
| // Common escapes work the same in both modes. | |
| {"full JSON: double quotes are escaped", | |
| xdebug::FULL_JSON, | |
| R"(say "hello")", | |
| R"(say \"hello\")"}, | |
| {"legacy: double quotes are escaped", | |
| !xdebug::FULL_JSON, | |
| R"(say "hello")", | |
| R"(say \"hello\")"}, | |
| {"full JSON: backslashes are escaped", | |
| xdebug::FULL_JSON, | |
| R"(path\to\file)", | |
| R"(path\\to\\file)"}, | |
| {"legacy: backslashes are escaped", | |
| !xdebug::FULL_JSON, | |
| R"(path\to\file)", | |
| R"(path\\to\\file)"}, | |
| {"full JSON: tab characters are escaped", | |
| xdebug::FULL_JSON, | |
| "line1\tline2", | |
| R"(line1\tline2)"}, | |
| {"full JSON: backspace characters are escaped", | |
| xdebug::FULL_JSON, | |
| "a\bb", | |
| R"(a\bb)"}, | |
| {"full JSON: form feed characters are escaped", | |
| xdebug::FULL_JSON, | |
| "a\fb", | |
| R"(a\fb)"}, | |
| {"full JSON: plain text passes through unchanged", | |
| xdebug::FULL_JSON, | |
| R"(hello world)", | |
| R"(hello world)"}, | |
| {"legacy: plain text passes through unchanged", | |
| !xdebug::FULL_JSON, | |
| R"(hello world)", | |
| R"(hello world)"}, | |
| }; | |
| // clang-format on | |
| auto const &test = GENERATE_REF(from_range(tests)); | |
| CAPTURE(test.description, test.full_json, test.input, test.expected); | |
| std::string result = escape_string(test.input, test.full_json); | |
| REQUIRE(result == test.expected); | |
| } | |
| TEST_CASE("xdebug::EscapeCharForJson backup calculation", "[xdebug][headers]") | |
| { | |
| struct BackupTestCase { | |
| std::string description; | |
| bool full_json; | |
| std::size_t expected_backup; | |
| }; | |
| // clang-format off | |
| static std::vector<BackupTestCase> const tests = { | |
| {R"(full JSON uses "," separator (backup = 2))", | |
| xdebug::FULL_JSON, | |
| 2}, | |
| {R"(legacy uses "',\n\t'" separator (backup = 4))", | |
| !xdebug::FULL_JSON, | |
| 4}, | |
| }; | |
| // clang-format on | |
| auto const &test = GENERATE_REF(from_range(tests)); | |
| CAPTURE(test.description, test.full_json, test.expected_backup); | |
| REQUIRE(xdebug::EscapeCharForJson::backup(test.full_json) == test.expected_backup); | |
| } |