Skip to content

CXXCBC-624: Fix user agent ID generation #692

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 7, 2024
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
2 changes: 2 additions & 0 deletions cmake/VersionInfo.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,8 @@ if(COUCHBASE_CXX_CLIENT_GIT_DESCRIBE MATCHES
endif()
endif()

set(COUCHBASE_CXX_CLIENT_WRAPPER_UNIFIED_ID "" CACHE STRING "Unified ID for wrapper SDK formatted as '<name>/<version>'")

configure_file(${PROJECT_SOURCE_DIR}/cmake/build_version.hxx.in
${PROJECT_BINARY_DIR}/generated/couchbase/build_version.hxx @ONLY)
configure_file(${PROJECT_SOURCE_DIR}/cmake/build_config.hxx.in
Expand Down
1 change: 1 addition & 0 deletions cmake/build_version.hxx.in
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ constexpr auto COUCHBASE_CXX_CLIENT_VERSION_BUILD = @couchbase_cxx_client_BUILD_
constexpr auto COUCHBASE_CXX_CLIENT_GIT_REVISION = "@COUCHBASE_CXX_CLIENT_GIT_REVISION@";
constexpr auto COUCHBASE_CXX_CLIENT_GIT_REVISION_SHORT = "@COUCHBASE_CXX_CLIENT_GIT_REVISION_SHORT@";
constexpr auto COUCHBASE_CXX_CLIENT_GIT_DESCRIBE = "@COUCHBASE_CXX_CLIENT_GIT_DESCRIBE@";
constexpr auto COUCHBASE_CXX_CLIENT_WRAPPER_UNIFIED_ID = "@COUCHBASE_CXX_CLIENT_WRAPPER_UNIFIED_ID@";
#cmakedefine COUCHBASE_CXX_CLIENT_STATIC_STDLIB 1
#cmakedefine COUCHBASE_CXX_CLIENT_STATIC_OPENSSL 1
53 changes: 47 additions & 6 deletions core/meta/version.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,28 @@ os() -> const std::string&
return system;
}

namespace
{
constexpr auto
has_wrapper_sdk_id() -> bool
{
return COUCHBASE_CXX_CLIENT_WRAPPER_UNIFIED_ID != nullptr &&
COUCHBASE_CXX_CLIENT_WRAPPER_UNIFIED_ID[0] != '\0';
}

auto
wrapper_sdk_id() -> std::string
{
return COUCHBASE_CXX_CLIENT_WRAPPER_UNIFIED_ID;
}

auto
cxx_sdk_id() -> std::string
{
return fmt::format("cxx/{}", sdk_semver());
}
} // namespace

constexpr const char* ssl_lib_id =
#if defined(COUCHBASE_CXX_CLIENT_STATIC_BORINGSSL)
"bssl"
Expand All @@ -329,16 +351,24 @@ user_agent_for_http(const std::string& client_id,
const std::string& session_id,
const std::string& extra) -> std::string
{
auto user_agent = fmt::format("{};{}/0x{:x};client/{};session/{};{}",
couchbase::core::meta::sdk_id(),
std::string user_agent{ has_wrapper_sdk_id() ? wrapper_sdk_id() : cxx_sdk_id() };
user_agent.append(" (");
if (has_wrapper_sdk_id()) {
user_agent.append(cxx_sdk_id()).append(";");
}

user_agent.append(fmt::format("{}/{};{}/0x{:x};client/{};session/{};{}",
COUCHBASE_CXX_CLIENT_SYSTEM_NAME,
COUCHBASE_CXX_CLIENT_SYSTEM_PROCESSOR,
ssl_lib_id,
OpenSSL_version_num(),
client_id,
session_id,
couchbase::core::meta::os());
couchbase::core::meta::os()));
if (!extra.empty()) {
user_agent.append(";").append(extra);
}
user_agent.append(")");
for (auto& ch : user_agent) {
if (ch == '\n' || ch == '\r') {
ch = ' ';
Expand All @@ -356,12 +386,22 @@ user_agent_for_mcbp(const std::string& client_id,
tao::json::value user_agent{
{ "i", fmt::format("{}/{}", client_id, session_id) },
};
const std::string core_id =
fmt::format("{};{}/0x{:x}", couchbase::core::meta::sdk_id(), ssl_lib_id, OpenSSL_version_num());
std::string core_id{ has_wrapper_sdk_id() ? wrapper_sdk_id() : cxx_sdk_id() };
core_id.append(" (");
if (has_wrapper_sdk_id()) {
core_id.append(cxx_sdk_id()).append(";");
}
core_id.append(fmt::format("{}/{};{}/0x{:x}",
COUCHBASE_CXX_CLIENT_SYSTEM_NAME,
COUCHBASE_CXX_CLIENT_SYSTEM_PROCESSOR,
ssl_lib_id,
OpenSSL_version_num()));
std::string sdk_id = core_id;
core_id.append(")");
if (!extra.empty()) {
sdk_id.append(";").append(extra);
}
sdk_id.append(")");
if (max_length > 0) {
auto current_length = utils::json::generate(user_agent).size();
auto allowed_length = max_length - current_length;
Expand All @@ -373,7 +413,8 @@ user_agent_for_mcbp(const std::string& client_id,
/* user-provided string is too weird, lets just fall back to just core */
sdk_id = core_id;
} else {
sdk_id.erase(allowed_length - escaped_characters);
sdk_id.erase(allowed_length - escaped_characters - 1);
sdk_id.append(")");
}
}
}
Expand Down
45 changes: 24 additions & 21 deletions test/test_unit_utils.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,8 @@ TEST_CASE("unit: user_agent string", "[unit]")
COUCHBASE_CXX_CLIENT_SYSTEM_PROCESSOR,
ssl_lib_id,
OpenSSL_version_num());
std::string core_version = fmt::format("cxx/{}.{}.{}/{};{}/{};{}/0x{:x}",
COUCHBASE_CXX_CLIENT_VERSION_MAJOR,
COUCHBASE_CXX_CLIENT_VERSION_MINOR,
COUCHBASE_CXX_CLIENT_VERSION_PATCH,
COUCHBASE_CXX_CLIENT_GIT_REVISION_SHORT,
std::string core_version = fmt::format("cxx/{} ({}/{};{}/0x{:x})",
couchbase::core::meta::sdk_semver(),
COUCHBASE_CXX_CLIENT_SYSTEM_NAME,
COUCHBASE_CXX_CLIENT_SYSTEM_PROCESSOR,
ssl_lib_id,
Expand All @@ -120,12 +117,13 @@ TEST_CASE("unit: user_agent string", "[unit]")
auto simple_user_agent = couchbase::core::meta::user_agent_for_mcbp("0xDEADBEEF", "0xCAFEBEBE");
REQUIRE(simple_user_agent ==
fmt::format(R"({{"a":"{}","i":"0xDEADBEEF/0xCAFEBEBE"}})", core_version));
REQUIRE(simple_user_agent.size() == 53 + os_version.size());

REQUIRE(couchbase::core::meta::user_agent_for_mcbp(
"0xDEADBEEF", "0xCAFEBEBE", "couchnode/1.2.3; openssl/1.1.1l") ==
fmt::format(R"({{"a":"{};couchnode/1.2.3; openssl/1.1.1l","i":"0xDEADBEEF/0xCAFEBEBE"}})",
core_version));
auto core_version_prefix = core_version.substr(0, core_version.size() - 1);
REQUIRE(
couchbase::core::meta::user_agent_for_mcbp(
"0xDEADBEEF", "0xCAFEBEBE", "couchnode/1.2.3; openssl/1.1.1l") ==
fmt::format(R"!({{"a":"{};couchnode/1.2.3; openssl/1.1.1l)","i":"0xDEADBEEF/0xCAFEBEBE"}})!",
core_version_prefix));

std::string long_extra = "01234567890abcdef01234567890abcdef"
"01234567890abcdef01234567890abcdef"
Expand All @@ -138,32 +136,37 @@ TEST_CASE("unit: user_agent string", "[unit]")
REQUIRE(long_extra.size() == 272);

REQUIRE(couchbase::core::meta::user_agent_for_mcbp("0xDEADBEEF", "0xCAFEBEBE", long_extra) ==
fmt::format(R"({{"a":"{};{}","i":"0xDEADBEEF/0xCAFEBEBE"}})", core_version, long_extra));
fmt::format(
R"!({{"a":"{};{})","i":"0xDEADBEEF/0xCAFEBEBE"}})!", core_version_prefix, long_extra));

auto trimmed_user_agent =
couchbase::core::meta::user_agent_for_mcbp("0xDEADBEEF", "0xCAFEBEBE", long_extra, 250);
REQUIRE(trimmed_user_agent.size() == 250);
REQUIRE(250 - simple_user_agent.size() == 197 - os_version.size());
REQUIRE(trimmed_user_agent == fmt::format(R"({{"a":"{};{}","i":"0xDEADBEEF/0xCAFEBEBE"}})",
core_version,
long_extra.substr(0, 196 - os_version.size())));
REQUIRE(trimmed_user_agent ==
fmt::format(R"!({{"a":"{};{})","i":"0xDEADBEEF/0xCAFEBEBE"}})!",
core_version_prefix,
long_extra.substr(0, 250 - simple_user_agent.size() - 1 /* ';' */)));

auto long_extra_with_non_printable_characters =
long_extra.substr(0, 193 - os_version.size()) + "\n\n";
long_extra.substr(0, 250 - simple_user_agent.size() - 4 /* ';' and room for 1.5 of '\n\n' */) +
"\n\n";
trimmed_user_agent = couchbase::core::meta::user_agent_for_mcbp(
"0xDEADBEEF", "0xCAFEBEBE", long_extra_with_non_printable_characters, 250);
REQUIRE(trimmed_user_agent.size() == 249);
REQUIRE(trimmed_user_agent == fmt::format(R"({{"a":"{};{}","i":"0xDEADBEEF/0xCAFEBEBE"}})",
core_version,
long_extra.substr(0, 193 - os_version.size()) + "\\n"));
REQUIRE(trimmed_user_agent ==
fmt::format(
R"!({{"a":"{};{})","i":"0xDEADBEEF/0xCAFEBEBE"}})!",
core_version_prefix,
long_extra.substr(0, 250 - simple_user_agent.size() - 4 /* ';' and room for '\n' */) +
"\\n"));

auto long_and_weird_extra = "hello" + std::string(300, '\n');
trimmed_user_agent = couchbase::core::meta::user_agent_for_mcbp(
"0xDEADBEEF", "0xCAFEBEBE", long_and_weird_extra, 250);
REQUIRE(trimmed_user_agent == simple_user_agent);

REQUIRE(fmt::format("{};client/0xDEADBEEF;session/0xCAFEBEBE;{};hello world",
core_version,
REQUIRE(fmt::format("{};client/0xDEADBEEF;session/0xCAFEBEBE;{};hello world)",
core_version_prefix,
couchbase::core::meta::os()) ==
couchbase::core::meta::user_agent_for_http("0xDEADBEEF", "0xCAFEBEBE", "hello\nworld"));
}
Expand Down
Loading