diff options
| -rw-r--r-- | examples/network/clone.c | 1 | ||||
| -rw-r--r-- | include/git2/cred_helpers.h | 3 | ||||
| -rw-r--r-- | include/git2/transport.h | 3 | ||||
| -rw-r--r-- | src/netops.c | 37 | ||||
| -rw-r--r-- | src/netops.h | 8 | ||||
| -rw-r--r-- | src/transports/cred_helpers.c | 25 | ||||
| -rw-r--r-- | src/transports/git.c | 12 | ||||
| -rw-r--r-- | src/transports/http.c | 31 | ||||
| -rw-r--r-- | src/transports/winhttp.c | 33 | ||||
| -rw-r--r-- | src/util.h | 15 | ||||
| -rw-r--r-- | tests-clar/network/cred.c | 33 | ||||
| -rw-r--r-- | tests-clar/network/urlparse.c | 82 | ||||
| -rw-r--r-- | tests-clar/online/clone.c | 31 | ||||
| -rw-r--r-- | tests-clar/online/push.c | 8 |
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; |
