Skip to content

Commit 4839f4f

Browse files
committed
http: allow users more control over user-agent
Users can now override the "product" portion of the user-agent (via GIT_OPT_SET_USER_AGENT_PRODUCT). This continues to default to "git/2.0", but users may define their own string, or may opt out of sending a user-agent entirely (by passing an empty string). Similarly, users may now also opt-out of sending any additional "comment" information by setting the GIT_OPT_SET_USER_AGENT value to an empty string.
1 parent 4d19e8c commit 4839f4f

File tree

7 files changed

+200
-57
lines changed

7 files changed

+200
-57
lines changed

include/git2/common.h

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,9 @@ typedef enum {
228228
GIT_OPT_SET_SERVER_CONNECT_TIMEOUT,
229229
GIT_OPT_GET_SERVER_CONNECT_TIMEOUT,
230230
GIT_OPT_SET_SERVER_TIMEOUT,
231-
GIT_OPT_GET_SERVER_TIMEOUT
231+
GIT_OPT_GET_SERVER_TIMEOUT,
232+
GIT_OPT_SET_USER_AGENT_PRODUCT,
233+
GIT_OPT_GET_USER_AGENT_PRODUCT
232234
} git_libgit2_opt_t;
233235

234236
/**
@@ -337,11 +339,35 @@ typedef enum {
337339
*
338340
* * opts(GIT_OPT_SET_USER_AGENT, const char *user_agent)
339341
*
340-
* > Set the value of the User-Agent header. This value will be
341-
* > appended to "git/1.0", for compatibility with other git clients.
342+
* > Set the value of the comment section of the User-Agent header.
343+
* > This can be information about your product and its version.
344+
* > By default this is "libgit2" followed by the libgit2 version.
342345
* >
343-
* > - `user_agent` is the value that will be delivered as the
344-
* > User-Agent header on HTTP requests.
346+
* > This value will be appended to User-Agent _product_, which
347+
* > is typically set to "git/2.0".
348+
* >
349+
* > Set to the empty string ("") to not send any information in the
350+
* > comment section, or set to NULL to restore the default.
351+
*
352+
* * opts(GIT_OPT_GET_USER_AGENT, git_buf *out)
353+
*
354+
* > Get the value of the User-Agent header.
355+
* > The User-Agent is written to the `out` buffer.
356+
*
357+
* * opts(GIT_OPT_SET_USER_AGENT_PRODUCT, const char *user_agent_product)
358+
*
359+
* > Set the value of the product portion of the User-Agent header.
360+
* > This defaults to "git/2.0", for compatibility with other git
361+
* > clients. It is recommended to keep this as git/<version> for
362+
* > compatibility with servers that do user-agent detection.
363+
* >
364+
* > Set to the empty string ("") to not send any user-agent string,
365+
* > or set to NULL to restore the default.
366+
*
367+
* * opts(GIT_OPT_GET_USER_AGENT_PRODUCT, git_buf *out)
368+
*
369+
* > Get the value of the User-Agent product header.
370+
* > The User-Agent product is written to the `out` buffer.
345371
*
346372
* * opts(GIT_OPT_SET_WINDOWS_SHAREMODE, unsigned long value)
347373
*
@@ -377,11 +403,6 @@ typedef enum {
377403
* >
378404
* > - `ciphers` is the list of ciphers that are eanbled.
379405
*
380-
* * opts(GIT_OPT_GET_USER_AGENT, git_buf *out)
381-
*
382-
* > Get the value of the User-Agent header.
383-
* > The User-Agent is written to the `out` buffer.
384-
*
385406
* * opts(GIT_OPT_ENABLE_OFS_DELTA, int enabled)
386407
*
387408
* > Enable or disable the use of "offset deltas" when creating packfiles,

src/libgit2/settings.c

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,14 @@ extern int git_socket_stream__connect_timeout;
5151
extern int git_socket_stream__timeout;
5252

5353
char *git__user_agent;
54+
char *git__user_agent_product;
5455
char *git__ssl_ciphers;
5556

5657
static void settings_global_shutdown(void)
5758
{
5859
git__free(git__user_agent);
60+
git__free(git__user_agent_product);
61+
5962
git__free(git__ssl_ciphers);
6063
git_repository__free_extensions();
6164
}
@@ -89,9 +92,16 @@ static int config_level_to_sysdir(int *out, int config_level)
8992
return -1;
9093
}
9194

95+
const char *git_settings__user_agent_product(void)
96+
{
97+
return git__user_agent_product ? git__user_agent_product :
98+
"git/2.0";
99+
}
100+
92101
const char *git_settings__user_agent(void)
93102
{
94-
return git__user_agent;
103+
return git__user_agent ? git__user_agent :
104+
"libgit2 " LIBGIT2_VERSION;
95105
}
96106

97107
int git_libgit2_opts(int key, ...)
@@ -211,14 +221,65 @@ int git_libgit2_opts(int key, ...)
211221
error = -1;
212222
#endif
213223
break;
224+
214225
case GIT_OPT_SET_USER_AGENT:
215-
git__free(git__user_agent);
216-
git__user_agent = git__strdup(va_arg(ap, const char *));
217-
if (!git__user_agent) {
218-
git_error_set_oom();
219-
error = -1;
226+
{
227+
const char *new_agent = va_arg(ap, const char *);
228+
229+
git__free(git__user_agent);
230+
231+
if (new_agent) {
232+
git__user_agent= git__strdup(new_agent);
233+
234+
if (!git__user_agent)
235+
error = -1;
236+
} else {
237+
git__user_agent = NULL;
238+
}
239+
}
240+
break;
241+
242+
case GIT_OPT_GET_USER_AGENT:
243+
{
244+
git_buf *out = va_arg(ap, git_buf *);
245+
git_str str = GIT_STR_INIT;
246+
247+
if ((error = git_buf_tostr(&str, out)) < 0 ||
248+
(error = git_str_puts(&str, git_settings__user_agent())) < 0)
249+
break;
250+
251+
error = git_buf_fromstr(out, &str);
252+
}
253+
break;
254+
255+
case GIT_OPT_SET_USER_AGENT_PRODUCT:
256+
{
257+
const char *new_agent = va_arg(ap, const char *);
258+
259+
git__free(git__user_agent_product);
260+
261+
if (new_agent) {
262+
git__user_agent_product = git__strdup(new_agent);
263+
264+
if (!git__user_agent_product)
265+
error = -1;
266+
} else {
267+
git__user_agent_product = NULL;
268+
}
220269
}
270+
break;
271+
272+
case GIT_OPT_GET_USER_AGENT_PRODUCT:
273+
{
274+
git_buf *out = va_arg(ap, git_buf *);
275+
git_str str = GIT_STR_INIT;
221276

277+
if ((error = git_buf_tostr(&str, out)) < 0 ||
278+
(error = git_str_puts(&str, git_settings__user_agent_product())) < 0)
279+
break;
280+
281+
error = git_buf_fromstr(out, &str);
282+
}
222283
break;
223284

224285
case GIT_OPT_ENABLE_STRICT_OBJECT_CREATION:
@@ -245,19 +306,6 @@ int git_libgit2_opts(int key, ...)
245306
#endif
246307
break;
247308

248-
case GIT_OPT_GET_USER_AGENT:
249-
{
250-
git_buf *out = va_arg(ap, git_buf *);
251-
git_str str = GIT_STR_INIT;
252-
253-
if ((error = git_buf_tostr(&str, out)) < 0 ||
254-
(error = git_str_puts(&str, git__user_agent)) < 0)
255-
break;
256-
257-
error = git_buf_fromstr(out, &str);
258-
}
259-
break;
260-
261309
case GIT_OPT_ENABLE_OFS_DELTA:
262310
git_smart__ofs_delta_enabled = (va_arg(ap, int) != 0);
263311
break;

src/libgit2/settings.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
extern int git_settings_global_init(void);
1111

1212
extern const char *git_settings__user_agent(void);
13+
extern const char *git_settings__user_agent_product(void);
1314

1415
#endif

src/libgit2/transports/http.h

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,4 @@
1515

1616
extern bool git_http__expect_continue;
1717

18-
GIT_INLINE(int) git_http__user_agent(git_str *buf)
19-
{
20-
const char *ua = git_settings__user_agent();
21-
22-
if (!ua)
23-
ua = "libgit2 " LIBGIT2_VERSION;
24-
25-
return git_str_printf(buf, "git/2.0 (%s)", ua);
26-
}
27-
2818
#endif

src/libgit2/transports/httpclient.c

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -651,6 +651,30 @@ static int puts_host_and_port(git_str *buf, git_net_url *url, bool force_port)
651651
return git_str_oom(buf) ? -1 : 0;
652652
}
653653

654+
static int append_user_agent(git_str *buf)
655+
{
656+
const char *product = git_settings__user_agent_product();
657+
const char *comment = git_settings__user_agent();
658+
659+
GIT_ASSERT(product && comment);
660+
661+
if (!*product)
662+
return 0;
663+
664+
git_str_puts(buf, "User-Agent: ");
665+
git_str_puts(buf, product);
666+
667+
if (*comment) {
668+
git_str_puts(buf, " (");
669+
git_str_puts(buf, comment);
670+
git_str_puts(buf, ")");
671+
}
672+
673+
git_str_puts(buf, "\r\n");
674+
675+
return git_str_oom(buf) ? -1 : 0;
676+
}
677+
654678
static int generate_connect_request(
655679
git_http_client *client,
656680
git_http_request *request)
@@ -665,9 +689,7 @@ static int generate_connect_request(
665689
puts_host_and_port(buf, &client->server.url, true);
666690
git_str_puts(buf, " HTTP/1.1\r\n");
667691

668-
git_str_puts(buf, "User-Agent: ");
669-
git_http__user_agent(buf);
670-
git_str_puts(buf, "\r\n");
692+
append_user_agent(buf);
671693

672694
git_str_puts(buf, "Host: ");
673695
puts_host_and_port(buf, &client->server.url, true);
@@ -711,9 +733,7 @@ static int generate_request(
711733

712734
git_str_puts(buf, " HTTP/1.1\r\n");
713735

714-
git_str_puts(buf, "User-Agent: ");
715-
git_http__user_agent(buf);
716-
git_str_puts(buf, "\r\n");
736+
append_user_agent(buf);
717737

718738
git_str_puts(buf, "Host: ");
719739
puts_host_and_port(buf, request->url, false);

src/libgit2/transports/winhttp.c

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,33 @@ static void CALLBACK winhttp_status(
746746
}
747747
}
748748

749+
static int user_agent(bool *exists, git_str *out)
750+
{
751+
const char *product = git_settings__user_agent_product();
752+
const char *comment = git_settings__user_agent();
753+
754+
GIT_ASSERT(product && comment);
755+
756+
if (!*product) {
757+
*exists = false;
758+
return 0;
759+
}
760+
761+
git_str_puts(out, product);
762+
763+
if (*comment) {
764+
git_str_puts(out, " (");
765+
git_str_puts(out, comment);
766+
git_str_puts(out, ")");
767+
}
768+
769+
if (git_str_oom(out))
770+
return -1;
771+
772+
*exists = true;
773+
return 0;
774+
}
775+
749776
static int winhttp_connect(
750777
winhttp_subtransport *t)
751778
{
@@ -757,6 +784,7 @@ static int winhttp_connect(
757784
int error = -1;
758785
int default_timeout = TIMEOUT_INFINITE;
759786
int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT;
787+
bool has_ua = true;
760788
DWORD protocols =
761789
WINHTTP_FLAG_SECURE_PROTOCOL_TLS1 |
762790
WINHTTP_FLAG_SECURE_PROTOCOL_TLS1_1 |
@@ -787,11 +815,11 @@ static int winhttp_connect(
787815
goto on_error;
788816
}
789817

790-
791-
if (git_http__user_agent(&ua) < 0)
818+
if (user_agent(&has_ua, &ua) < 0)
792819
goto on_error;
793820

794-
if (git_utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) {
821+
if (has_ua &&
822+
git_utf8_to_16_alloc(&wide_ua, git_str_cstr(&ua)) < 0) {
795823
git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters");
796824
goto on_error;
797825
}

tests/libgit2/core/useragent.c

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,52 @@
11
#include "clar_libgit2.h"
22
#include "settings.h"
33

4-
void test_core_useragent__get(void)
4+
static git_buf default_ua = GIT_BUF_INIT;
5+
static git_buf default_product = GIT_BUF_INIT;
6+
7+
void test_core_useragent__initialize(void)
8+
{
9+
cl_git_pass(git_libgit2_opts(GIT_OPT_GET_USER_AGENT, &default_ua));
10+
cl_git_pass(git_libgit2_opts(GIT_OPT_GET_USER_AGENT_PRODUCT, &default_product));
11+
}
12+
13+
void test_core_useragent__cleanup(void)
14+
{
15+
git_libgit2_opts(GIT_OPT_SET_USER_AGENT, NULL);
16+
git_libgit2_opts(GIT_OPT_SET_USER_AGENT_PRODUCT, NULL);
17+
18+
git_buf_dispose(&default_ua);
19+
git_buf_dispose(&default_product);
20+
}
21+
22+
void test_core_useragent__get_default(void)
23+
{
24+
cl_assert(default_ua.size);
25+
cl_assert(default_ua.ptr);
26+
cl_assert(git__prefixcmp(default_ua.ptr, "libgit2 ") == 0);
27+
28+
cl_assert(default_product.size);
29+
cl_assert(default_product.ptr);
30+
cl_assert(git__prefixcmp(default_product.ptr, "git/") == 0);
31+
}
32+
33+
void test_core_useragent__set(void)
534
{
6-
const char *custom_name = "super duper git";
7-
git_str buf = GIT_STR_INIT;
35+
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, "foo bar 4.24"));
36+
cl_assert_equal_s("foo bar 4.24", git_settings__user_agent());
37+
cl_assert_equal_s(default_product.ptr, git_settings__user_agent_product());
838

9-
cl_assert_equal_p(NULL, git_settings__user_agent());
10-
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, custom_name));
11-
cl_assert_equal_s(custom_name, git_settings__user_agent());
39+
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT_PRODUCT, "baz/2.2.3"));
40+
cl_assert_equal_s("foo bar 4.24", git_settings__user_agent());
41+
cl_assert_equal_s("baz/2.2.3", git_settings__user_agent_product());
1242

13-
cl_git_pass(git_libgit2_opts(GIT_OPT_GET_USER_AGENT, &buf));
14-
cl_assert_equal_s(custom_name, buf.ptr);
43+
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, ""));
44+
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT_PRODUCT, ""));
45+
cl_assert_equal_s("", git_settings__user_agent());
46+
cl_assert_equal_s("", git_settings__user_agent_product());
1547

16-
git_str_dispose(&buf);
48+
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, NULL));
49+
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT_PRODUCT, NULL));
50+
cl_assert_equal_s(default_ua.ptr, git_settings__user_agent());
51+
cl_assert_equal_s(default_product.ptr, git_settings__user_agent_product());
1752
}

0 commit comments

Comments
 (0)