From 03b0642a4b5327a6dba6788311d9e5135b91461e Mon Sep 17 00:00:00 2001 From: Christopher Nielsen Date: Mon, 24 Jul 2023 20:37:34 -0400 Subject: [PATCH 01/15] stransport: macos: replace errSSLNetworkTimeout, with hard-coded value - Constant only available in 10.13+, causing build failures for older macOS releases Fixes: https://github.com/libgit2/libgit2/issues/6606 --- src/libgit2/streams/stransport.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libgit2/streams/stransport.c b/src/libgit2/streams/stransport.c index d956df84d10..7a3585e246b 100644 --- a/src/libgit2/streams/stransport.c +++ b/src/libgit2/streams/stransport.c @@ -162,7 +162,7 @@ static OSStatus write_cb(SSLConnectionRef conn, const void *data, size_t *len) if (ret < 0) { st->error = ret; return (ret == GIT_TIMEOUT) ? - errSSLNetworkTimeout : + -9853 /* errSSLNetworkTimeout */: -36 /* ioErr */; } @@ -214,7 +214,7 @@ static OSStatus read_cb(SSLConnectionRef conn, void *data, size_t *len) if (ret < 0) { st->error = ret; error = (ret == GIT_TIMEOUT) ? - errSSLNetworkTimeout : + -9853 /* errSSLNetworkTimeout */: -36 /* ioErr */; break; } else if (ret == 0) { From 085a169c1d73e02888708652d7772b3bc1f1b28b Mon Sep 17 00:00:00 2001 From: David Runge Date: Fri, 21 Jul 2023 16:18:39 +0200 Subject: [PATCH 02/15] fix: Add missing include for oidarray. Add a missing include for `git2/oidarray.h` so build doesn't fail on using `git_oidarray` when using custom transports. Fixes #6607 --- include/git2/sys/transport.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/git2/sys/transport.h b/include/git2/sys/transport.h index 96a35d08cb2..370ca45d570 100644 --- a/include/git2/sys/transport.h +++ b/include/git2/sys/transport.h @@ -9,6 +9,7 @@ #define INCLUDE_sys_git_transport_h #include "git2/net.h" +#include "git2/oidarray.h" #include "git2/proxy.h" #include "git2/remote.h" #include "git2/strarray.h" From 86a4a2655fc66ff3ae65cc3c689c52f7bb37a1ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Mart=C3=ADn=20Nieto?= Date: Tue, 18 Jul 2023 12:31:46 +0200 Subject: [PATCH 03/15] repository: make cleanup safe for re-use with grafts We are allowed to call `git_repository__cleanup` multiple times, and this happens e.g. in rugged if we want to free up resources before GC gets around to them. This means that we cannot leave dangling pointers in it, which we were doing with the grafts. Fix this by setting the pointers to NULL after freeing the resources. --- src/libgit2/repository.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/libgit2/repository.c b/src/libgit2/repository.c index 97f776c4a34..05ece6efc41 100644 --- a/src/libgit2/repository.c +++ b/src/libgit2/repository.c @@ -153,7 +153,9 @@ int git_repository__cleanup(git_repository *repo) git_cache_clear(&repo->objects); git_attr_cache_flush(repo); git_grafts_free(repo->grafts); + repo->grafts = NULL; git_grafts_free(repo->shallow_grafts); + repo->shallow_grafts = NULL; set_config(repo, NULL); set_index(repo, NULL); From ec2439bf393c786451d248070e9f81fa08b62151 Mon Sep 17 00:00:00 2001 From: steven9724 <116153756+steven9724@users.noreply.github.com> Date: Tue, 20 Jun 2023 10:36:24 +0100 Subject: [PATCH 04/15] ssh: fix known_hosts leak in _git_ssh_setup_conn --- src/libgit2/transports/ssh.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/libgit2/transports/ssh.c b/src/libgit2/transports/ssh.c index af618e1a6ed..de63d454ee6 100644 --- a/src/libgit2/transports/ssh.c +++ b/src/libgit2/transports/ssh.c @@ -877,11 +877,12 @@ static int _git_ssh_setup_conn( t->current_stream = s; done: + if (known_hosts) + libssh2_knownhost_free(known_hosts); + if (error < 0) { ssh_stream_free(*stream); - if (known_hosts) - libssh2_knownhost_free(known_hosts); if (session) libssh2_session_free(session); } From 402f63a46be9f44e2d4da28999ed59a9f3eb7277 Mon Sep 17 00:00:00 2001 From: lmcglash Date: Mon, 17 Jul 2023 21:41:04 +0100 Subject: [PATCH 05/15] Return an error for invalid proxy URLs instead of crashing. --- src/libgit2/transports/http.c | 13 +++++++++++-- tests/libgit2/online/clone.c | 13 +++++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/libgit2/transports/http.c b/src/libgit2/transports/http.c index 0534503bf25..a7aea6b377e 100644 --- a/src/libgit2/transports/http.c +++ b/src/libgit2/transports/http.c @@ -334,10 +334,19 @@ static int lookup_proxy( return 0; } - if (!proxy || - (error = git_net_url_parse(&transport->proxy.url, proxy)) < 0) + if (!proxy) goto done; + + if ((error = git_net_url_parse(&transport->proxy.url, proxy) < 0)) + goto done; + + if (!git_net_url_valid(&transport->proxy.url)) { + git_error_set(GIT_ERROR_HTTP, "invalid proxy url: %s", proxy); + error = GIT_EINVALIDSPEC; + goto done; + } + *out_use = true; done: diff --git a/tests/libgit2/online/clone.c b/tests/libgit2/online/clone.c index dbcac50ae6e..5c714d1aa9d 100644 --- a/tests/libgit2/online/clone.c +++ b/tests/libgit2/online/clone.c @@ -968,6 +968,19 @@ static int proxy_cert_cb(git_cert *cert, int valid, const char *host, void *payl return valid ? 0 : GIT_ECERTIFICATE; } +void test_online_clone__proxy_invalid_url(void) +{ + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; + g_options.fetch_opts.proxy_opts.credentials = proxy_cred_cb; + g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb; + + g_options.fetch_opts.proxy_opts.url = "noschemeorport"; + cl_git_fail_with(GIT_EINVALIDSPEC, git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); + + g_options.fetch_opts.proxy_opts.url = "noscheme:8080"; + cl_git_fail_with(GIT_EINVALIDSPEC, git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); +} + void test_online_clone__proxy_credentials_request(void) { git_str url = GIT_STR_INIT; From 5a13d4ca56256d14c6f56a6ab8b80a3075192fdd Mon Sep 17 00:00:00 2001 From: lmcglash Date: Wed, 19 Jul 2023 14:33:39 +0100 Subject: [PATCH 06/15] Validate proxy URL on Windows. --- src/libgit2/transports/http.c | 11 ++++------- src/libgit2/transports/winhttp.c | 2 +- tests/libgit2/online/clone.c | 4 ++-- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/libgit2/transports/http.c b/src/libgit2/transports/http.c index a7aea6b377e..eed51914549 100644 --- a/src/libgit2/transports/http.c +++ b/src/libgit2/transports/http.c @@ -334,16 +334,13 @@ static int lookup_proxy( return 0; } - if (!proxy) - goto done; - - - if ((error = git_net_url_parse(&transport->proxy.url, proxy) < 0)) + if (!proxy || + (error = git_net_url_parse(&transport->proxy.url, proxy)) < 0) goto done; if (!git_net_url_valid(&transport->proxy.url)) { - git_error_set(GIT_ERROR_HTTP, "invalid proxy url: %s", proxy); - error = GIT_EINVALIDSPEC; + git_error_set(GIT_ERROR_HTTP, "invalid URL: '%s'", proxy); + error = -1; goto done; } diff --git a/src/libgit2/transports/winhttp.c b/src/libgit2/transports/winhttp.c index 27e0fb6f7e9..b7ffb9512ae 100644 --- a/src/libgit2/transports/winhttp.c +++ b/src/libgit2/transports/winhttp.c @@ -446,7 +446,7 @@ static int winhttp_stream_connect(winhttp_stream *s) if ((error = git_net_url_parse(&t->proxy.url, proxy_url)) < 0) goto on_error; - if (strcmp(t->proxy.url.scheme, "http") != 0 && strcmp(t->proxy.url.scheme, "https") != 0) { + if (!git_net_url_valid(&t->proxy.url)) { git_error_set(GIT_ERROR_HTTP, "invalid URL: '%s'", proxy_url); error = -1; goto on_error; diff --git a/tests/libgit2/online/clone.c b/tests/libgit2/online/clone.c index 5c714d1aa9d..0c53514a425 100644 --- a/tests/libgit2/online/clone.c +++ b/tests/libgit2/online/clone.c @@ -975,10 +975,10 @@ void test_online_clone__proxy_invalid_url(void) g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb; g_options.fetch_opts.proxy_opts.url = "noschemeorport"; - cl_git_fail_with(GIT_EINVALIDSPEC, git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); + cl_git_fail(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); g_options.fetch_opts.proxy_opts.url = "noscheme:8080"; - cl_git_fail_with(GIT_EINVALIDSPEC, git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); + cl_git_fail(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); } void test_online_clone__proxy_credentials_request(void) From a3d2922f480bd719162466981e7837c5ff3fb65b Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 31 Jul 2023 17:14:29 +0100 Subject: [PATCH 07/15] httpclient: safety --- src/libgit2/transports/httpclient.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libgit2/transports/httpclient.c b/src/libgit2/transports/httpclient.c index 0ad6cd4dcb9..a20b594930d 100644 --- a/src/libgit2/transports/httpclient.c +++ b/src/libgit2/transports/httpclient.c @@ -837,6 +837,11 @@ GIT_INLINE(int) server_setup_from_url( git_http_server *server, git_net_url *url) { + GIT_ASSERT_ARG(url); + GIT_ASSERT_ARG(url->scheme); + GIT_ASSERT_ARG(url->host); + GIT_ASSERT_ARG(url->port); + if (!server->url.scheme || strcmp(server->url.scheme, url->scheme) || !server->url.host || strcmp(server->url.host, url->host) || !server->url.port || strcmp(server->url.port, url->port)) { From 95d4cb6eac7f168c6954b2f6f11a5aa930b0192c Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 1 Aug 2023 11:58:53 +0100 Subject: [PATCH 08/15] net: refactor url parsing Refactor url parsing to simplify the state-passing (introducing a struct) and add a path parser for future reusability. --- src/util/net.c | 378 ++++++++++++++++++++++++----------------- tests/util/url/parse.c | 14 ++ 2 files changed, 235 insertions(+), 157 deletions(-) diff --git a/src/util/net.c b/src/util/net.c index dd8a1ba4670..15fff859213 100644 --- a/src/util/net.c +++ b/src/util/net.c @@ -19,6 +19,30 @@ #define DEFAULT_PORT_GIT "9418" #define DEFAULT_PORT_SSH "22" +#define GIT_NET_URL_PARSER_INIT { 0 } + +typedef struct { + int hierarchical : 1; + + const char *scheme; + const char *user; + const char *password; + const char *host; + const char *port; + const char *path; + const char *query; + const char *fragment; + + size_t scheme_len; + size_t user_len; + size_t password_len; + size_t host_len; + size_t port_len; + size_t path_len; + size_t query_len; + size_t fragment_len; +} git_net_url_parser; + bool git_net_hostname_matches_cert( const char *hostname, const char *pattern) @@ -63,6 +87,12 @@ bool git_net_hostname_matches_cert( return false; } +#define is_valid_scheme_char(c) \ + (((c) >= 'a' && (c) <= 'z') || \ + ((c) >= 'A' && (c) <= 'Z') || \ + ((c) >= '0' && (c) <= '9') || \ + (c) == '+' || (c) == '-' || (c) == '.') + bool git_net_str_is_url(const char *str) { const char *c; @@ -71,10 +101,7 @@ bool git_net_str_is_url(const char *str) if (*c == ':' && *(c+1) == '/' && *(c+2) == '/') return true; - if ((*c < 'a' || *c > 'z') && - (*c < 'A' || *c > 'Z') && - (*c < '0' || *c > '9') && - (*c != '+' && *c != '-' && *c != '.')) + if (!is_valid_scheme_char(*c)) break; } @@ -97,6 +124,16 @@ static const char *default_port_for_scheme(const char *scheme) return NULL; } +static bool is_ssh_scheme(const char *scheme, size_t scheme_len) +{ + if (!scheme_len) + return false; + + return strncasecmp(scheme, "ssh", scheme_len) == 0 || + strncasecmp(scheme, "ssh+git", scheme_len) == 0 || + strncasecmp(scheme, "git+ssh", scheme_len) == 0; +} + int git_net_url_dup(git_net_url *out, git_net_url *in) { if (in->scheme) { @@ -144,12 +181,9 @@ static int url_invalid(const char *message) } static int url_parse_authority( - const char **user_start, size_t *user_len, - const char **password_start, size_t *password_len, - const char **host_start, size_t *host_len, - const char **port_start, size_t *port_len, - const char *authority_start, size_t len, - const char *scheme_start, size_t scheme_len) + git_net_url_parser *parser, + const char *authority, + size_t len) { const char *c, *hostport_end, *host_end = NULL, *userpass_end, *user_end = NULL; @@ -165,14 +199,14 @@ static int url_parse_authority( * walk the authority backwards so that we can parse google code's * ssh urls that are not rfc compliant and allow @ in the username */ - for (hostport_end = authority_start + len, c = hostport_end - 1; - c >= authority_start && !user_end; + for (hostport_end = authority + len, c = hostport_end - 1; + c >= authority && !user_end; c--) { switch (state) { case HOSTPORT: if (*c == ':') { - *port_start = c + 1; - *port_len = hostport_end - *port_start; + parser->port = c + 1; + parser->port_len = hostport_end - parser->port; host_end = c; state = HOST; break; @@ -200,9 +234,10 @@ static int url_parse_authority( } else if (*c == '@') { - *host_start = c + 1; - *host_len = host_end ? host_end - *host_start : - hostport_end - *host_start; + parser->host = c + 1; + parser->host_len = host_end ? + host_end - parser->host : + hostport_end - parser->host; userpass_end = c; state = USERPASS; } @@ -215,8 +250,8 @@ static int url_parse_authority( case IPV6: if (*c == '[') { - *host_start = c + 1; - *host_len = host_end - *host_start; + parser->host = c + 1; + parser->host_len = host_end - parser->host; state = HOST_END; } @@ -240,12 +275,12 @@ static int url_parse_authority( case USERPASS: if (*c == '@' && - strncasecmp(scheme_start, "ssh", scheme_len)) + !is_ssh_scheme(parser->scheme, parser->scheme_len)) return url_invalid("malformed hostname"); if (*c == ':') { - *password_start = c + 1; - *password_len = userpass_end - *password_start; + parser->password = c + 1; + parser->password_len = userpass_end - parser->password; user_end = c; state = USER; break; @@ -260,24 +295,24 @@ static int url_parse_authority( switch (state) { case HOSTPORT: - *host_start = authority_start; - *host_len = (hostport_end - *host_start); + parser->host = authority; + parser->host_len = (hostport_end - parser->host); break; case HOST: - *host_start = authority_start; - *host_len = (host_end - *host_start); + parser->host = authority; + parser->host_len = (host_end - parser->host); break; case IPV6: return url_invalid("malformed hostname"); case HOST_END: break; case USERPASS: - *user_start = authority_start; - *user_len = (userpass_end - *user_start); + parser->user = authority; + parser->user_len = (userpass_end - parser->user); break; case USER: - *user_start = authority_start; - *user_len = (user_end - *user_start); + parser->user = authority; + parser->user_len = (user_end - parser->user); break; default: GIT_ASSERT(!"unhandled state"); @@ -286,97 +321,30 @@ static int url_parse_authority( return 0; } -int git_net_url_parse(git_net_url *url, const char *given) +static int url_parse_path( + git_net_url_parser *parser, + const char *path, + size_t len) { - const char *c, *scheme_start, *authority_start, *user_start, - *password_start, *host_start, *port_start, *path_start, - *query_start, *fragment_start, *default_port; - git_str scheme = GIT_STR_INIT, user = GIT_STR_INIT, - password = GIT_STR_INIT, host = GIT_STR_INIT, - port = GIT_STR_INIT, path = GIT_STR_INIT, - query = GIT_STR_INIT, fragment = GIT_STR_INIT; - size_t scheme_len = 0, user_len = 0, password_len = 0, host_len = 0, - port_len = 0, path_len = 0, query_len = 0, fragment_len = 0; - bool hierarchical = false; - int error = 0; + const char *c, *end; - enum { - SCHEME, - AUTHORITY_START, AUTHORITY, - PATH_START, PATH, - QUERY, - FRAGMENT - } state = SCHEME; + enum { PATH, QUERY, FRAGMENT } state = PATH; - memset(url, 0, sizeof(git_net_url)); + parser->path = path; + end = path + len; - for (c = scheme_start = given; *c; c++) { + for (c = path; c < end; c++) { switch (state) { - case SCHEME: - if (*c == ':') { - scheme_len = (c - scheme_start); - - if (*(c+1) == '/' && *(c+2) == '/') { - c += 2; - hierarchical = true; - state = AUTHORITY_START; - } else { - state = PATH_START; - } - } else if ((*c < 'A' || *c > 'Z') && - (*c < 'a' || *c > 'z') && - (*c < '0' || *c > '9') && - (*c != '+' && *c != '-' && *c != '.')) { - /* - * an illegal scheme character means that we - * were just given a relative path - */ - path_start = given; - state = PATH; - break; - } - break; - - case AUTHORITY_START: - authority_start = c; - state = AUTHORITY; - - /* fall through */ - - case AUTHORITY: - if (*c != '/') - break; - - /* - * authority is sufficiently complex that we parse - * it separately - */ - if ((error = url_parse_authority( - &user_start, &user_len, - &password_start,&password_len, - &host_start, &host_len, - &port_start, &port_len, - authority_start, (c - authority_start), - scheme_start, scheme_len)) < 0) - goto done; - - /* fall through */ - - case PATH_START: - path_start = c; - state = PATH; - /* fall through */ - case PATH: switch (*c) { case '?': - path_len = (c - path_start); - query_start = c + 1; + parser->path_len = (c - parser->path); + parser->query = c + 1; state = QUERY; break; case '#': - path_len = (c - path_start); - fragment_start = c + 1; + parser->path_len = (c - parser->path); + parser->fragment = c + 1; state = FRAGMENT; break; } @@ -384,8 +352,8 @@ int git_net_url_parse(git_net_url *url, const char *given) case QUERY: if (*c == '#') { - query_len = (c - query_start); - fragment_start = c + 1; + parser->query_len = (c - parser->query); + parser->fragment = c + 1; state = FRAGMENT; } break; @@ -399,82 +367,70 @@ int git_net_url_parse(git_net_url *url, const char *given) } switch (state) { - case SCHEME: - /* - * if we never saw a ':' then we were given a relative - * path, not a bare scheme - */ - path_start = given; - path_len = (c - scheme_start); - break; - case AUTHORITY_START: - break; - case AUTHORITY: - if ((error = url_parse_authority( - &user_start, &user_len, - &password_start,&password_len, - &host_start, &host_len, - &port_start, &port_len, - authority_start, (c - authority_start), - scheme_start, scheme_len)) < 0) - goto done; - break; - case PATH_START: - break; case PATH: - path_len = (c - path_start); + parser->path_len = (c - parser->path); break; case QUERY: - query_len = (c - query_start); + parser->query_len = (c - parser->query); break; case FRAGMENT: - fragment_len = (c - fragment_start); + parser->fragment_len = (c - parser->fragment); break; - default: - GIT_ASSERT(!"unhandled state"); } - if (scheme_len) { - if ((error = git_str_put(&scheme, scheme_start, scheme_len)) < 0) + return 0; +} + +static int url_parse_finalize(git_net_url *url, git_net_url_parser *parser) +{ + git_str scheme = GIT_STR_INIT, user = GIT_STR_INIT, + password = GIT_STR_INIT, host = GIT_STR_INIT, + port = GIT_STR_INIT, path = GIT_STR_INIT, + query = GIT_STR_INIT, fragment = GIT_STR_INIT; + const char *default_port; + int error = 0; + + if (parser->scheme_len) { + if ((error = git_str_put(&scheme, parser->scheme, parser->scheme_len)) < 0) goto done; git__strntolower(scheme.ptr, scheme.size); } - if (user_len && - (error = git_str_decode_percent(&user, user_start, user_len)) < 0) + if (parser->user_len && + (error = git_str_decode_percent(&user, parser->user, parser->user_len)) < 0) goto done; - if (password_len && - (error = git_str_decode_percent(&password, password_start, password_len)) < 0) + if (parser->password_len && + (error = git_str_decode_percent(&password, parser->password, parser->password_len)) < 0) goto done; - if (host_len && - (error = git_str_decode_percent(&host, host_start, host_len)) < 0) + if (parser->host_len && + (error = git_str_decode_percent(&host, parser->host, parser->host_len)) < 0) goto done; - if (port_len) - error = git_str_put(&port, port_start, port_len); - else if (scheme_len && (default_port = default_port_for_scheme(scheme.ptr)) != NULL) + if (parser->port_len) + error = git_str_put(&port, parser->port, parser->port_len); + else if (parser->scheme_len && (default_port = default_port_for_scheme(scheme.ptr)) != NULL) error = git_str_puts(&port, default_port); if (error < 0) goto done; - if (path_len) - error = git_str_put(&path, path_start, path_len); - else if (hierarchical) + if (parser->path_len) + error = git_str_put(&path, parser->path, parser->path_len); + else if (parser->hierarchical) error = git_str_puts(&path, "/"); if (error < 0) goto done; - if (query_len && - (error = git_str_decode_percent(&query, query_start, query_len)) < 0) + if (parser->query_len && + (error = git_str_decode_percent(&query, parser->query, parser->query_len)) < 0) goto done; - if (fragment_len && - (error = git_str_decode_percent(&fragment, fragment_start, fragment_len)) < 0) + if (parser->fragment_len && + (error = git_str_decode_percent(&fragment, parser->fragment, parser->fragment_len)) < 0) goto done; url->scheme = git_str_detach(&scheme); @@ -501,6 +457,114 @@ int git_net_url_parse(git_net_url *url, const char *given) return error; } +int git_net_url_parse(git_net_url *url, const char *given) +{ + git_net_url_parser parser = GIT_NET_URL_PARSER_INIT; + const char *c, *authority, *path; + size_t authority_len = 0, path_len = 0; + int error = 0; + + enum { + SCHEME_START, SCHEME, + AUTHORITY_START, AUTHORITY, + PATH_START, PATH + } state = SCHEME_START; + + memset(url, 0, sizeof(git_net_url)); + + for (c = given; *c; c++) { + switch (state) { + case SCHEME_START: + parser.scheme = c; + state = SCHEME; + + /* fall through */ + + case SCHEME: + if (*c == ':') { + parser.scheme_len = (c - parser.scheme); + + if (parser.scheme_len && + *(c+1) == '/' && *(c+2) == '/') { + c += 2; + parser.hierarchical = 1; + state = AUTHORITY_START; + } else { + state = PATH_START; + } + } else if (!is_valid_scheme_char(*c)) { + /* + * an illegal scheme character means that we + * were just given a relative path + */ + path = given; + state = PATH; + break; + } + break; + + case AUTHORITY_START: + authority = c; + state = AUTHORITY; + + /* fall through */ + case AUTHORITY: + if (*c != '/') + break; + + authority_len = (c - authority); + + /* fall through */ + case PATH_START: + path = c; + state = PATH; + break; + + case PATH: + break; + + default: + GIT_ASSERT(!"unhandled state"); + } + } + + switch (state) { + case SCHEME: + /* + * if we never saw a ':' then we were given a relative + * path, not a bare scheme + */ + path = given; + path_len = (c - path); + break; + case AUTHORITY_START: + break; + case AUTHORITY: + authority_len = (c - authority); + break; + case PATH_START: + break; + case PATH: + path_len = (c - path); + break; + default: + GIT_ASSERT(!"unhandled state"); + } + + if (authority_len && + (error = url_parse_authority(&parser, authority, authority_len)) < 0) + goto done; + + if (path_len && + (error = url_parse_path(&parser, path, path_len)) < 0) + goto done; + + error = url_parse_finalize(url, &parser); + +done: + return error; +} + static int scp_invalid(const char *message) { git_error_set(GIT_ERROR_NET, "invalid scp-style path: %s", message); diff --git a/tests/util/url/parse.c b/tests/util/url/parse.c index 631d9b456d9..35486f7b7a7 100644 --- a/tests/util/url/parse.c +++ b/tests/util/url/parse.c @@ -669,6 +669,20 @@ void test_url_parse__ipv6_invalid_addresses(void) /* Oddities */ +void test_url_parse__empty_scheme(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "://example.com/resource")); + cl_assert_equal_s(conndata.scheme, NULL); + cl_assert_equal_s(conndata.host, NULL); + cl_assert_equal_s(conndata.port, NULL); + cl_assert_equal_s(conndata.path, "//example.com/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + void test_url_parse__invalid_scheme_is_relative(void) { cl_git_pass(git_net_url_parse(&conndata, "foo!bar://host:42/path/to/project?query_string=yes")); From 1d7d6ff95a30970fa16fbce57f1f4a31acc2fb21 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 1 Aug 2023 11:59:55 +0100 Subject: [PATCH 09/15] net: introduce http-biased url parsing Introduce a url parser that defaults to treating poorly specified URLs as http URLs. For example: `localhost:8080` is treated as `http://localhost:8080/` by the http-biased url parsing, instead of a URL with a scheme `localhost` and a path of `8080`.. --- src/util/net.c | 43 +++ src/util/net.h | 8 + tests/util/url/http.c | 752 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 803 insertions(+) create mode 100644 tests/util/url/http.c diff --git a/src/util/net.c b/src/util/net.c index 15fff859213..afd52ce0830 100644 --- a/src/util/net.c +++ b/src/util/net.c @@ -565,6 +565,49 @@ int git_net_url_parse(git_net_url *url, const char *given) return error; } +int git_net_url_parse_http( + git_net_url *url, + const char *given) +{ + git_net_url_parser parser = GIT_NET_URL_PARSER_INIT; + const char *c, *authority, *path = NULL; + size_t authority_len = 0, path_len = 0; + int error; + + /* Hopefully this is a proper URL with a scheme. */ + if (git_net_str_is_url(given)) + return git_net_url_parse(url, given); + + memset(url, 0, sizeof(git_net_url)); + + /* Without a scheme, we are in the host (authority) section. */ + for (c = authority = given; *c; c++) { + if (!path && *c == '/') { + authority_len = (c - authority); + path = c; + } + } + + if (path) + path_len = (c - path); + else + authority_len = (c - authority); + + parser.scheme = "http"; + parser.scheme_len = 4; + parser.hierarchical = 1; + + if (authority_len && + (error = url_parse_authority(&parser, authority, authority_len)) < 0) + return error; + + if (path_len && + (error = url_parse_path(&parser, path, path_len)) < 0) + return error; + + return url_parse_finalize(url, &parser); +} + static int scp_invalid(const char *message) { git_error_set(GIT_ERROR_NET, "invalid scp-style path: %s", message); diff --git a/src/util/net.h b/src/util/net.h index c9a84cb6cec..8024956ad0c 100644 --- a/src/util/net.h +++ b/src/util/net.h @@ -57,6 +57,14 @@ extern int git_net_url_parse_scp(git_net_url *url, const char *str); */ extern int git_net_url_parse_standard_or_scp(git_net_url *url, const char *str); +/** + * Parses a string containing an HTTP endpoint that may not be a + * well-formed URL. For example, "localhost" or "localhost:port". + */ +extern int git_net_url_parse_http( + git_net_url *url, + const char *str); + /** Appends a path and/or query string to the given URL */ extern int git_net_url_joinpath( git_net_url *out, diff --git a/tests/util/url/http.c b/tests/util/url/http.c new file mode 100644 index 00000000000..88238896257 --- /dev/null +++ b/tests/util/url/http.c @@ -0,0 +1,752 @@ +#include "clar_libgit2.h" +#include "net.h" + +static git_net_url conndata; + +void test_url_http__initialize(void) +{ + memset(&conndata, 0, sizeof(conndata)); +} + +void test_url_http__cleanup(void) +{ + git_net_url_dispose(&conndata); +} + +/* Hostname */ + +void test_url_http__has_scheme(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "http://example.com/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__no_scheme(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "example.com/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__hostname_root(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "example.com/")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__hostname_implied_root(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "example.com")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__hostname_numeric(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "8888888/")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "8888888"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__hostname_implied_root_custom_port(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "example.com:42")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "42"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_url_http__hostname_implied_root_empty_port(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "example.com:")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__hostname_encoded_password(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, + "user:pass%2fis%40bad@hostname.com:1234/")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "hostname.com"); + cl_assert_equal_s(conndata.port, "1234"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass/is@bad"); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_url_http__hostname_user(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, + "user@example.com/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__hostname_user_pass(void) +{ + /* user:pass@hostname.tld/resource */ + cl_git_pass(git_net_url_parse_http(&conndata, + "user:pass@example.com/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__hostname_port(void) +{ + /* hostname.tld:port/resource */ + cl_git_pass(git_net_url_parse_http(&conndata, + "example.com:9191/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_url_http__hostname_empty_port(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "example.com:/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__hostname_user_port(void) +{ + /* user@hostname.tld:port/resource */ + cl_git_pass(git_net_url_parse_http(&conndata, + "user@example.com:9191/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_url_http__hostname_user_pass_port(void) +{ + /* user:pass@hostname.tld:port/resource */ + cl_git_pass(git_net_url_parse_http(&conndata, + "user:pass@example.com:9191/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_url_http__hostname_user_pass_port_query(void) +{ + /* user:pass@hostname.tld:port/resource */ + cl_git_pass(git_net_url_parse_http(&conndata, + "user:pass@example.com:9191/resource?query=q&foo=bar&z=asdf")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_s(conndata.query, "query=q&foo=bar&z=asdf"); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_url_http__hostname_user_pass_port_fragment(void) +{ + /* user:pass@hostname.tld:port/resource */ + cl_git_pass(git_net_url_parse_http(&conndata, + "user:pass@example.com:9191/resource#fragment")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_s(conndata.fragment, "fragment"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_url_http__hostname_user_pass_port_query_fragment(void) +{ + /* user:pass@hostname.tld:port/resource */ + cl_git_pass(git_net_url_parse_http(&conndata, + "user:pass@example.com:9191/resource?query=q&foo=bar&z=asdf#fragment")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_s(conndata.query, "query=q&foo=bar&z=asdf"); + cl_assert_equal_s(conndata.fragment, "fragment"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_url_http__fragment_with_question_mark(void) +{ + /* user:pass@hostname.tld:port/resource */ + cl_git_pass(git_net_url_parse_http(&conndata, + "user:pass@example.com:9191/resource#fragment_with?question_mark")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_p(conndata.query, NULL); + cl_assert_equal_s(conndata.fragment, "fragment_with?question_mark"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +/* IPv4 addresses */ + +void test_url_http__ipv4_trivial(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "192.168.1.1/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__ipv4_root(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "192.168.1.1/")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__ipv4_implied_root(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "192.168.1.1")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__ipv4_implied_root_custom_port(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "192.168.1.1:42")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "42"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_url_http__ipv4_implied_root_empty_port(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "192.168.1.1:")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__ipv4_encoded_password(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, + "user:pass%2fis%40bad@192.168.1.1:1234/")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "1234"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass/is@bad"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_url_http__ipv4_user(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, + "user@192.168.1.1/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__ipv4_user_pass(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, + "user:pass@192.168.1.1/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__ipv4_port(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, + "192.168.1.1:9191/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_url_http__ipv4_empty_port(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "192.168.1.1:/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__ipv4_user_port(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, + "user@192.168.1.1:9191/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_url_http__ipv4_user_pass_port(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, + "user:pass@192.168.1.1:9191/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "192.168.1.1"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +/* IPv6 addresses */ + +void test_url_http__ipv6_trivial(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "[fe80::dcad:beff:fe00:0001]/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__ipv6_root(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "[fe80::dcad:beff:fe00:0001]/")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__ipv6_implied_root(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "[fe80::dcad:beff:fe00:0001]")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__ipv6_implied_root_custom_port(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "[fe80::dcad:beff:fe00:0001]:42")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "42"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_url_http__ipv6_implied_root_empty_port(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "[fe80::dcad:beff:fe00:0001]:")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__ipv6_encoded_password(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, + "user:pass%2fis%40bad@[fe80::dcad:beff:fe00:0001]:1234/")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "1234"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass/is@bad"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_url_http__ipv6_user(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, + "user@[fe80::dcad:beff:fe00:0001]/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__ipv6_user_pass(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, + "user:pass@[fe80::dcad:beff:fe00:0001]/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__ipv6_port(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, + "[fe80::dcad:beff:fe00:0001]:9191/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_url_http__ipv6_empty_port(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "[fe80::dcad:beff:fe00:0001]:/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__ipv6_user_port(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, + "user@[fe80::dcad:beff:fe00:0001]:9191/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_url_http__ipv6_user_pass_port(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, + "user:pass@[fe80::dcad:beff:fe00:0001]:9191/resource")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "fe80::dcad:beff:fe00:0001"); + cl_assert_equal_s(conndata.port, "9191"); + cl_assert_equal_s(conndata.path, "/resource"); + cl_assert_equal_s(conndata.username, "user"); + cl_assert_equal_s(conndata.password, "pass"); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 0); +} + +void test_url_http__ipv6_invalid_addresses(void) +{ + /* Opening bracket missing */ + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "fe80::dcad:beff:fe00:0001]/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "fe80::dcad:beff:fe00:0001]/")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "fe80::dcad:beff:fe00:0001]")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "fe80::dcad:beff:fe00:0001]:42")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "fe80::dcad:beff:fe00:0001]:")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "user:pass%2fis%40bad@fe80::dcad:beff:fe00:0001]:1234/")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "user@fe80::dcad:beff:fe00:0001]/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "user:pass@fe80::dcad:beff:fe00:0001]/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "fe80::dcad:beff:fe00:0001]:9191/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "fe80::dcad:beff:fe00:0001]:/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "user@fe80::dcad:beff:fe00:0001]:9191/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "user:pass@fe80::dcad:beff:fe00:0001]:9191/resource")); + + /* Closing bracket missing */ + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "[fe80::dcad:beff:fe00:0001/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "[fe80::dcad:beff:fe00:0001/")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "[fe80::dcad:beff:fe00:0001")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "[fe80::dcad:beff:fe00:0001:42")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "[fe80::dcad:beff:fe00:0001:")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "user:pass%2fis%40bad@[fe80::dcad:beff:fe00:0001:1234/")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "user@[fe80::dcad:beff:fe00:0001/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "user:pass@[fe80::dcad:beff:fe00:0001/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "[fe80::dcad:beff:fe00:0001:9191/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "[fe80::dcad:beff:fe00:0001:/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "user@[fe80::dcad:beff:fe00:0001:9191/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "user:pass@[fe80::dcad:beff:fe00:0001:9191/resource")); + + /* Both brackets missing */ + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "fe80::dcad:beff:fe00:0001/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "fe80::dcad:beff:fe00:0001/")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "fe80::dcad:beff:fe00:0001")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "fe80::dcad:beff:fe00:0001:42")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "fe80::dcad:beff:fe00:0001:")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "user:pass%2fis%40bad@fe80::dcad:beff:fe00:0001:1234/")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "user@fe80::dcad:beff:fe00:0001/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "user:pass@fe80::dcad:beff:fe00:0001/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "fe80::dcad:beff:fe00:0001:9191/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "fe80::dcad:beff:fe00:0001:/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "user@fe80::dcad:beff:fe00:0001:9191/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "user:pass@fe80::dcad:beff:fe00:0001:9191/resource")); + + /* Invalid character inside address */ + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, "[fe8o::dcad:beff:fe00:0001]/resource")); + + /* Characters before/after braces */ + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "fe80::[dcad:beff:fe00:0001]/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "cafe[fe80::dcad:beff:fe00:0001]/resource")); + cl_git_fail_with(GIT_EINVALIDSPEC, git_net_url_parse_http(&conndata, + "[fe80::dcad:beff:fe00:0001]cafe/resource")); +} + +/* Oddities */ + +void test_url_http__invalid_scheme_is_relative(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "foo!bar://host:42/path/to/project?query_string=yes")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "foo!bar"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "//host:42/path/to/project"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_s(conndata.query, "query_string=yes"); + cl_assert_equal_p(conndata.fragment, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__scheme_case_is_normalized(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "GIT+SSH://host:42/path/to/project")); + cl_assert_equal_s(conndata.scheme, "git+ssh"); +} + +void test_url_http__no_scheme_relative_path(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "path")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "path"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__no_scheme_absolute_path(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "/path")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_p(conndata.host, NULL); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/path"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__empty_path_with_empty_authority(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_p(conndata.host, NULL); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_url_http__spaces_in_the_name(void) +{ + cl_git_pass(git_net_url_parse_http(&conndata, "libgit2@dev.azure.com/libgit2/test/_git/spaces%20in%20the%20name")); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "dev.azure.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/libgit2/test/_git/spaces%20in%20the%20name"); + cl_assert_equal_s(conndata.username, "libgit2"); + cl_assert_equal_p(conndata.password, NULL); + cl_assert_equal_i(git_net_url_is_default_port(&conndata), 1); +} From 7a901fb42f2e3cff72b9b28702370ef7b2f19a1e Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 1 Aug 2023 12:16:32 +0100 Subject: [PATCH 10/15] http: allow proxies to be specified in common format The common format for specifying proxy URLs is just 'host:port'. Handle the common case. --- src/libgit2/transports/http.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libgit2/transports/http.c b/src/libgit2/transports/http.c index eed51914549..8437674fcc9 100644 --- a/src/libgit2/transports/http.c +++ b/src/libgit2/transports/http.c @@ -335,7 +335,7 @@ static int lookup_proxy( } if (!proxy || - (error = git_net_url_parse(&transport->proxy.url, proxy)) < 0) + (error = git_net_url_parse_http(&transport->proxy.url, proxy)) < 0) goto done; if (!git_net_url_valid(&transport->proxy.url)) { From 00c62ab0ad2e620b1d8ff18b7b5a3989efaa6635 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Tue, 1 Aug 2023 14:07:53 +0100 Subject: [PATCH 11/15] http: test proxies in url and host:port format Test proxies specified by both host:port format in configuration options, environment variables, and `http.proxy` configuration. --- tests/libgit2/online/clone.c | 124 ++++++++++++++++++++++++++++++----- 1 file changed, 107 insertions(+), 17 deletions(-) diff --git a/tests/libgit2/online/clone.c b/tests/libgit2/online/clone.c index 0c53514a425..5789e9654c3 100644 --- a/tests/libgit2/online/clone.c +++ b/tests/libgit2/online/clone.c @@ -43,7 +43,6 @@ static char *_github_ssh_privkey = NULL; static char *_github_ssh_passphrase = NULL; static char *_github_ssh_remotehostkey = NULL; -static int _orig_proxies_need_reset = 0; static char *_orig_http_proxy = NULL; static char *_orig_https_proxy = NULL; static char *_orig_no_proxy = NULL; @@ -99,10 +98,12 @@ void test_online_clone__initialize(void) _github_ssh_passphrase = cl_getenv("GITTEST_GITHUB_SSH_PASSPHRASE"); _github_ssh_remotehostkey = cl_getenv("GITTEST_GITHUB_SSH_REMOTE_HOSTKEY"); + _orig_http_proxy = cl_getenv("HTTP_PROXY"); + _orig_https_proxy = cl_getenv("HTTPS_PROXY"); + _orig_no_proxy = cl_getenv("NO_PROXY"); + if (_remote_expectcontinue) git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1); - - _orig_proxies_need_reset = 0; } void test_online_clone__cleanup(void) @@ -140,15 +141,13 @@ void test_online_clone__cleanup(void) git__free(_github_ssh_passphrase); git__free(_github_ssh_remotehostkey); - if (_orig_proxies_need_reset) { - cl_setenv("HTTP_PROXY", _orig_http_proxy); - cl_setenv("HTTPS_PROXY", _orig_https_proxy); - cl_setenv("NO_PROXY", _orig_no_proxy); + cl_setenv("HTTP_PROXY", _orig_http_proxy); + cl_setenv("HTTPS_PROXY", _orig_https_proxy); + cl_setenv("NO_PROXY", _orig_no_proxy); - git__free(_orig_http_proxy); - git__free(_orig_https_proxy); - git__free(_orig_no_proxy); - } + git__free(_orig_http_proxy); + git__free(_orig_https_proxy); + git__free(_orig_no_proxy); git_libgit2_opts(GIT_OPT_SET_SSL_CERT_LOCATIONS, NULL, NULL); git_libgit2_opts(GIT_OPT_SET_SERVER_TIMEOUT, 0); @@ -968,6 +967,79 @@ static int proxy_cert_cb(git_cert *cert, int valid, const char *host, void *payl return valid ? 0 : GIT_ECERTIFICATE; } +void test_online_clone__proxy_http_host_port_in_opts(void) +{ + if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) + cl_skip(); + + if (_remote_proxy_scheme && strcmp(_remote_proxy_scheme, "http") != 0) + cl_skip(); + + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; + g_options.fetch_opts.proxy_opts.url = _remote_proxy_host; + g_options.fetch_opts.proxy_opts.credentials = proxy_cred_cb; + + called_proxy_creds = 0; + cl_git_pass(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options)); + cl_assert(called_proxy_creds == 1); +} + +void test_online_clone__proxy_http_host_port_in_env(void) +{ + if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) + cl_skip(); + + if (_remote_proxy_scheme && strcmp(_remote_proxy_scheme, "http") != 0) + cl_skip(); + + cl_setenv("HTTP_PROXY", _remote_proxy_host); + cl_setenv("HTTPS_PROXY", _remote_proxy_host); + cl_setenv("NO_PROXY", NULL); + + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_AUTO; + g_options.fetch_opts.proxy_opts.credentials = proxy_cred_cb; + + called_proxy_creds = 0; + cl_git_pass(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options)); + cl_assert(called_proxy_creds == 1); +} + +static int repository_create_with_proxy( + git_repository **out, + const char *path, + int bare, + void *payload) +{ + git_repository *repo; + git_config *config; + char *value = (char *)payload; + + cl_git_pass(git_repository_init(&repo, path, bare)); + cl_git_pass(git_repository_config(&config, repo)); + + cl_git_pass(git_config_set_string(config, "http.proxy", value)); + + git_config_free(config); + + *out = repo; + return 0; +} + +void test_online_clone__proxy_http_host_port_in_config(void) +{ + if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) + cl_skip(); + + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_AUTO; + g_options.fetch_opts.proxy_opts.credentials = proxy_cred_cb; + g_options.repository_cb = repository_create_with_proxy; + g_options.repository_cb_payload = _remote_proxy_host; + + called_proxy_creds = 0; + cl_git_pass(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options)); + cl_assert(called_proxy_creds == 1); +} + void test_online_clone__proxy_invalid_url(void) { g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; @@ -1003,7 +1075,7 @@ void test_online_clone__proxy_credentials_request(void) git_str_dispose(&url); } -void test_online_clone__proxy_credentials_in_url(void) +void test_online_clone__proxy_credentials_in_well_formed_url(void) { git_str url = GIT_STR_INIT; @@ -1024,17 +1096,35 @@ void test_online_clone__proxy_credentials_in_url(void) git_str_dispose(&url); } -void test_online_clone__proxy_credentials_in_environment(void) +void test_online_clone__proxy_credentials_in_host_port_format(void) { git_str url = GIT_STR_INIT; if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) cl_skip(); - _orig_http_proxy = cl_getenv("HTTP_PROXY"); - _orig_https_proxy = cl_getenv("HTTPS_PROXY"); - _orig_no_proxy = cl_getenv("NO_PROXY"); - _orig_proxies_need_reset = 1; + if (_remote_proxy_scheme && strcmp(_remote_proxy_scheme, "http") != 0) + cl_skip(); + + cl_git_pass(git_str_printf(&url, "%s:%s@%s", + _remote_proxy_user, _remote_proxy_pass, _remote_proxy_host)); + + g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED; + g_options.fetch_opts.proxy_opts.url = url.ptr; + g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb; + called_proxy_creds = 0; + cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options)); + cl_assert(called_proxy_creds == 0); + + git_str_dispose(&url); +} + +void test_online_clone__proxy_credentials_in_environment(void) +{ + git_str url = GIT_STR_INIT; + + if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass) + cl_skip(); g_options.fetch_opts.proxy_opts.type = GIT_PROXY_AUTO; g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb; From 38a3a3711d7d6fb15bec6ce529a4d1eb0eb8fdeb Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Wed, 2 Aug 2023 11:02:44 +0100 Subject: [PATCH 12/15] winhttp: use new http-style url parser for proxies --- src/libgit2/transports/winhttp.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libgit2/transports/winhttp.c b/src/libgit2/transports/winhttp.c index b7ffb9512ae..ae572c56d8e 100644 --- a/src/libgit2/transports/winhttp.c +++ b/src/libgit2/transports/winhttp.c @@ -443,7 +443,7 @@ static int winhttp_stream_connect(winhttp_stream *s) git_net_url_dispose(&t->proxy.url); - if ((error = git_net_url_parse(&t->proxy.url, proxy_url)) < 0) + if ((error = git_net_url_parse_http(&t->proxy.url, proxy_url)) < 0) goto on_error; if (!git_net_url_valid(&t->proxy.url)) { From e577da9d59ff3ddff8515ab63617e729827fb0fc Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Thu, 10 Aug 2023 09:52:04 +0100 Subject: [PATCH 13/15] Revert "CMake: Search for ssh2 instead of libssh2." --- cmake/SelectSSH.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/SelectSSH.cmake b/cmake/SelectSSH.cmake index 968a63114f3..23dfc978521 100644 --- a/cmake/SelectSSH.cmake +++ b/cmake/SelectSSH.cmake @@ -1,6 +1,6 @@ # Optional external dependency: libssh2 if(USE_SSH) - find_pkglibraries(LIBSSH2 ssh2) + find_pkglibraries(LIBSSH2 libssh2) if(NOT LIBSSH2_FOUND) find_package(LibSSH2) set(LIBSSH2_INCLUDE_DIRS ${LIBSSH2_INCLUDE_DIR}) From 8b59fc54aadda094550f23d5439e5b1521b15338 Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 14 Aug 2023 21:24:25 +0100 Subject: [PATCH 14/15] v1.7.1: add changelog --- docs/changelog.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/docs/changelog.md b/docs/changelog.md index f733102356a..ab7f358db16 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,3 +1,26 @@ +v1.7.1 +------ + +## What's Changed + +### Bug fixes + +* proxy: Return an error for invalid proxy URLs instead of crashing. by @lrm29 in https://github.com/libgit2/libgit2/pull/6597 +* ssh: fix known_hosts leak in _git_ssh_setup_conn by @steven9724 in https://github.com/libgit2/libgit2/pull/6599 +* repository: make cleanup safe for re-use with grafts by @carlosmn in https://github.com/libgit2/libgit2/pull/6600 +* fix: Add missing include for oidarray. by @dvzrv in https://github.com/libgit2/libgit2/pull/6608 +* Revert "CMake: Search for ssh2 instead of libssh2." by @ethomson in https://github.com/libgit2/libgit2/pull/6619 + +### Compatibility improvements + +* stransport: macOS: replace errSSLNetworkTimeout, with hard-coded value by @mascguy in https://github.com/libgit2/libgit2/pull/6610 + +## New Contributors +* @dvzrv made their first contribution in https://github.com/libgit2/libgit2/pull/6608 +* @steven9724 made their first contribution in https://github.com/libgit2/libgit2/pull/6599 + +**Full Changelog**: https://github.com/libgit2/libgit2/compare/v1.7.0...v1.7.1 + v1.7 ---- From 81a9edd3078aac28481d1896d507274283c6fe9a Mon Sep 17 00:00:00 2001 From: Edward Thomson Date: Mon, 14 Aug 2023 21:25:26 +0100 Subject: [PATCH 15/15] v1.7.1: update version numbers --- CMakeLists.txt | 2 +- include/git2/version.h | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cfb5a7d6f4d..29f2766766a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,7 +6,7 @@ cmake_minimum_required(VERSION 3.5.1) -project(libgit2 VERSION "1.7.0" LANGUAGES C) +project(libgit2 VERSION "1.7.1" LANGUAGES C) # Add find modules to the path set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") diff --git a/include/git2/version.h b/include/git2/version.h index 088ba6b07b1..9062c92ff75 100644 --- a/include/git2/version.h +++ b/include/git2/version.h @@ -11,7 +11,7 @@ * The version string for libgit2. This string follows semantic * versioning (v2) guidelines. */ -#define LIBGIT2_VERSION "1.7.0" +#define LIBGIT2_VERSION "1.7.1" /** The major version number for this version of libgit2. */ #define LIBGIT2_VER_MAJOR 1 @@ -20,7 +20,7 @@ #define LIBGIT2_VER_MINOR 7 /** The revision ("teeny") version number for this version of libgit2. */ -#define LIBGIT2_VER_REVISION 0 +#define LIBGIT2_VER_REVISION 1 /** The Windows DLL patch number for this version of libgit2. */ #define LIBGIT2_VER_PATCH 0 diff --git a/package.json b/package.json index 8c6f4ef6e80..5fc0896b16b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "libgit2", - "version": "1.7.0", + "version": "1.7.1", "repo": "https://github.com/libgit2/libgit2", "description": " A cross-platform, linkable library implementation of Git that you can use in your application.", "install": "mkdir build && cd build && cmake .. && cmake --build ."