As some issues that can happen with a Git client can be operating system specific, it can be useful for a server to know which OS a client is using. In the same way it can be useful for a client to know which OS a server is using. Our current agent capability is in the form of "package/version" (e.g., "git/1.8.3.1"). Let's extend it to include the operating system name (os) i.e in the form "package/version os" (e.g., "git/1.8.3.1 Linux"). Including OS details in the agent capability simplifies implementation, maintains backward compatibility, avoids introducing a new capability, encourages adoption across Git-compatible software, and enhances debugging by providing complete environment information without affecting functionality. Add the `transfer.advertiseOSInfo` config option to address privacy concerns. It defaults to `true` and can be changed to `false`. When `true`, both the client and server independently append their operating system name(os) to the `agent` capability value. The `agent` capability will now be in form of "package/version os" (e.g., "git/1.8.3.1 Linux"). When `false`, the `agent` capability will be in the form of "package/version" e.g "git/1.8.3.1". The server's configuration is independent of the client's. Defaults to `true`. The operating system name is retrieved using the 'sysname' field of the `uname(2)` system call or its equivalent. However, there are differences between `uname(1)` (command-line utility) and `uname(2)` (system call) outputs on Windows. These discrepancies complicate testing on Windows platforms. For example: - `uname(1)` output: MINGW64_NT-10.0-20348.3.4.10-87d57229.x86_64\ .2024-02-14.20:17.UTC.x86_64 - `uname(2)` output: Windows.10.0.20348 On Windows, uname(2) is not actually system-supplied but is instead already faked up by Git itself. We could have overcome the test issue on Windows by implementing a new `uname` subcommand in `test-tool` using uname(2), but except uname(2), which would be tested against itself, there would be nothing platform specific, so it's just simpler to disable the tests on Windows. Mentored-by: Christian Couder <chriscool@xxxxxxxxxxxxx> Signed-off-by: Usman Akinyemi <usmanakinyemi202@xxxxxxxxx> --- Documentation/config/transfer.txt | 8 +++++++ Documentation/gitprotocol-v2.txt | 15 ++++++++----- t/t5555-http-smart-common.sh | 10 ++++++++- t/t5701-git-serve.sh | 9 +++++++- t/test-lib-functions.sh | 8 +++++++ version.c | 37 +++++++++++++++++++++++++++++++ version.h | 15 +++++++++++++ 7 files changed, 95 insertions(+), 7 deletions(-) diff --git a/Documentation/config/transfer.txt b/Documentation/config/transfer.txt index f1ce50f4a6..1e1dc849ef 100644 --- a/Documentation/config/transfer.txt +++ b/Documentation/config/transfer.txt @@ -125,3 +125,11 @@ transfer.bundleURI:: transfer.advertiseObjectInfo:: When `true`, the `object-info` capability is advertised by servers. Defaults to false. + +transfer.advertiseOSInfo:: + When `true`, both the client and server independently append their + operating system name (os) to the `agent` capability value. The `agent` + capability will now be in form of "package/version os" (e.g., + "git/1.8.3.1 Linux"). When `false`, the `agent` capability will be + in the form of "package/version" e.g "git/1.8.3.1". The server's + configuration is independent of the client's. Defaults to `true`. diff --git a/Documentation/gitprotocol-v2.txt b/Documentation/gitprotocol-v2.txt index 1652fef3ae..8fab7d7d52 100644 --- a/Documentation/gitprotocol-v2.txt +++ b/Documentation/gitprotocol-v2.txt @@ -184,11 +184,16 @@ form `agent=X`) to notify the client that the server is running version the `agent` capability with a value `Y` (in the form `agent=Y`) in its request to the server (but it MUST NOT do so if the server did not advertise the agent capability). The `X` and `Y` strings may contain any -printable ASCII characters except space (i.e., the byte range 32 < x < -127), and are typically of the form "package/version" (e.g., -"git/1.8.3.1"). The agent strings are purely informative for statistics -and debugging purposes, and MUST NOT be used to programmatically assume -the presence or absence of particular features. +printable ASCII characters (i.e., the byte range 32 < x < 127), and are +typically of the form "package/version os" (e.g., "git/1.8.3.1 Linux") +where `os` is the operating system name (e.g., "Linux"). `X` and `Y` can +be configured using the GIT_USER_AGENT environment variable and it takes +priority. If `transfer.advertiseOSInfo` is `false` on the server, the server +omits the `os` from X. If it is `false` on the client, the client omits the +`os` from `Y`. The `os` is retrieved using the 'sysname' field of the `uname(2)` +system call or its equivalent. The agent strings are purely informative for +statistics and debugging purposes, and MUST NOT be used to programmatically +assume the presence or absence of particular features. ls-refs ~~~~~~~ diff --git a/t/t5555-http-smart-common.sh b/t/t5555-http-smart-common.sh index e47ea1ad10..140a7f0ffb 100755 --- a/t/t5555-http-smart-common.sh +++ b/t/t5555-http-smart-common.sh @@ -123,9 +123,17 @@ test_expect_success 'git receive-pack --advertise-refs: v1' ' ' test_expect_success 'git upload-pack --advertise-refs: v2' ' + printf "agent=FAKE" >agent_capability && + if test_have_prereq WINDOWS + then + printf "\n" >>agent_capability && + git config transfer.advertiseOSInfo false + else + printf " %s\n" $(uname -s | test_redact_non_printables) >>agent_capability + fi && cat >expect <<-EOF && version 2 - agent=FAKE + $(cat agent_capability) ls-refs=unborn fetch=shallow wait-for-done server-option diff --git a/t/t5701-git-serve.sh b/t/t5701-git-serve.sh index 4c24a188b9..a4c12372f8 100755 --- a/t/t5701-git-serve.sh +++ b/t/t5701-git-serve.sh @@ -8,13 +8,20 @@ export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME . ./test-lib.sh test_expect_success 'setup to generate files with expected content' ' - printf "agent=git/%s\n" "$(git version | cut -d" " -f3)" >agent_capability && + printf "agent=git/%s" "$(git version | cut -d" " -f3)" >agent_capability && test_oid_cache <<-EOF && wrong_algo sha1:sha256 wrong_algo sha256:sha1 EOF + if test_have_prereq WINDOWS + then + printf "\n" >>agent_capability && + git config transfer.advertiseOSInfo false + else + printf " %s\n" $(uname -s | test_redact_non_printables) >>agent_capability + fi && cat >expect.base <<-EOF && version 2 $(cat agent_capability) diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 78e054ab50..3465904323 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -2007,3 +2007,11 @@ test_trailing_hash () { test-tool hexdump | sed "s/ //g" } + +# Trim and replace each character with ascii code below 32 or above +# 127 (included) using a dot '.' character. +# Octal intervals \001-\040 and \177-\377 +# correspond to decimal intervals 1-32 and 127-255 +test_redact_non_printables () { + tr -d "\n\r" | tr "[\001-\040][\177-\377]" "." +} diff --git a/version.c b/version.c index d95221a72a..f0f936a75e 100644 --- a/version.c +++ b/version.c @@ -1,9 +1,12 @@ +#define USE_THE_REPOSITORY_VARIABLE + #include "git-compat-util.h" #include "version.h" #include "version-def.h" #include "strbuf.h" #include "sane-ctype.h" #include "gettext.h" +#include "config.h" const char git_version_string[] = GIT_VERSION; const char git_built_from_commit_string[] = GIT_BUILT_FROM_COMMIT; @@ -43,6 +46,12 @@ const char *git_user_agent_sanitized(void) strbuf_addstr(&buf, git_user_agent()); redact_non_printables(&buf); + /* Add os name if the transfer.advertiseosinfo config is true */ + if (advertise_os_info()) { + /* Add space to space character after git version string */ + strbuf_addch(&buf, ' '); + strbuf_addstr(&buf, os_info_sanitized()); + } agent = strbuf_detach(&buf, NULL); } @@ -69,3 +78,31 @@ int get_uname_info(struct strbuf *buf, unsigned int full) strbuf_addf(buf, "%s\n", uname_info.sysname); return 0; } + +const char *os_info_sanitized(void) +{ + static const char *os = NULL; + + if (!os) { + struct strbuf buf = STRBUF_INIT; + + get_uname_info(&buf, 0); + /* Sanitize the os information immediately */ + redact_non_printables(&buf); + os = strbuf_detach(&buf, NULL); + } + + return os; +} + +int advertise_os_info(void) +{ + static int transfer_advertise_os_info= -1; + + if (transfer_advertise_os_info == -1) { + repo_config_get_bool(the_repository, "transfer.advertiseosinfo", &transfer_advertise_os_info); + /* enabled by default */ + transfer_advertise_os_info = !!transfer_advertise_os_info; + } + return transfer_advertise_os_info; +} diff --git a/version.h b/version.h index 5eb586c0bd..b2325865d7 100644 --- a/version.h +++ b/version.h @@ -1,6 +1,8 @@ #ifndef VERSION_H #define VERSION_H +struct repository; + extern const char git_version_string[]; extern const char git_built_from_commit_string[]; @@ -14,4 +16,17 @@ const char *git_user_agent_sanitized(void); */ int get_uname_info(struct strbuf *buf, unsigned int full); +/* + Retrieve, sanitize and cache operating system info for subsequent + calls. Return a pointer to the sanitized operating system info + string. +*/ +const char *os_info_sanitized(void); + +/* + Retrieve and cache transfer.advertiseosinfo config value. Return 1 + if true, 0 if false. +*/ +int advertise_os_info(void); + #endif /* VERSION_H */ -- 2.48.1