summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--examples/network/clone.c1
-rw-r--r--include/git2/cred_helpers.h3
-rw-r--r--include/git2/transport.h3
-rw-r--r--src/netops.c37
-rw-r--r--src/netops.h8
-rw-r--r--src/transports/cred_helpers.c25
-rw-r--r--src/transports/git.c12
-rw-r--r--src/transports/http.c31
-rw-r--r--src/transports/winhttp.c33
-rw-r--r--src/util.h15
-rw-r--r--tests-clar/network/cred.c33
-rw-r--r--tests-clar/network/urlparse.c82
-rw-r--r--tests-clar/online/clone.c31
-rw-r--r--tests-clar/online/push.c8
14 files changed, 295 insertions, 27 deletions
diff --git a/examples/network/clone.c b/examples/network/clone.c
index 63072eea0..5b0a81073 100644
--- a/examples/network/clone.c
+++ b/examples/network/clone.c
@@ -61,6 +61,7 @@ static void checkout_progress(const char *path, size_t cur, size_t tot, void *pa
static int cred_acquire(git_cred **out,
const char * UNUSED(url),
+ const char * UNUSED(username_from_url),
unsigned int UNUSED(allowed_types),
void * UNUSED(payload))
{
diff --git a/include/git2/cred_helpers.h b/include/git2/cred_helpers.h
index 7c213c8dd..e3eb91d6c 100644
--- a/include/git2/cred_helpers.h
+++ b/include/git2/cred_helpers.h
@@ -34,6 +34,8 @@ typedef struct git_cred_userpass_payload {
*
* @param cred The newly created credential object.
* @param url The resource for which we are demanding a credential.
+ * @param username_from_url The username that was embedded in a "user@host"
+ * remote url, or NULL if not included.
* @param allowed_types A bitmask stating which cred types are OK to return.
* @param payload The payload provided when specifying this callback. (This is
* interpreted as a `git_cred_userpass_payload*`.)
@@ -41,6 +43,7 @@ typedef struct git_cred_userpass_payload {
GIT_EXTERN(int) git_cred_userpass(
git_cred **cred,
const char *url,
+ const char *user_from_url,
unsigned int allowed_types,
void *payload);
diff --git a/include/git2/transport.h b/include/git2/transport.h
index 4945ff151..469b43f72 100644
--- a/include/git2/transport.h
+++ b/include/git2/transport.h
@@ -62,6 +62,8 @@ GIT_EXTERN(int) git_cred_userpass_plaintext_new(
*
* @param cred The newly created credential object.
* @param url The resource for which we are demanding a credential.
+ * @param username_from_url The username that was embedded in a "user@host"
+ * remote url, or NULL if not included.
* @param allowed_types A bitmask stating which cred types are OK to return.
* @param payload The payload provided when specifying this callback.
* @return 0 for success or an error code for failure
@@ -69,6 +71,7 @@ GIT_EXTERN(int) git_cred_userpass_plaintext_new(
typedef int (*git_cred_acquire_cb)(
git_cred **cred,
const char *url,
+ const char *username_from_url,
unsigned int allowed_types,
void *payload);
diff --git a/src/netops.c b/src/netops.c
index fd83a1cc3..69179dd1c 100644
--- a/src/netops.c
+++ b/src/netops.c
@@ -577,27 +577,54 @@ int gitno_select_in(gitno_buffer *buf, long int sec, long int usec)
return select((int)buf->socket->socket + 1, &fds, NULL, NULL, &tv);
}
-int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port)
+int gitno_extract_url_parts(
+ char **host,
+ char **port,
+ char **username,
+ char **password,
+ const char *url,
+ const char *default_port)
{
- char *colon, *slash, *delim;
+ char *colon, *slash, *at, *end;
+ const char *start;
+
+ /*
+ *
+ * ==> [user[:pass]@]hostname.tld[:port]/resource
+ */
colon = strchr(url, ':');
slash = strchr(url, '/');
+ at = strchr(url, '@');
if (slash == NULL) {
giterr_set(GITERR_NET, "Malformed URL: missing /");
return -1;
}
+ start = url;
+ if (at && at < slash) {
+ start = at+1;
+ *username = git__substrdup(url, at - url);
+ }
+
+ if (colon && colon < at) {
+ git__free(*username);
+ *username = git__substrdup(url, colon-url);
+ *password = git__substrdup(colon+1, at-colon-1);
+ colon = strchr(at, ':');
+ }
+
if (colon == NULL) {
*port = git__strdup(default_port);
} else {
- *port = git__strndup(colon + 1, slash - colon - 1);
+ *port = git__substrdup(colon + 1, slash - colon - 1);
}
GITERR_CHECK_ALLOC(*port);
- delim = colon == NULL ? slash : colon;
- *host = git__strndup(url, delim - url);
+ end = colon == NULL ? slash : colon;
+
+ *host = git__substrdup(start, end - start);
GITERR_CHECK_ALLOC(*host);
return 0;
diff --git a/src/netops.h b/src/netops.h
index f8ff42c40..d352bf3b6 100644
--- a/src/netops.h
+++ b/src/netops.h
@@ -66,6 +66,12 @@ int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags);
int gitno_close(gitno_socket *s);
int gitno_select_in(gitno_buffer *buf, long int sec, long int usec);
-int gitno_extract_host_and_port(char **host, char **port, const char *url, const char *default_port);
+int gitno_extract_url_parts(
+ char **host,
+ char **port,
+ char **username,
+ char **password,
+ const char *url,
+ const char *default_port);
#endif
diff --git a/src/transports/cred_helpers.c b/src/transports/cred_helpers.c
index 8d8eb9990..d420e3e3c 100644
--- a/src/transports/cred_helpers.c
+++ b/src/transports/cred_helpers.c
@@ -11,17 +11,38 @@
int git_cred_userpass(
git_cred **cred,
const char *url,
+ const char *user_from_url,
unsigned int allowed_types,
void *payload)
{
git_cred_userpass_payload *userpass = (git_cred_userpass_payload*)payload;
+ const char *effective_username = NULL;
GIT_UNUSED(url);
- if (!userpass || !userpass->username || !userpass->password) return -1;
+ if (!userpass || !userpass->password) return -1;
+
+ /* Username resolution: a username can be passed with the URL, the
+ * credentials payload, or both. Here's what we do. Note that if we get
+ * this far, we know that any password the url may contain has already
+ * failed at least once, so we ignore it.
+ *
+ * | Payload | URL | Used |
+ * +-------------+----------+-----------+
+ * | yes | no | payload |
+ * | yes | yes | payload |
+ * | no | yes | url |
+ * | no | no | FAIL |
+ */
+ if (userpass->username)
+ effective_username = userpass->username;
+ else if (user_from_url)
+ effective_username = user_from_url;
+ else
+ return -1;
if ((GIT_CREDTYPE_USERPASS_PLAINTEXT & allowed_types) == 0 ||
- git_cred_userpass_plaintext_new(cred, userpass->username, userpass->password) < 0)
+ git_cred_userpass_plaintext_new(cred, effective_username, userpass->password) < 0)
return -1;
return 0;
diff --git a/src/transports/git.c b/src/transports/git.c
index ba6dbfea9..3a0b86345 100644
--- a/src/transports/git.c
+++ b/src/transports/git.c
@@ -179,7 +179,7 @@ static int _git_uploadpack_ls(
const char *url,
git_smart_subtransport_stream **stream)
{
- char *host, *port;
+ char *host, *port, *user=NULL, *pass=NULL;
git_stream *s;
*stream = NULL;
@@ -192,7 +192,7 @@ static int _git_uploadpack_ls(
s = (git_stream *)*stream;
- if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0)
+ if (gitno_extract_url_parts(&host, &port, &user, &pass, url, GIT_DEFAULT_PORT) < 0)
goto on_error;
if (gitno_connect(&s->socket, host, port, 0) < 0)
@@ -201,6 +201,8 @@ static int _git_uploadpack_ls(
t->current_stream = s;
git__free(host);
git__free(port);
+ git__free(user);
+ git__free(pass);
return 0;
on_error:
@@ -233,7 +235,7 @@ static int _git_receivepack_ls(
const char *url,
git_smart_subtransport_stream **stream)
{
- char *host, *port;
+ char *host, *port, *user=NULL, *pass=NULL;
git_stream *s;
*stream = NULL;
@@ -246,7 +248,7 @@ static int _git_receivepack_ls(
s = (git_stream *)*stream;
- if (gitno_extract_host_and_port(&host, &port, url, GIT_DEFAULT_PORT) < 0)
+ if (gitno_extract_url_parts(&host, &port, &user, &pass, url, GIT_DEFAULT_PORT) < 0)
goto on_error;
if (gitno_connect(&s->socket, host, port, 0) < 0)
@@ -255,6 +257,8 @@ static int _git_receivepack_ls(
t->current_stream = s;
git__free(host);
git__free(port);
+ git__free(user);
+ git__free(pass);
return 0;
on_error:
diff --git a/src/transports/http.c b/src/transports/http.c
index 96a9c3942..964bafb19 100644
--- a/src/transports/http.c
+++ b/src/transports/http.c
@@ -60,7 +60,10 @@ typedef struct {
const char *path;
char *host;
char *port;
+ char *user_from_url;
+ char *pass_from_url;
git_cred *cred;
+ git_cred *url_cred;
http_authmechanism_t auth_mechanism;
unsigned connected : 1,
use_ssl : 1;
@@ -144,6 +147,14 @@ static int gen_request(
apply_basic_credential(buf, t->cred) < 0)
return -1;
+ /* Use url-parsed basic auth if username and password are both provided */
+ if (!t->cred && t->user_from_url && t->pass_from_url) {
+ if (!t->url_cred &&
+ git_cred_userpass_plaintext_new(&t->url_cred, t->user_from_url, t->pass_from_url) < 0)
+ return -1;
+ if (apply_basic_credential(buf, t->url_cred) < 0) return -1;
+ }
+
git_buf_puts(buf, "\r\n");
if (git_buf_oom(buf))
@@ -256,6 +267,7 @@ static int on_headers_complete(http_parser *parser)
if (t->owner->cred_acquire_cb(&t->cred,
t->owner->url,
+ t->user_from_url,
allowed_types,
t->owner->cred_acquire_payload) < 0)
return PARSE_ERROR_GENERIC;
@@ -742,8 +754,8 @@ static int http_action(
if (!default_port)
return -1;
- if ((ret = gitno_extract_host_and_port(&t->host, &t->port,
- url, default_port)) < 0)
+ if ((ret = gitno_extract_url_parts(&t->host, &t->port,
+ &t->user_from_url, &t->pass_from_url, url, default_port)) < 0)
return ret;
t->path = strchr(url, '/');
@@ -809,6 +821,11 @@ static int http_close(git_smart_subtransport *subtransport)
t->cred = NULL;
}
+ if (t->url_cred) {
+ t->url_cred->free(t->url_cred);
+ t->url_cred = NULL;
+ }
+
if (t->host) {
git__free(t->host);
t->host = NULL;
@@ -819,6 +836,16 @@ static int http_close(git_smart_subtransport *subtransport)
t->port = NULL;
}
+ if (t->user_from_url) {
+ git__free(t->user_from_url);
+ t->user_from_url = NULL;
+ }
+
+ if (t->pass_from_url) {
+ git__free(t->pass_from_url);
+ t->pass_from_url = NULL;
+ }
+
return 0;
}
diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
index 808f6afaa..64bfece34 100644
--- a/src/transports/winhttp.c
+++ b/src/transports/winhttp.c
@@ -73,7 +73,10 @@ typedef struct {
const char *path;
char *host;
char *port;
+ char *user_from_url;
+ char *pass_from_url;
git_cred *cred;
+ git_cred *url_cred;
int auth_mechanism;
HINTERNET session;
HINTERNET connection;
@@ -250,6 +253,16 @@ static int winhttp_stream_connect(winhttp_stream *s)
apply_basic_credential(s->request, t->cred) < 0)
goto on_error;
+ /* If no other credentials have been applied and the URL has username and
+ * password, use those */
+ if (!t->cred && t->user_from_url && t->pass_from_url) {
+ if (!t->url_cred &&
+ git_cred_userpass_plaintext_new(&t->url_cred, t->user_from_url, t->pass_from_url) < 0)
+ goto on_error;
+ if (apply_basic_credential(s->request, t->url_cred) < 0)
+ goto on_error;
+ }
+
/* We've done everything up to calling WinHttpSendRequest. */
error = 0;
@@ -447,7 +460,7 @@ replay:
if (allowed_types &&
(!t->cred || 0 == (t->cred->credtype & allowed_types))) {
- if (t->owner->cred_acquire_cb(&t->cred, t->owner->url, allowed_types, t->owner->cred_acquire_payload) < 0)
+ if (t->owner->cred_acquire_cb(&t->cred, t->owner->url, t->user_from_url, allowed_types, t->owner->cred_acquire_payload) < 0)
return -1;
assert(t->cred);
@@ -788,7 +801,8 @@ static int winhttp_connect(
t->use_ssl = 1;
}
- if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0)
+ if ((ret = gitno_extract_url_parts(&t->host, &t->port, &t->user_from_url,
+ &t->pass_from_url, url, default_port)) < 0)
return ret;
t->path = strchr(url, '/');
@@ -944,11 +958,26 @@ static int winhttp_close(git_smart_subtransport *subtransport)
t->port = NULL;
}
+ if (t->user_from_url) {
+ git__free(t->user_from_url);
+ t->user_from_url = NULL;
+ }
+
+ if (t->pass_from_url) {
+ git__free(t->pass_from_url);
+ t->pass_from_url = NULL;
+ }
+
if (t->cred) {
t->cred->free(t->cred);
t->cred = NULL;
}
+ if (t->url_cred) {
+ t->url_cred->free(t->url_cred);
+ t->url_cred = NULL;
+ }
+
if (t->connection) {
if (!WinHttpCloseHandle(t->connection)) {
giterr_set(GITERR_OS, "Unable to close connection");
diff --git a/src/util.h b/src/util.h
index e75d777a8..351ff422b 100644
--- a/src/util.h
+++ b/src/util.h
@@ -51,11 +51,7 @@ GIT_INLINE(char *) git__strndup(const char *str, size_t n)
while (length < n && str[length])
++length;
- ptr = (char*)malloc(length + 1);
- if (!ptr) {
- giterr_set_oom();
- return NULL;
- }
+ ptr = (char*)git__malloc(length + 1);
if (length)
memcpy(ptr, str, length);
@@ -65,6 +61,15 @@ GIT_INLINE(char *) git__strndup(const char *str, size_t n)
return ptr;
}
+/* NOTE: This doesn't do null or '\0' checking. Watch those boundaries! */
+GIT_INLINE(char *) git__substrdup(const char *start, size_t n)
+{
+ char *ptr = (char*)git__malloc(n+1);
+ memcpy(ptr, start, n);
+ ptr[n] = '\0';
+ return ptr;
+}
+
GIT_INLINE(void *) git__realloc(void *ptr, size_t size)
{
void *new_ptr = realloc(ptr, size);
diff --git a/tests-clar/network/cred.c b/tests-clar/network/cred.c
index b7f45c23b..6994cc0c3 100644
--- a/tests-clar/network/cred.c
+++ b/tests-clar/network/cred.c
@@ -6,14 +6,14 @@ void test_network_cred__stock_userpass_validates_args(void)
{
git_cred_userpass_payload payload = {0};
- cl_git_fail(git_cred_userpass(NULL, NULL, 0, NULL));
+ cl_git_fail(git_cred_userpass(NULL, NULL, NULL, 0, NULL));
payload.username = "user";
- cl_git_fail(git_cred_userpass(NULL, NULL, 0, &payload));
+ cl_git_fail(git_cred_userpass(NULL, NULL, NULL, 0, &payload));
payload.username = NULL;
payload.username = "pass";
- cl_git_fail(git_cred_userpass(NULL, NULL, 0, &payload));
+ cl_git_fail(git_cred_userpass(NULL, NULL, NULL, 0, &payload));
}
void test_network_cred__stock_userpass_validates_that_method_is_allowed(void)
@@ -21,7 +21,30 @@ void test_network_cred__stock_userpass_validates_that_method_is_allowed(void)
git_cred *cred;
git_cred_userpass_payload payload = {"user", "pass"};
- cl_git_fail(git_cred_userpass(&cred, NULL, 0, &payload));
- cl_git_pass(git_cred_userpass(&cred, NULL, GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload));
+ cl_git_fail(git_cred_userpass(&cred, NULL, NULL, 0, &payload));
+ cl_git_pass(git_cred_userpass(&cred, NULL, NULL, GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload));
+ cred->free(cred);
+}
+
+void test_network_cred__stock_userpass_properly_handles_username_in_url(void)
+{
+ git_cred *cred;
+ git_cred_userpass_plaintext *plain;
+ git_cred_userpass_payload payload = {"alice", "password"};
+
+ cl_git_pass(git_cred_userpass(&cred, NULL, NULL, GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload));
+ plain = (git_cred_userpass_plaintext*)cred;
+ cl_assert_equal_s(plain->username, "alice");
+ cred->free(cred);
+
+ cl_git_pass(git_cred_userpass(&cred, NULL, "bob", GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload));
+ plain = (git_cred_userpass_plaintext*)cred;
+ cl_assert_equal_s(plain->username, "alice");
+ cred->free(cred);
+
+ payload.username = NULL;
+ cl_git_pass(git_cred_userpass(&cred, NULL, "bob", GIT_CREDTYPE_USERPASS_PLAINTEXT, &payload));
+ plain = (git_cred_userpass_plaintext*)cred;
+ cl_assert_equal_s(plain->username, "bob");
cred->free(cred);
}
diff --git a/tests-clar/network/urlparse.c b/tests-clar/network/urlparse.c
new file mode 100644
index 000000000..84d0bfb88
--- /dev/null
+++ b/tests-clar/network/urlparse.c
@@ -0,0 +1,82 @@
+#include "clar_libgit2.h"
+#include "netops.h"
+
+char *host, *port, *user, *pass;
+
+void test_network_urlparse__initialize(void)
+{
+ host = port = user = pass = NULL;
+}
+
+void test_network_urlparse__cleanup(void)
+{
+#define FREE_AND_NULL(x) if (x) { git__free(x); x = NULL; }
+ FREE_AND_NULL(host);
+ FREE_AND_NULL(port);
+ FREE_AND_NULL(user);
+ FREE_AND_NULL(pass);
+}
+
+void test_network_urlparse__trivial(void)
+{
+ cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass,
+ "example.com/resource", "8080"));
+ cl_assert_equal_s(host, "example.com");
+ cl_assert_equal_s(port, "8080");
+ cl_assert_equal_sz(user, NULL);
+ cl_assert_equal_sz(pass, NULL);
+}
+
+void test_network_urlparse__user(void)
+{
+ cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass,
+ "user@example.com/resource", "8080"));
+ cl_assert_equal_s(host, "example.com");
+ cl_assert_equal_s(port, "8080");
+ cl_assert_equal_s(user, "user");
+ cl_assert_equal_sz(pass, NULL);
+}
+
+void test_network_urlparse__user_pass(void)
+{
+ /* user:pass@hostname.tld/resource */
+ cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass,
+ "user:pass@example.com/resource", "8080"));
+ cl_assert_equal_s(host, "example.com");
+ cl_assert_equal_s(port, "8080");
+ cl_assert_equal_s(user, "user");
+ cl_assert_equal_s(pass, "pass");
+}
+
+void test_network_urlparse__port(void)
+{
+ /* hostname.tld:port/resource */
+ cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass,
+ "example.com:9191/resource", "8080"));
+ cl_assert_equal_s(host, "example.com");
+ cl_assert_equal_s(port, "9191");
+ cl_assert_equal_sz(user, NULL);
+ cl_assert_equal_sz(pass, NULL);
+}
+
+void test_network_urlparse__user_port(void)
+{
+ /* user@hostname.tld:port/resource */
+ cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass,
+ "user@example.com:9191/resource", "8080"));
+ cl_assert_equal_s(host, "example.com");
+ cl_assert_equal_s(port, "9191");
+ cl_assert_equal_s(user, "user");
+ cl_assert_equal_sz(pass, NULL);
+}
+
+void test_network_urlparse__user_pass_port(void)
+{
+ /* user:pass@hostname.tld:port/resource */
+ cl_git_pass(gitno_extract_url_parts(&host, &port, &user, &pass,
+ "user:pass@example.com:9191/resource", "8080"));
+ cl_assert_equal_s(host, "example.com");
+ cl_assert_equal_s(port, "9191");
+ cl_assert_equal_s(user, "user");
+ cl_assert_equal_s(pass, "pass");
+}
diff --git a/tests-clar/online/clone.c b/tests-clar/online/clone.c
index 8226bd054..6a46fa511 100644
--- a/tests-clar/online/clone.c
+++ b/tests-clar/online/clone.c
@@ -6,6 +6,9 @@
#define LIVE_REPO_URL "http://github.com/libgit2/TestGitRepository"
#define LIVE_EMPTYREPO_URL "http://github.com/libgit2/TestEmptyRepository"
+#define BB_REPO_URL "https://libgit2@bitbucket.org/libgit2/testgitrepository.git"
+#define BB_REPO_URL_WITH_PASS "https://libgit2:libgit2@bitbucket.org/libgit2/testgitrepository.git"
+#define BB_REPO_URL_WITH_WRONG_PASS "https://libgit2:wrong@bitbucket.org/libgit2/testgitrepository.git"
static git_repository *g_repo;
static git_clone_options g_options;
@@ -150,4 +153,32 @@ void test_online_clone__credentials(void)
g_options.cred_acquire_payload = &user_pass;
cl_git_pass(git_clone(&g_repo, remote_url, "./foo", &g_options));
+ git_repository_free(g_repo); g_repo = NULL;
+ cl_fixture_cleanup("./foo");
+}
+
+void test_online_clone__bitbucket_style(void)
+{
+ git_cred_userpass_payload user_pass = {
+ "libgit2", "libgit2"
+ };
+
+ g_options.cred_acquire_cb = git_cred_userpass;
+ g_options.cred_acquire_payload = &user_pass;
+
+ cl_git_pass(git_clone(&g_repo, BB_REPO_URL, "./foo", &g_options));
+ git_repository_free(g_repo); g_repo = NULL;
+ cl_fixture_cleanup("./foo");
+
+ /* User and pass from URL */
+ user_pass.password = "wrong";
+ cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_PASS, "./foo", &g_options));
+ git_repository_free(g_repo); g_repo = NULL;
+ cl_fixture_cleanup("./foo");
+
+ /* Wrong password in URL, fall back to user_pass */
+ user_pass.password = "libgit2";
+ cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_WRONG_PASS, "./foo", &g_options));
+ git_repository_free(g_repo); g_repo = NULL;
+ cl_fixture_cleanup("./foo");
}
diff --git a/tests-clar/online/push.c b/tests-clar/online/push.c
index 8f92cdd5e..56183473a 100644
--- a/tests-clar/online/push.c
+++ b/tests-clar/online/push.c
@@ -30,9 +30,15 @@ static git_oid _tag_tree;
static git_oid _tag_blob;
static git_oid _tag_lightweight;
-static int cred_acquire_cb(git_cred **cred, const char *url, unsigned int allowed_types, void *payload)
+static int cred_acquire_cb(
+ git_cred **cred,
+ const char *url,
+ const char *user_from_url,
+ unsigned int allowed_types,
+ void *payload)
{
GIT_UNUSED(url);
+ GIT_UNUSED(user_from_url);
*((bool*)payload) = true;