Commit ecdc04287a9a245b9947aee21a86dc2f09a1bdcf

Carlos Martín Nieto 2015-11-12T19:20:36

Merge pull request #3448 from libgit2/cmn/custom-agent Support setting custom user-agent

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 359e78d..7d5a416 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,9 @@ v0.23 + 1
 * Symlinks are now followed when locking a file, which can be
   necessary when multiple worktrees share a base repository.
 
+* You can now set your own user-agent to be sent for HTTP requests by
+  using the `GIT_OPT_SET_USER_AGENT` with `git_libgit2_opts()`.
+
 ### API additions
 
 * `git_config_lock()` has been added, which allow for
diff --git a/include/git2/common.h b/include/git2/common.h
index 5779061..bf341e7 100644
--- a/include/git2/common.h
+++ b/include/git2/common.h
@@ -145,6 +145,7 @@ typedef enum {
 	GIT_OPT_GET_TEMPLATE_PATH,
 	GIT_OPT_SET_TEMPLATE_PATH,
 	GIT_OPT_SET_SSL_CERT_LOCATIONS,
+	GIT_OPT_SET_USER_AGENT,
 } git_libgit2_opt_t;
 
 /**
@@ -240,6 +241,8 @@ typedef enum {
  *		>
  * 		> Either parameter may be `NULL`, but not both.
  *
+ *	* opts(GIT_OPT_SET_USER_AGENT, const char *user_agent)
+ *
  * @param option Option key
  * @param ... value to set the option
  * @return 0 on success, <0 on failure
diff --git a/src/global.c b/src/global.c
index 3d37ee4..0eab8d5 100644
--- a/src/global.c
+++ b/src/global.c
@@ -31,6 +31,7 @@ static git_mutex *openssl_locks;
 static git_global_shutdown_fn git__shutdown_callbacks[MAX_SHUTDOWN_CB];
 static git_atomic git__n_shutdown_callbacks;
 static git_atomic git__n_inits;
+char *git__user_agent;
 
 void git__on_shutdown(git_global_shutdown_fn callback)
 {
@@ -269,6 +270,8 @@ int git_libgit2_shutdown(void)
 		git_win32__crtdbg_stacktrace_cleanup();
 		git_win32__stack_cleanup();
 #endif
+
+		git__free(git__user_agent);
 	}
 
 	/* Exit the lock */
@@ -369,6 +372,7 @@ int git_libgit2_shutdown(void)
 
 	git__global_state_cleanup(ptr);
 	git__free(ptr);
+	git__free(git__user_agent);
 
 	pthread_key_delete(_tls_key);
 	git_mutex_free(&git__mwindow_mutex);
@@ -423,6 +427,7 @@ int git_libgit2_shutdown(void)
 	git__shutdown();
 	git__global_state_cleanup(&__state);
 	uninit_ssl();
+	git__free(git__user_agent);
 
 	return 0;
 }
diff --git a/src/global.h b/src/global.h
index 37e909a..9fdcee5 100644
--- a/src/global.h
+++ b/src/global.h
@@ -35,4 +35,6 @@ extern void git__on_shutdown(git_global_shutdown_fn callback);
 
 extern void git__free_tls_data(void);
 
+extern const char *git_libgit2__user_agent(void);
+
 #endif
diff --git a/src/settings.c b/src/settings.c
index 2097ca3..030d285 100644
--- a/src/settings.c
+++ b/src/settings.c
@@ -57,6 +57,13 @@ static int config_level_to_sysdir(int config_level)
 	return val;
 }
 
+extern char *git__user_agent;
+
+const char *git_libgit2__user_agent()
+{
+	return git__user_agent;
+}
+
 int git_libgit2_opts(int key, ...)
 {
 	int error = 0;
@@ -153,6 +160,15 @@ int git_libgit2_opts(int key, ...)
 		error = -1;
 #endif
 		break;
+	case GIT_OPT_SET_USER_AGENT:
+		git__free(git__user_agent);
+		git__user_agent = git__strdup(va_arg(ap, const char *));
+		if (!git__user_agent) {
+			giterr_set_oom();
+			error = -1;
+		}
+
+		break;
 	}
 
 	va_end(ap);
diff --git a/src/transports/http.c b/src/transports/http.c
index e5f2b9f..88b124b 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -10,6 +10,7 @@
 #include "http_parser.h"
 #include "buffer.h"
 #include "netops.h"
+#include "global.h"
 #include "remote.h"
 #include "smart.h"
 #include "auth.h"
@@ -186,6 +187,16 @@ static int apply_credentials(git_buf *buf, http_subtransport *t)
 	return context->next_token(buf, context, cred);
 }
 
+static const char *user_agent(void)
+{
+	const char *custom = git_libgit2__user_agent();
+
+	if (custom)
+		return custom;
+
+	return "libgit2 " LIBGIT2_VERSION;
+}
+
 static int gen_request(
 	git_buf *buf,
 	http_stream *s,
@@ -197,7 +208,7 @@ static int gen_request(
 
 	git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, path, s->service_url);
 
-	git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");
+	git_buf_printf(buf, "User-Agent: git/1.0 (%s)\r\n", user_agent());
 	git_buf_printf(buf, "Host: %s\r\n", t->connection_data.host);
 
 	if (s->chunked || content_length > 0) {
diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
index b364e90..77d939b 100644
--- a/src/transports/winhttp.c
+++ b/src/transports/winhttp.c
@@ -15,6 +15,7 @@
 #include "smart.h"
 #include "remote.h"
 #include "repository.h"
+#include "global.h"
 
 #include <wincrypt.h>
 #include <winhttp.h>
@@ -567,12 +568,28 @@ static int winhttp_close_connection(winhttp_subtransport *t)
 	return ret;
 }
 
+static int user_agent(git_buf *ua)
+{
+	const char *custom = git_libgit2__user_agent();
+
+	git_buf_clear(ua);
+	git_buf_PUTS(ua, "git/1.0 (");
+
+	if (custom)
+		git_buf_puts(ua, custom);
+	else
+		git_buf_PUTS(ua, "libgit2 " LIBGIT2_VERSION);
+
+	return git_buf_putc(ua, ')');
+}
+
 static int winhttp_connect(
 	winhttp_subtransport *t)
 {
-	wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")";
 	wchar_t *wide_host;
 	int32_t port;
+	wchar_t *wide_ua;
+	git_buf ua = GIT_BUF_INIT;
 	int error = -1;
 	int default_timeout = TIMEOUT_INFINITE;
 	int default_connect_timeout = DEFAULT_CONNECT_TIMEOUT;
@@ -590,9 +607,23 @@ static int winhttp_connect(
 		return -1;
 	}
 
+	if ((error = user_agent(&ua)) < 0) {
+		git__free(wide_host);
+		return error;
+	}
+
+	if (git__utf8_to_16_alloc(&wide_ua, git_buf_cstr(&ua)) < 0) {
+		giterr_set(GITERR_OS, "Unable to convert host to wide characters");
+		git__free(wide_host);
+		git_buf_free(&ua);
+		return -1;
+	}
+
+	git_buf_free(&ua);
+
 	/* Establish session */
 	t->session = WinHttpOpen(
-		ua,
+		wide_ua,
 		WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
 		WINHTTP_NO_PROXY_NAME,
 		WINHTTP_NO_PROXY_BYPASS,
@@ -628,6 +659,7 @@ on_error:
 		winhttp_close_connection(t);
 
 	git__free(wide_host);
+	git__free(wide_ua);
 
 	return error;
 }
diff --git a/tests/core/useragent.c b/tests/core/useragent.c
new file mode 100644
index 0000000..6d06693
--- /dev/null
+++ b/tests/core/useragent.c
@@ -0,0 +1,11 @@
+#include "clar_libgit2.h"
+#include "global.h"
+
+void test_core_useragent__get(void)
+{
+	const char *custom_name = "super duper git";
+
+	cl_assert_equal_p(NULL, git_libgit2__user_agent());
+	cl_git_pass(git_libgit2_opts(GIT_OPT_SET_USER_AGENT, custom_name));
+	cl_assert_equal_s(custom_name, git_libgit2__user_agent());
+}