diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2019-03-11 11:43:08 +0000 |
---|---|---|
committer | Edward Thomson <ethomson@edwardthomson.com> | 2019-06-10 19:58:22 +0100 |
commit | c6ab183e9c960b74471a7d106a4deb0c8b28a8ec (patch) | |
tree | c95f2f36156abd863337e7b6a18e9ec693a5a565 | |
parent | f4584a1e8bf3095b90f5564c0f89878ef79855c5 (diff) | |
download | libgit2-c6ab183e9c960b74471a7d106a4deb0c8b28a8ec.tar.gz |
net: rename gitno_connection_data to git_net_url
"Connection data" is an imprecise and largely incorrect name; these
structures are actually parsed URLs. Provide a parser that takes a URL
string and produces a URL structure (if it is valid).
Separate the HTTP redirect handling logic from URL parsing, keeping a
`gitno_connection_data_handle_redirect` whose only job is redirect
handling logic and does not parse URLs itself.
-rw-r--r-- | src/net.c | 184 | ||||
-rw-r--r-- | src/net.h | 36 | ||||
-rw-r--r-- | src/netops.c | 211 | ||||
-rw-r--r-- | src/netops.h | 28 | ||||
-rw-r--r-- | src/transports/auth.c | 8 | ||||
-rw-r--r-- | src/transports/auth.h | 6 | ||||
-rw-r--r-- | src/transports/auth_negotiate.c | 8 | ||||
-rw-r--r-- | src/transports/auth_negotiate.h | 2 | ||||
-rw-r--r-- | src/transports/git.c | 27 | ||||
-rw-r--r-- | src/transports/http.c | 48 | ||||
-rw-r--r-- | src/transports/ssh.c | 56 | ||||
-rw-r--r-- | src/transports/winhttp.c | 78 | ||||
-rw-r--r-- | tests/network/redirect.c | 113 | ||||
-rw-r--r-- | tests/network/urlparse.c | 262 |
14 files changed, 587 insertions, 480 deletions
diff --git a/src/net.c b/src/net.c new file mode 100644 index 000000000..e35abbb94 --- /dev/null +++ b/src/net.c @@ -0,0 +1,184 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ + +#include "net.h" +#include "netops.h" + +#include <ctype.h> +#include "git2/errors.h" + +#include "posix.h" +#include "buffer.h" +#include "http_parser.h" +#include "global.h" + +#define DEFAULT_PORT_HTTP "80" +#define DEFAULT_PORT_HTTPS "443" +#define DEFAULT_PORT_GIT "9418" +#define DEFAULT_PORT_SSH "22" + +static const char *default_port_for_scheme(const char *scheme) +{ + if (strcmp(scheme, "http") == 0) + return DEFAULT_PORT_HTTP; + else if (strcmp(scheme, "https") == 0) + return DEFAULT_PORT_HTTPS; + else if (strcmp(scheme, "git") == 0) + return DEFAULT_PORT_GIT; + else if (strcmp(scheme, "ssh") == 0) + return DEFAULT_PORT_SSH; + + return NULL; +} + +int git_net_url_parse(git_net_url *url, const char *given) +{ + struct http_parser_url u = {0}; + bool has_scheme, has_host, has_port, has_path, has_query, has_userinfo; + git_buf scheme = GIT_BUF_INIT, + host = GIT_BUF_INIT, + port = GIT_BUF_INIT, + path = GIT_BUF_INIT, + username = GIT_BUF_INIT, + password = GIT_BUF_INIT, + query = GIT_BUF_INIT; + int error = GIT_EINVALIDSPEC; + + if (http_parser_parse_url(given, strlen(given), false, &u)) { + git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given); + goto done; + } + + has_scheme = !!(u.field_set & (1 << UF_SCHEMA)); + has_host = !!(u.field_set & (1 << UF_HOST)); + has_port = !!(u.field_set & (1 << UF_PORT)); + has_path = !!(u.field_set & (1 << UF_PATH)); + has_query = !!(u.field_set & (1 << UF_QUERY)); + has_userinfo = !!(u.field_set & (1 << UF_USERINFO)); + + if (has_scheme) { + const char *url_scheme = given + u.field_data[UF_SCHEMA].off; + size_t url_scheme_len = u.field_data[UF_SCHEMA].len; + git_buf_put(&scheme, url_scheme, url_scheme_len); + git__strntolower(scheme.ptr, scheme.size); + } else { + git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given); + goto done; + } + + if (has_host) { + const char *url_host = given + u.field_data[UF_HOST].off; + size_t url_host_len = u.field_data[UF_HOST].len; + git_buf_decode_percent(&host, url_host, url_host_len); + } + + if (has_port) { + const char *url_port = given + u.field_data[UF_PORT].off; + size_t url_port_len = u.field_data[UF_PORT].len; + git_buf_put(&port, url_port, url_port_len); + } else { + const char *default_port = default_port_for_scheme(scheme.ptr); + + if (default_port == NULL) { + git_error_set(GIT_ERROR_NET, "unknown scheme for URL '%s'", given); + goto done; + } + + git_buf_puts(&port, default_port); + } + + if (has_path) { + const char *url_path = given + u.field_data[UF_PATH].off; + size_t url_path_len = u.field_data[UF_PATH].len; + git_buf_decode_percent(&path, url_path, url_path_len); + } else { + git_buf_puts(&path, "/"); + } + + if (has_query) { + const char *url_query = given + u.field_data[UF_QUERY].off; + size_t url_query_len = u.field_data[UF_QUERY].len; + git_buf_decode_percent(&query, url_query, url_query_len); + } + + if (has_userinfo) { + const char *url_userinfo = given + u.field_data[UF_USERINFO].off; + size_t url_userinfo_len = u.field_data[UF_USERINFO].len; + const char *colon = memchr(url_userinfo, ':', url_userinfo_len); + + if (colon) { + const char *url_username = url_userinfo; + size_t url_username_len = colon - url_userinfo; + const char *url_password = colon + 1; + size_t url_password_len = url_userinfo_len - (url_username_len + 1); + + git_buf_decode_percent(&username, url_username, url_username_len); + git_buf_decode_percent(&password, url_password, url_password_len); + } else { + git_buf_decode_percent(&username, url_userinfo, url_userinfo_len); + } + } + + if (git_buf_oom(&scheme) || + git_buf_oom(&host) || + git_buf_oom(&port) || + git_buf_oom(&path) || + git_buf_oom(&query) || + git_buf_oom(&username) || + git_buf_oom(&password)) + return -1; + + url->scheme = git_buf_detach(&scheme); + url->host = git_buf_detach(&host); + url->port = git_buf_detach(&port); + url->path = git_buf_detach(&path); + url->query = git_buf_detach(&query); + url->username = git_buf_detach(&username); + url->password = git_buf_detach(&password); + + error = 0; + +done: + git_buf_dispose(&scheme); + git_buf_dispose(&host); + git_buf_dispose(&port); + git_buf_dispose(&path); + git_buf_dispose(&query); + git_buf_dispose(&username); + git_buf_dispose(&password); + return error; +} + +int git_net_url_is_default_port(git_net_url *url) +{ + return (strcmp(url->port, default_port_for_scheme(url->scheme)) == 0); +} + +void git_net_url_swap(git_net_url *a, git_net_url *b) +{ + git_net_url tmp = GIT_NET_URL_INIT; + + memcpy(&tmp, a, sizeof(git_net_url)); + memcpy(a, b, sizeof(git_net_url)); + memcpy(b, &tmp, sizeof(git_net_url)); +} + +void git_net_url_dispose(git_net_url *url) +{ + if (url->username) + git__memzero(url->username, strlen(url->username)); + + if (url->password) + git__memzero(url->password, strlen(url->password)); + + git__free(url->scheme); url->scheme = NULL; + git__free(url->host); url->host = NULL; + git__free(url->port); url->port = NULL; + git__free(url->path); url->path = NULL; + git__free(url->username); url->username = NULL; + git__free(url->password); url->password = NULL; +} diff --git a/src/net.h b/src/net.h new file mode 100644 index 000000000..6df129089 --- /dev/null +++ b/src/net.h @@ -0,0 +1,36 @@ +/* + * Copyright (C) the libgit2 contributors. All rights reserved. + * + * This file is part of libgit2, distributed under the GNU GPL v2 with + * a Linking Exception. For full terms see the included COPYING file. + */ +#ifndef INCLUDE_net_h__ +#define INCLUDE_net_h__ + +#include "common.h" + +typedef struct git_net_url { + char *scheme; + char *host; + char *port; + char *path; + char *query; + char *username; + char *password; +} git_net_url; + +#define GIT_NET_URL_INIT { NULL } + +/** Parses a string containing a URL into a structure. */ +int git_net_url_parse(git_net_url *url, const char *str); + +/** Returns nonzero if the URL is on the default port. */ +int git_net_url_is_default_port(git_net_url *url); + +/** Swaps the contents of one URL for another. */ +void git_net_url_swap(git_net_url *a, git_net_url *b); + +/** Disposes the contents of the structure. */ +void git_net_url_dispose(git_net_url *url); + +#endif diff --git a/src/netops.c b/src/netops.c index ecbc2aebe..708f694e3 100644 --- a/src/netops.c +++ b/src/netops.c @@ -119,192 +119,79 @@ int gitno__match_host(const char *pattern, const char *host) return -1; } -static const char *default_port_http = "80"; -static const char *default_port_https = "443"; - -const char *gitno__default_port( - gitno_connection_data *data) -{ - return data->use_ssl ? default_port_https : default_port_http; -} - -static const char *prefix_http = "http://"; -static const char *prefix_https = "https://"; - -int gitno_connection_data_from_url( - gitno_connection_data *data, - const char *url, +int gitno_connection_data_handle_redirect( + git_net_url *url, + const char *redirect_str, const char *service_suffix) { - int error = -1; - const char *default_port = NULL, *path_search_start = NULL; - char *original_host = NULL; - - /* service_suffix is optional */ - assert(data && url); + git_net_url tmp = GIT_NET_URL_INIT; + int error = 0; - /* Save these for comparison later */ - original_host = data->host; - data->host = NULL; - gitno_connection_data_free_ptrs(data); + assert(url && redirect_str); - if (!git__prefixcmp(url, prefix_http)) { - path_search_start = url + strlen(prefix_http); - default_port = default_port_http; + if (redirect_str[0] == '/') { + git__free(url->path); - if (data->use_ssl) { - git_error_set(GIT_ERROR_NET, "redirect from HTTPS to HTTP is not allowed"); - goto cleanup; + if ((url->path = git__strdup(redirect_str)) == NULL) { + error = -1; + goto done; } - } else if (!git__prefixcmp(url, prefix_https)) { - path_search_start = url + strlen(prefix_https); - default_port = default_port_https; - data->use_ssl = true; - } else if (url[0] == '/') - default_port = gitno__default_port(data); - - if (!default_port) { - git_error_set(GIT_ERROR_NET, "unrecognized URL prefix"); - goto cleanup; - } + } else { + git_net_url *original = url; - error = gitno_extract_url_parts( - &data->host, &data->port, &data->path, &data->user, &data->pass, - url, default_port); + if ((error = git_net_url_parse(&tmp, redirect_str)) < 0) + goto done; - if (url[0] == '/') { - /* Relative redirect; reuse original host name and port */ - path_search_start = url; - git__free(data->host); - data->host = original_host; - original_host = NULL; - } + /* Validate that this is a legal redirection */ - if (!error) { - const char *path = strchr(path_search_start, '/'); - size_t pathlen = strlen(path); - size_t suffixlen = service_suffix ? strlen(service_suffix) : 0; - - if (suffixlen && - !memcmp(path + pathlen - suffixlen, service_suffix, suffixlen)) { - git__free(data->path); - data->path = git__strndup(path, pathlen - suffixlen); - } else { - git__free(data->path); - data->path = git__strdup(path); - } + if (original->scheme && + strcmp(original->scheme, tmp.scheme) != 0 && + strcmp(tmp.scheme, "https") != 0) { + git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'", + original->scheme, tmp.scheme); - /* Check for errors in the resulting data */ - if (original_host && url[0] != '/' && strcmp(original_host, data->host)) { - git_error_set(GIT_ERROR_NET, "cross host redirect not allowed"); error = -1; + goto done; } - } -cleanup: - if (original_host) git__free(original_host); - return error; -} + if (original->host && + git__strcasecmp(original->host, tmp.host) != 0) { + git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'", + original->host, tmp.host); -void gitno_connection_data_free_ptrs(gitno_connection_data *d) -{ - git__free(d->host); d->host = NULL; - git__free(d->port); d->port = NULL; - git__free(d->path); d->path = NULL; - git__free(d->user); d->user = NULL; - git__free(d->pass); d->pass = NULL; -} - -int gitno_extract_url_parts( - char **host_out, - char **port_out, - char **path_out, - char **username_out, - char **password_out, - const char *url, - const char *default_port) -{ - struct http_parser_url u = {0}; - bool has_host, has_port, has_path, has_userinfo; - git_buf host = GIT_BUF_INIT, - port = GIT_BUF_INIT, - path = GIT_BUF_INIT, - username = GIT_BUF_INIT, - password = GIT_BUF_INIT; - int error = 0; + error = -1; + goto done; + } - if (http_parser_parse_url(url, strlen(url), false, &u)) { - git_error_set(GIT_ERROR_NET, "malformed URL '%s'", url); - error = GIT_EINVALIDSPEC; - goto done; + git_net_url_swap(url, &tmp); } - has_host = !!(u.field_set & (1 << UF_HOST)); - has_port = !!(u.field_set & (1 << UF_PORT)); - has_path = !!(u.field_set & (1 << UF_PATH)); - has_userinfo = !!(u.field_set & (1 << UF_USERINFO)); + /* Remove the service suffix if it was given to us */ + if (service_suffix) { + const char *service_query = strchr(service_suffix, '?'); + size_t suffix_len = service_query ? + (size_t)(service_query - service_suffix) : strlen(service_suffix); + size_t path_len = strlen(url->path); - if (has_host) { - const char *url_host = url + u.field_data[UF_HOST].off; - size_t url_host_len = u.field_data[UF_HOST].len; - git_buf_decode_percent(&host, url_host, url_host_len); - } + if (suffix_len && path_len >= suffix_len) { + size_t suffix_offset = path_len - suffix_len; - if (has_port) { - const char *url_port = url + u.field_data[UF_PORT].off; - size_t url_port_len = u.field_data[UF_PORT].len; - git_buf_put(&port, url_port, url_port_len); - } else { - git_buf_puts(&port, default_port); - } + if (git__strncmp(url->path + suffix_offset, service_suffix, suffix_len) == 0 && + (!service_query || git__strcmp(url->query, service_query + 1) == 0)) { + /* Ensure we leave a minimum of '/' as the path */ + if (suffix_offset == 0) + suffix_offset++; - if (has_path && path_out) { - const char *url_path = url + u.field_data[UF_PATH].off; - size_t url_path_len = u.field_data[UF_PATH].len; - git_buf_decode_percent(&path, url_path, url_path_len); - } else if (path_out) { - git_error_set(GIT_ERROR_NET, "invalid url, missing path"); - error = GIT_EINVALIDSPEC; - goto done; - } + url->path[suffix_offset] = '\0'; - if (has_userinfo) { - const char *url_userinfo = url + u.field_data[UF_USERINFO].off; - size_t url_userinfo_len = u.field_data[UF_USERINFO].len; - const char *colon = memchr(url_userinfo, ':', url_userinfo_len); - - if (colon) { - const char *url_username = url_userinfo; - size_t url_username_len = colon - url_userinfo; - const char *url_password = colon + 1; - size_t url_password_len = url_userinfo_len - (url_username_len + 1); - - git_buf_decode_percent(&username, url_username, url_username_len); - git_buf_decode_percent(&password, url_password, url_password_len); - } else { - git_buf_decode_percent(&username, url_userinfo, url_userinfo_len); + git__free(url->query); + url->query = NULL; + } } } - if (git_buf_oom(&host) || - git_buf_oom(&port) || - git_buf_oom(&path) || - git_buf_oom(&username) || - git_buf_oom(&password)) - return -1; - - *host_out = git_buf_detach(&host); - *port_out = git_buf_detach(&port); - if (path_out) - *path_out = git_buf_detach(&path); - *username_out = git_buf_detach(&username); - *password_out = git_buf_detach(&password); - done: - git_buf_dispose(&host); - git_buf_dispose(&port); - git_buf_dispose(&path); - git_buf_dispose(&username); - git_buf_dispose(&password); + git_net_url_dispose(&tmp); return error; } + diff --git a/src/netops.h b/src/netops.h index f376bd911..4c4bf78b0 100644 --- a/src/netops.h +++ b/src/netops.h @@ -11,6 +11,7 @@ #include "posix.h" #include "stream.h" +#include "net.h" #ifdef GIT_OPENSSL # include <openssl/ssl.h> @@ -64,38 +65,15 @@ int gitno_recv(gitno_buffer *buf); void gitno_consume(gitno_buffer *buf, const char *ptr); void gitno_consume_n(gitno_buffer *buf, size_t cons); -typedef struct gitno_connection_data { - char *host; - char *port; - char *path; - char *user; - char *pass; - bool use_ssl; -} gitno_connection_data; - /* * This replaces all the pointers in `data` with freshly-allocated strings, * that the caller is responsible for freeing. * `gitno_connection_data_free_ptrs` is good for this. */ -int gitno_connection_data_from_url( - gitno_connection_data *data, +int gitno_connection_data_handle_redirect( + git_net_url *data, const char *url, const char *service_suffix); -/* This frees all the pointers IN the struct, but not the struct itself. */ -void gitno_connection_data_free_ptrs(gitno_connection_data *data); - -int gitno_extract_url_parts( - char **host, - char **port, - char **path, - char **username, - char **password, - const char *url, - const char *default_port); - -const char *gitno__default_port(gitno_connection_data *data); - #endif diff --git a/src/transports/auth.c b/src/transports/auth.c index 6c69282b4..ea6cb6502 100644 --- a/src/transports/auth.c +++ b/src/transports/auth.c @@ -56,18 +56,18 @@ static git_http_auth_context basic_context = { }; int git_http_auth_basic( - git_http_auth_context **out, const gitno_connection_data *connection_data) + git_http_auth_context **out, const git_net_url *url) { - GIT_UNUSED(connection_data); + GIT_UNUSED(url); *out = &basic_context; return 0; } int git_http_auth_dummy( - git_http_auth_context **out, const gitno_connection_data *connection_data) + git_http_auth_context **out, const git_net_url *url) { - GIT_UNUSED(connection_data); + GIT_UNUSED(url); *out = NULL; return 0; diff --git a/src/transports/auth.h b/src/transports/auth.h index e5cf7eff0..7b64770d6 100644 --- a/src/transports/auth.h +++ b/src/transports/auth.h @@ -50,15 +50,15 @@ typedef struct { /** Function to initialize an authentication context */ int (*init_context)( git_http_auth_context **out, - const gitno_connection_data *connection_data); + const git_net_url *url); } git_http_auth_scheme; int git_http_auth_dummy( git_http_auth_context **out, - const gitno_connection_data *connection_data); + const git_net_url *url); int git_http_auth_basic( git_http_auth_context **out, - const gitno_connection_data *connection_data); + const git_net_url *url); #endif diff --git a/src/transports/auth_negotiate.c b/src/transports/auth_negotiate.c index 25c865c15..03d333626 100644 --- a/src/transports/auth_negotiate.c +++ b/src/transports/auth_negotiate.c @@ -194,7 +194,7 @@ static void negotiate_context_free(git_http_auth_context *c) static int negotiate_init_context( http_auth_negotiate_context *ctx, - const gitno_connection_data *connection_data) + const git_net_url *url) { OM_uint32 status_major, status_minor; gss_OID item, *oid; @@ -235,7 +235,7 @@ static int negotiate_init_context( } git_buf_puts(&ctx->target, "HTTP@"); - git_buf_puts(&ctx->target, connection_data->host); + git_buf_puts(&ctx->target, url->host); if (git_buf_oom(&ctx->target)) return -1; @@ -248,7 +248,7 @@ static int negotiate_init_context( int git_http_auth_negotiate( git_http_auth_context **out, - const gitno_connection_data *connection_data) + const git_net_url *url) { http_auth_negotiate_context *ctx; @@ -257,7 +257,7 @@ int git_http_auth_negotiate( ctx = git__calloc(1, sizeof(http_auth_negotiate_context)); GIT_ERROR_CHECK_ALLOC(ctx); - if (negotiate_init_context(ctx, connection_data) < 0) { + if (negotiate_init_context(ctx, url) < 0) { git__free(ctx); return -1; } diff --git a/src/transports/auth_negotiate.h b/src/transports/auth_negotiate.h index 15a528aaf..d3f2ba2a1 100644 --- a/src/transports/auth_negotiate.h +++ b/src/transports/auth_negotiate.h @@ -16,7 +16,7 @@ extern int git_http_auth_negotiate( git_http_auth_context **out, - const gitno_connection_data *connection_data); + const git_net_url *url); #else diff --git a/src/transports/git.c b/src/transports/git.c index 9fd3b47fc..e48b7f961 100644 --- a/src/transports/git.c +++ b/src/transports/git.c @@ -192,8 +192,9 @@ static int _git_uploadpack_ls( const char *url, git_smart_subtransport_stream **stream) { - char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; + git_net_url urldata = GIT_NET_URL_INIT; const char *stream_url = url; + const char *host, *port; git_proto_stream *s; int error; @@ -202,17 +203,15 @@ static int _git_uploadpack_ls( if (!git__prefixcmp(url, prefix_git)) stream_url += strlen(prefix_git); - if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT)) < 0) + if ((error = git_net_url_parse(&urldata, url)) < 0) return error; - error = git_proto_stream_alloc(t, stream_url, cmd_uploadpack, host, port, stream); + host = urldata.host; + port = urldata.port ? urldata.port : GIT_DEFAULT_PORT; - git__free(host); - git__free(port); - git__free(path); - git__free(user); - git__free(pass); + error = git_proto_stream_alloc(t, stream_url, cmd_uploadpack, host, port, stream); + git_net_url_dispose(&urldata); if (error < 0) { git_proto_stream_free(*stream); @@ -251,7 +250,7 @@ static int _git_receivepack_ls( const char *url, git_smart_subtransport_stream **stream) { - char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; + git_net_url urldata = GIT_NET_URL_INIT; const char *stream_url = url; git_proto_stream *s; int error; @@ -260,16 +259,12 @@ static int _git_receivepack_ls( if (!git__prefixcmp(url, prefix_git)) stream_url += strlen(prefix_git); - if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, GIT_DEFAULT_PORT)) < 0) + if ((error = git_net_url_parse(&urldata, url)) < 0) return error; - error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, host, port, stream); + error = git_proto_stream_alloc(t, stream_url, cmd_receivepack, urldata.host, urldata.port, stream); - git__free(host); - git__free(port); - git__free(path); - git__free(user); - git__free(pass); + git_net_url_dispose(&urldata); if (error < 0) { git_proto_stream_free(*stream); diff --git a/src/transports/http.c b/src/transports/http.c index 9d77e627f..a93d36b9e 100644 --- a/src/transports/http.c +++ b/src/transports/http.c @@ -12,6 +12,7 @@ #include "git2.h" #include "http_parser.h" #include "buffer.h" +#include "net.h" #include "netops.h" #include "global.h" #include "remote.h" @@ -71,7 +72,7 @@ typedef struct { } http_stream; typedef struct { - gitno_connection_data url; + git_net_url url; git_stream *stream; git_cred *cred; @@ -191,10 +192,10 @@ static int apply_credentials( git_http_auth_context *context; /* Apply the credentials given to us in the URL */ - if (!cred && server->url.user && server->url.pass) { + if (!cred && server->url.username && server->url.password) { if (!server->url_cred && git_cred_userpass_plaintext_new(&server->url_cred, - server->url.user, server->url.pass) < 0) + server->url.username, server->url.password) < 0) return -1; cred = server->url_cred; @@ -226,7 +227,7 @@ static int gen_request( if (t->proxy_opts.type == GIT_PROXY_SPECIFIED) git_buf_printf(buf, "%s %s://%s:%s%s%s HTTP/1.1\r\n", s->verb, - t->server.url.use_ssl ? "https" : "http", + t->server.url.scheme, t->server.url.host, t->server.url.port, path, s->service_url); @@ -238,9 +239,10 @@ static int gen_request( git_http__user_agent(buf); git_buf_puts(buf, "\r\n"); git_buf_printf(buf, "Host: %s", t->server.url.host); - if (strcmp(t->server.url.port, gitno__default_port(&t->server.url)) != 0) { + + if (!git_net_url_is_default_port(&t->server.url)) git_buf_printf(buf, ":%s", t->server.url.port); - } + git_buf_puts(buf, "\r\n"); if (s->chunked || content_length > 0) { @@ -484,7 +486,7 @@ static int on_headers_complete(http_parser *parser) SERVER_TYPE_PROXY, t->proxy_opts.credentials, t->proxy_opts.payload, - t->proxy.url.user, + t->proxy.url.username, proxy_auth_types); /* Check for an authentication failure. */ @@ -495,7 +497,7 @@ static int on_headers_complete(http_parser *parser) SERVER_TYPE_REMOTE, t->owner->cred_acquire_cb, t->owner->cred_acquire_payload, - t->server.url.user, + t->server.url.username, server_auth_types); /* Check for a redirect. @@ -507,7 +509,7 @@ static int on_headers_complete(http_parser *parser) parser->status_code == 308) && t->location) { - if (gitno_connection_data_from_url(&t->server.url, t->location, s->service_url) < 0) + if (gitno_connection_data_handle_redirect(&t->server.url, t->location, s->service_url) < 0) return t->parse_error = PARSE_ERROR_GENERIC; /* Set the redirect URL on the stream. This is a transfer of @@ -676,7 +678,7 @@ static int load_proxy_config(http_subtransport *t) git_proxy_init_options(&t->proxy_opts, GIT_PROXY_OPTIONS_VERSION); if ((error = git_remote__get_http_proxy(t->owner->owner, - !!t->server.url.use_ssl, &t->proxy_url)) < 0) + !strcmp(t->server.url.scheme, "https"), &t->proxy_url)) < 0) return error; if (!t->proxy_url) @@ -698,10 +700,11 @@ static int load_proxy_config(http_subtransport *t) return -1; } - if ((error = gitno_connection_data_from_url(&t->proxy.url, t->proxy_opts.url, NULL)) < 0) + git_net_url_dispose(&t->proxy.url); + if ((error = git_net_url_parse(&t->proxy.url, t->proxy_opts.url)) < 0) return error; - if (t->proxy.url.use_ssl) { + if (!strcmp(t->proxy.url.scheme, "https")) { git_error_set(GIT_ERROR_NET, "SSL connections to proxy are not supported"); return -1; } @@ -711,7 +714,7 @@ static int load_proxy_config(http_subtransport *t) static int check_certificate( git_stream *stream, - gitno_connection_data *url, + git_net_url *url, int is_valid, git_transport_certificate_check_cb cert_cb, void *cert_cb_payload) @@ -740,7 +743,7 @@ static int check_certificate( static int stream_connect( git_stream *stream, - gitno_connection_data *url, + git_net_url *url, git_transport_certificate_check_cb cert_cb, void *cb_payload) { @@ -812,7 +815,7 @@ static int proxy_headers_complete(http_parser *parser) SERVER_TYPE_PROXY, t->proxy_opts.credentials, t->proxy_opts.payload, - t->proxy.url.user, + t->proxy.url.username, proxy_auth_types); if (parser->status_code != 200) { @@ -927,7 +930,7 @@ done: static int http_connect(http_subtransport *t) { - gitno_connection_data *url; + git_net_url *url; git_stream *proxy_stream = NULL, *stream = NULL; git_transport_certificate_check_cb cert_cb; void *cb_payload; @@ -965,7 +968,7 @@ static int http_connect(http_subtransport *t) cb_payload = t->owner->message_cb_payload; } - if (url->use_ssl) + if (strcmp(url->scheme, "https") == 0) error = git_tls_stream_new(&stream, url->host, url->port); else error = git_socket_stream_new(&stream, url->host, url->port); @@ -982,7 +985,7 @@ static int http_connect(http_subtransport *t) * an HTTPS connection, then we need to build a CONNECT tunnel. */ if (t->proxy_opts.type == GIT_PROXY_SPECIFIED && - t->server.url.use_ssl) { + strcmp(t->server.url.scheme, "https") == 0) { proxy_stream = stream; stream = NULL; @@ -1378,7 +1381,7 @@ static int http_action( * that would be insecure in plaintext (eg, HTTP Basic). */ if ((!t->server.url.host || !t->server.url.port || !t->server.url.path) && - (ret = gitno_connection_data_from_url(&t->server.url, url, NULL)) < 0) + (ret = git_net_url_parse(&t->server.url, url)) < 0) return ret; assert(t->server.url.host && t->server.url.port && t->server.url.path); @@ -1445,11 +1448,8 @@ static int http_close(git_smart_subtransport *subtransport) free_auth_contexts(&t->server.auth_contexts); free_auth_contexts(&t->proxy.auth_contexts); - gitno_connection_data_free_ptrs(&t->server.url); - memset(&t->server.url, 0x0, sizeof(gitno_connection_data)); - - gitno_connection_data_free_ptrs(&t->proxy.url); - memset(&t->proxy.url, 0x0, sizeof(gitno_connection_data)); + git_net_url_dispose(&t->server.url); + git_net_url_dispose(&t->proxy.url); git__free(t->proxy_url); t->proxy_url = NULL; diff --git a/src/transports/ssh.c b/src/transports/ssh.c index 9b5d8a528..caa3a17f5 100644 --- a/src/transports/ssh.c +++ b/src/transports/ssh.c @@ -14,6 +14,7 @@ #include "global.h" #include "git2.h" #include "buffer.h" +#include "net.h" #include "netops.h" #include "smart.h" #include "cred.h" @@ -258,8 +259,7 @@ static int ssh_stream_alloc( } static int git_ssh_extract_url_parts( - char **host, - char **username, + git_net_url *urldata, const char *url) { char *colon, *at; @@ -271,11 +271,11 @@ static int git_ssh_extract_url_parts( at = strchr(url, '@'); if (at) { start = at + 1; - *username = git__substrdup(url, at - url); - GIT_ERROR_CHECK_ALLOC(*username); + urldata->username = git__substrdup(url, at - url); + GIT_ERROR_CHECK_ALLOC(urldata->username); } else { start = url; - *username = NULL; + urldata->username = NULL; } if (colon == NULL || (colon < start)) { @@ -283,8 +283,8 @@ static int git_ssh_extract_url_parts( return -1; } - *host = git__substrdup(start, colon - start); - GIT_ERROR_CHECK_ALLOC(*host); + urldata->host = git__substrdup(start, colon - start); + GIT_ERROR_CHECK_ALLOC(urldata->host); return 0; } @@ -506,14 +506,15 @@ static int _git_ssh_session_create( return 0; } +#define SSH_DEFAULT_PORT "22" + static int _git_ssh_setup_conn( ssh_subtransport *t, const char *url, const char *cmd, git_smart_subtransport_stream **stream) { - char *host=NULL, *port=NULL, *path=NULL, *user=NULL, *pass=NULL; - const char *default_port="22"; + git_net_url urldata = GIT_NET_URL_INIT; int auth_methods, error = 0; size_t i; ssh_stream *s; @@ -535,19 +536,22 @@ static int _git_ssh_setup_conn( const char *p = ssh_prefixes[i]; if (!git__prefixcmp(url, p)) { - if ((error = gitno_extract_url_parts(&host, &port, &path, &user, &pass, url, default_port)) < 0) + if ((error = git_net_url_parse(&urldata, url)) < 0) goto done; goto post_extract; } } - if ((error = git_ssh_extract_url_parts(&host, &user, url)) < 0) + if ((error = git_ssh_extract_url_parts(&urldata, url)) < 0) goto done; - port = git__strdup(default_port); - GIT_ERROR_CHECK_ALLOC(port); + + if (urldata.port == NULL) + urldata.port = git__strdup(SSH_DEFAULT_PORT); + + GIT_ERROR_CHECK_ALLOC(urldata.port); post_extract: - if ((error = git_socket_stream_new(&s->io, host, port)) < 0 || + if ((error = git_socket_stream_new(&s->io, urldata.host, urldata.port)) < 0 || (error = git_stream_connect(s->io)) < 0) goto done; @@ -583,7 +587,7 @@ post_extract: cert_ptr = &cert; - error = t->owner->certificate_check_cb((git_cert *) cert_ptr, 0, host, t->owner->message_cb_payload); + error = t->owner->certificate_check_cb((git_cert *) cert_ptr, 0, urldata.host, t->owner->message_cb_payload); if (error < 0 && error != GIT_PASSTHROUGH) { if (!git_error_last()) @@ -594,21 +598,21 @@ post_extract: } /* we need the username to ask for auth methods */ - if (!user) { + if (!urldata.username) { if ((error = request_creds(&cred, t, NULL, GIT_CREDTYPE_USERNAME)) < 0) goto done; - user = git__strdup(((git_cred_username *) cred)->username); + urldata.username = git__strdup(((git_cred_username *) cred)->username); cred->free(cred); cred = NULL; - if (!user) + if (!urldata.username) goto done; - } else if (user && pass) { - if ((error = git_cred_userpass_plaintext_new(&cred, user, pass)) < 0) + } else if (urldata.username && urldata.password) { + if ((error = git_cred_userpass_plaintext_new(&cred, urldata.username, urldata.password)) < 0) goto done; } - if ((error = list_auth_methods(&auth_methods, session, user)) < 0) + if ((error = list_auth_methods(&auth_methods, session, urldata.username)) < 0) goto done; error = GIT_EAUTH; @@ -622,10 +626,10 @@ post_extract: cred = NULL; } - if ((error = request_creds(&cred, t, user, auth_methods)) < 0) + if ((error = request_creds(&cred, t, urldata.username, auth_methods)) < 0) goto done; - if (strcmp(user, git_cred__username(cred))) { + if (strcmp(urldata.username, git_cred__username(cred))) { git_error_set(GIT_ERROR_SSH, "username does not match previous request"); error = -1; goto done; @@ -662,11 +666,7 @@ done: if (cred) cred->free(cred); - git__free(host); - git__free(port); - git__free(path); - git__free(user); - git__free(pass); + git_net_url_dispose(&urldata); return error; } diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c index f922cb480..395a1a10d 100644 --- a/src/transports/winhttp.c +++ b/src/transports/winhttp.c @@ -103,8 +103,8 @@ typedef struct { typedef struct { git_smart_subtransport parent; transport_smart *owner; - gitno_connection_data connection_data; - gitno_connection_data proxy_connection_data; + git_net_url url; + git_net_url proxy_url; git_cred *cred; git_cred *url_cred; git_cred *proxy_cred; @@ -280,7 +280,7 @@ static int certificate_check(winhttp_stream *s, int valid) return GIT_ECERTIFICATE; } - if (t->owner->certificate_check_cb == NULL || !t->connection_data.use_ssl) + if (t->owner->certificate_check_cb == NULL || git__strcmp(t->url.scheme, "https") != 0) return 0; if (!WinHttpQueryOption(s->request, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &cert_ctx, &cert_ctx_size)) { @@ -292,7 +292,7 @@ static int certificate_check(winhttp_stream *s, int valid) cert.parent.cert_type = GIT_CERT_X509; cert.data = cert_ctx->pbCertEncoded; cert.len = cert_ctx->cbCertEncoded; - error = t->owner->certificate_check_cb((git_cert *) &cert, valid, t->connection_data.host, t->owner->message_cb_payload); + error = t->owner->certificate_check_cb((git_cert *) &cert, valid, t->url.host, t->owner->message_cb_payload); CertFreeCertificateContext(cert_ctx); if (error == GIT_PASSTHROUGH) @@ -329,9 +329,6 @@ static void winhttp_stream_close(winhttp_stream *s) s->sent_request = 0; } -#define SCHEME_HTTP "http://" -#define SCHEME_HTTPS "https://" - static int winhttp_stream_connect(winhttp_stream *s) { winhttp_subtransport *t = OWNING_SUBTRANSPORT(s); @@ -348,7 +345,7 @@ static int winhttp_stream_connect(winhttp_stream *s) const git_proxy_options *proxy_opts; /* Prepare URL */ - git_buf_printf(&buf, "%s%s", t->connection_data.path, s->service_url); + git_buf_printf(&buf, "%s%s", t->url.path, s->service_url); if (git_buf_oom(&buf)) return -1; @@ -367,7 +364,7 @@ static int winhttp_stream_connect(winhttp_stream *s) NULL, WINHTTP_NO_REFERER, types, - t->connection_data.use_ssl ? WINHTTP_FLAG_SECURE : 0); + git__strcmp(t->url.scheme, "https") == 0 ? WINHTTP_FLAG_SECURE : 0); if (!s->request) { git_error_set(GIT_ERROR_OS, "failed to open request"); @@ -382,7 +379,7 @@ static int winhttp_stream_connect(winhttp_stream *s) proxy_opts = &t->owner->proxy; if (proxy_opts->type == GIT_PROXY_AUTO) { /* Set proxy if necessary */ - if (git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &proxy_url) < 0) + if (git_remote__get_http_proxy(t->owner->owner, (strcmp(t->url.scheme, "https") == 0), &proxy_url) < 0) goto on_error; } else if (proxy_opts->type == GIT_PROXY_SPECIFIED) { @@ -395,38 +392,33 @@ static int winhttp_stream_connect(winhttp_stream *s) WINHTTP_PROXY_INFO proxy_info; wchar_t *proxy_wide; - if (!git__prefixcmp(proxy_url, SCHEME_HTTP)) { - t->proxy_connection_data.use_ssl = false; - } else if (!git__prefixcmp(proxy_url, SCHEME_HTTPS)) { - t->proxy_connection_data.use_ssl = true; - } else { - git_error_set(GIT_ERROR_NET, "invalid URL: '%s'", proxy_url); - return -1; - } + git_net_url_dispose(&t->proxy_url); - gitno_connection_data_free_ptrs(&t->proxy_connection_data); + if ((error = git_net_url_parse(&t->proxy_url, proxy_url)) < 0) + goto on_error; - if ((error = gitno_extract_url_parts(&t->proxy_connection_data.host, &t->proxy_connection_data.port, NULL, - &t->proxy_connection_data.user, &t->proxy_connection_data.pass, proxy_url, NULL)) < 0) + if (strcmp(t->proxy_url.scheme, "http") != 0 && strcmp(t->proxy_url.scheme, "https") != 0) { + git_error_set(GIT_ERROR_NET, "invalid URL: '%s'", proxy_url); + error = -1; goto on_error; + } - if (t->proxy_connection_data.user && t->proxy_connection_data.pass) { + if (t->proxy_url.username && t->proxy_url.password) { if (t->proxy_cred) { t->proxy_cred->free(t->proxy_cred); } - if ((error = git_cred_userpass_plaintext_new(&t->proxy_cred, t->proxy_connection_data.user, t->proxy_connection_data.pass)) < 0) + if ((error = git_cred_userpass_plaintext_new(&t->proxy_cred, t->proxy_url.username, t->proxy_url.password)) < 0) goto on_error; } - if (t->proxy_connection_data.use_ssl) - git_buf_PUTS(&processed_url, SCHEME_HTTPS); - else - git_buf_PUTS(&processed_url, SCHEME_HTTP); + git_buf_puts(&processed_url, t->proxy_url.scheme); + git_buf_PUTS(&processed_url, "://"); + + git_buf_puts(&processed_url, t->proxy_url.host); - git_buf_puts(&processed_url, t->proxy_connection_data.host); - if (t->proxy_connection_data.port) - git_buf_printf(&processed_url, ":%s", t->proxy_connection_data.port); + if (!git_net_url_is_default_port(&t->proxy_url)) + git_buf_printf(&processed_url, ":%s", t->proxy_url.port); if (git_buf_oom(&processed_url)) { error = -1; @@ -543,7 +535,7 @@ static int winhttp_stream_connect(winhttp_stream *s) } /* If requested, disable certificate validation */ - if (t->connection_data.use_ssl) { + if (strcmp(t->url.scheme, "https") == 0) { int flags; if (t->owner->parent.read_flags(&t->owner->parent, &flags) < 0) @@ -562,9 +554,9 @@ static int winhttp_stream_connect(winhttp_stream *s) /* If no other credentials have been applied and the URL has username and * password, use those */ - if (!t->cred && t->connection_data.user && t->connection_data.pass) { + if (!t->cred && t->url.username && t->url.password) { if (!t->url_cred && - git_cred_userpass_plaintext_new(&t->url_cred, t->connection_data.user, t->connection_data.pass) < 0) + git_cred_userpass_plaintext_new(&t->url_cred, t->url.username, t->url.password) < 0) goto on_error; if (apply_userpass_credential(s->request, GIT_WINHTTP_AUTH_BASIC, t->url_cred) < 0) goto on_error; @@ -741,12 +733,12 @@ static int winhttp_connect( t->connection = NULL; /* Prepare port */ - if (git__strntol32(&port, t->connection_data.port, - strlen(t->connection_data.port), NULL, 10) < 0) + if (git__strntol32(&port, t->url.port, + strlen(t->url.port), NULL, 10) < 0) return -1; /* Prepare host */ - if (git__utf8_to_16_alloc(&wide_host, t->connection_data.host) < 0) { + if (git__utf8_to_16_alloc(&wide_host, t->url.host) < 0) { git_error_set(GIT_ERROR_OS, "unable to convert host to wide characters"); return -1; } @@ -1062,7 +1054,7 @@ replay: if (!git__prefixcmp_icase(location8, prefix_https)) { /* Upgrade to secure connection; disconnect and start over */ - if (gitno_connection_data_from_url(&t->connection_data, location8, s->service_url) < 0) { + if (gitno_connection_data_handle_redirect(&t->url, location8, s->service_url) < 0) { git__free(location8); return -1; } @@ -1112,7 +1104,7 @@ replay: /* Start with the user-supplied credential callback, if present */ if (t->owner->cred_acquire_cb) { cred_error = t->owner->cred_acquire_cb(&t->cred, t->owner->url, - t->connection_data.user, allowed_types, t->owner->cred_acquire_payload); + t->url.username, allowed_types, t->owner->cred_acquire_payload); /* Treat GIT_PASSTHROUGH as though git_cred_acquire_cb isn't set */ if (cred_error == GIT_PASSTHROUGH) @@ -1124,7 +1116,7 @@ replay: /* Invoke the fallback credentials acquisition callback if necessary */ if (cred_error > 0) { cred_error = fallback_cred_acquire_cb(&t->cred, t->owner->url, - t->connection_data.user, allowed_types, NULL); + t->url.username, allowed_types, NULL); if (cred_error < 0) return cred_error; @@ -1496,7 +1488,7 @@ static int winhttp_action( int ret = -1; if (!t->connection) - if ((ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0 || + if ((ret = git_net_url_parse(&t->url, url)) < 0 || (ret = winhttp_connect(t)) < 0) return ret; @@ -1538,10 +1530,8 @@ static int winhttp_close(git_smart_subtransport *subtransport) { winhttp_subtransport *t = (winhttp_subtransport *)subtransport; - gitno_connection_data_free_ptrs(&t->connection_data); - memset(&t->connection_data, 0x0, sizeof(gitno_connection_data)); - gitno_connection_data_free_ptrs(&t->proxy_connection_data); - memset(&t->proxy_connection_data, 0x0, sizeof(gitno_connection_data)); + git_net_url_dispose(&t->url); + git_net_url_dispose(&t->proxy_url); if (t->cred) { t->cred->free(t->cred); diff --git a/tests/network/redirect.c b/tests/network/redirect.c new file mode 100644 index 000000000..3fc0b1826 --- /dev/null +++ b/tests/network/redirect.c @@ -0,0 +1,113 @@ +#include "clar_libgit2.h" +#include "net.h" +#include "netops.h" + +static git_net_url conndata; + +void test_network_redirect__initialize(void) +{ + memset(&conndata, 0, sizeof(conndata)); +} + +void test_network_redirect__cleanup(void) +{ + git_net_url_dispose(&conndata); +} + +void test_network_redirect__redirect_http(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "http://example.com/foo/bar/baz")); + cl_git_pass(gitno_connection_data_handle_redirect(&conndata, + "http://example.com/foo/bar/baz", "bar/baz")); + 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, "/foo/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); +} + +void test_network_redirect__redirect_ssl(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://example.com/foo/bar/baz")); + cl_git_pass(gitno_connection_data_handle_redirect(&conndata, + "https://example.com/foo/bar/baz", "bar/baz")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/foo/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); +} + +void test_network_redirect__redirect_leaves_root_path(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://example.com/foo/bar/baz")); + cl_git_pass(gitno_connection_data_handle_redirect(&conndata, + "https://example.com/foo/bar/baz", "/foo/bar/baz")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); +} + +void test_network_redirect__redirect_encoded_username_password(void) +{ + cl_git_pass(git_net_url_parse(&conndata, + "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz")); + cl_git_pass(gitno_connection_data_handle_redirect(&conndata, + "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz", "bar/baz")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/foo/"); + cl_assert_equal_s(conndata.username, "user/name"); + cl_assert_equal_s(conndata.password, "pass@word%zyx%v"); +} + +void test_network_redirect__redirect_cross_host_denied(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "https://bar.com/bar/baz")); + cl_git_fail_with(gitno_connection_data_handle_redirect(&conndata, + "https://foo.com/bar/baz", NULL), + -1); +} + +void test_network_redirect__redirect_http_downgrade_denied(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/baz")); + cl_git_fail_with(gitno_connection_data_handle_redirect(&conndata, + "http://foo.com/bar/baz", NULL), + -1); +} + +void test_network_redirect__redirect_relative(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://foo.com/bar/baz/biff")); + cl_git_pass(gitno_connection_data_handle_redirect(&conndata, + "/zap/baz/biff?bam", NULL)); + cl_assert_equal_s(conndata.scheme, "http"); + cl_assert_equal_s(conndata.host, "foo.com"); + cl_assert_equal_s(conndata.port, "80"); + cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); +} + +void test_network_redirect__redirect_relative_ssl(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "https://foo.com/bar/baz/biff")); + cl_git_pass(gitno_connection_data_handle_redirect(&conndata, + "/zap/baz/biff?bam", NULL)); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "foo.com"); + cl_assert_equal_s(conndata.port, "443"); + cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam"); + cl_assert_equal_p(conndata.username, NULL); + cl_assert_equal_p(conndata.password, NULL); +} diff --git a/tests/network/urlparse.c b/tests/network/urlparse.c index 4a3096baa..c2362e628 100644 --- a/tests/network/urlparse.c +++ b/tests/network/urlparse.c @@ -1,220 +1,144 @@ #include "clar_libgit2.h" -#include "netops.h" +#include "net.h" -static char *host, *port, *path, *user, *pass; -static gitno_connection_data conndata; +static git_net_url conndata; void test_network_urlparse__initialize(void) { - host = port = path = user = pass = NULL; memset(&conndata, 0, sizeof(conndata)); } 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(path); - FREE_AND_NULL(user); - FREE_AND_NULL(pass); - - gitno_connection_data_free_ptrs(&conndata); + git_net_url_dispose(&conndata); } void test_network_urlparse__trivial(void) { - cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "http://example.com/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "8080"); - cl_assert_equal_s(path, "/resource"); - cl_assert_equal_p(user, NULL); - cl_assert_equal_p(pass, NULL); + cl_git_pass(git_net_url_parse(&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_i(git_net_url_is_default_port(&conndata), 1); } void test_network_urlparse__root(void) { - cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "http://example.com/", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "8080"); - cl_assert_equal_s(path, "/"); - cl_assert_equal_p(user, NULL); - cl_assert_equal_p(pass, NULL); + cl_git_pass(git_net_url_parse(&conndata, "http://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_i(git_net_url_is_default_port(&conndata), 1); } -void test_network_urlparse__just_hostname(void) +void test_network_urlparse__implied_root(void) { - cl_git_fail_with(GIT_EINVALIDSPEC, - gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "http://example.com", "8080")); + cl_git_pass(git_net_url_parse(&conndata, "http://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_i(git_net_url_is_default_port(&conndata), 1); +} + +void test_network_urlparse__implied_root_custom_port(void) +{ + cl_git_pass(git_net_url_parse(&conndata, "http://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_i(git_net_url_is_default_port(&conndata), 0); } void test_network_urlparse__encoded_password(void) { - cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "https://user:pass%2fis%40bad@hostname.com:1234/", "1")); - cl_assert_equal_s(host, "hostname.com"); - cl_assert_equal_s(port, "1234"); - cl_assert_equal_s(path, "/"); - cl_assert_equal_s(user, "user"); - cl_assert_equal_s(pass, "pass/is@bad"); + cl_git_pass(git_net_url_parse(&conndata, + "https://user:pass%2fis%40bad@hostname.com:1234/")); + cl_assert_equal_s(conndata.scheme, "https"); + 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_i(git_net_url_is_default_port(&conndata), 0); } void test_network_urlparse__user(void) { - cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "https://user@example.com/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "8080"); - cl_assert_equal_s(path, "/resource"); - cl_assert_equal_s(user, "user"); - cl_assert_equal_p(pass, NULL); + cl_git_pass(git_net_url_parse(&conndata, + "https://user@example.com/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "443"); + 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_network_urlparse__user_pass(void) { /* user:pass@hostname.tld/resource */ - cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "https://user:pass@example.com/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "8080"); - cl_assert_equal_s(path, "/resource"); - cl_assert_equal_s(user, "user"); - cl_assert_equal_s(pass, "pass"); + cl_git_pass(git_net_url_parse(&conndata, + "https://user:pass@example.com/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + cl_assert_equal_s(conndata.host, "example.com"); + cl_assert_equal_s(conndata.port, "443"); + 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_network_urlparse__port(void) { /* hostname.tld:port/resource */ - cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "https://example.com:9191/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "9191"); - cl_assert_equal_s(path, "/resource"); - cl_assert_equal_p(user, NULL); - cl_assert_equal_p(pass, NULL); + cl_git_pass(git_net_url_parse(&conndata, + "https://example.com:9191/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + 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_i(git_net_url_is_default_port(&conndata), 0); } void test_network_urlparse__user_port(void) { /* user@hostname.tld:port/resource */ - cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "https://user@example.com:9191/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "9191"); - cl_assert_equal_s(path, "/resource"); - cl_assert_equal_s(user, "user"); - cl_assert_equal_p(pass, NULL); + cl_git_pass(git_net_url_parse(&conndata, + "https://user@example.com:9191/resource")); + cl_assert_equal_s(conndata.scheme, "https"); + 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_i(git_net_url_is_default_port(&conndata), 0); } void test_network_urlparse__user_pass_port(void) { /* user:pass@hostname.tld:port/resource */ - cl_git_pass(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "https://user:pass@example.com:9191/resource", "8080")); - cl_assert_equal_s(host, "example.com"); - cl_assert_equal_s(port, "9191"); - cl_assert_equal_s(path, "/resource"); - cl_assert_equal_s(user, "user"); - cl_assert_equal_s(pass, "pass"); -} - -void test_network_urlparse__optional_path(void) -{ - cl_git_fail(gitno_extract_url_parts(&host, &port, &path, &user, &pass, - "https://user:pass@example.com:9191", "8080")); - - cl_git_pass(gitno_extract_url_parts(&host, &port, NULL, &user, &pass, - "https://user:pass@example.com:9191", "8080")); -} - -void test_network_urlparse__connection_data_http(void) -{ - cl_git_pass(gitno_connection_data_from_url(&conndata, - "http://example.com/foo/bar/baz", "bar/baz")); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/foo/"); - cl_assert_equal_p(conndata.user, NULL); - cl_assert_equal_p(conndata.pass, NULL); - cl_assert_equal_i(conndata.use_ssl, false); -} - -void test_network_urlparse__connection_data_ssl(void) -{ - cl_git_pass(gitno_connection_data_from_url(&conndata, - "https://example.com/foo/bar/baz", "bar/baz")); + cl_git_pass(git_net_url_parse(&conndata, + "https://user:pass@example.com:9191/resource")); + cl_assert_equal_s(conndata.scheme, "https"); cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "443"); - cl_assert_equal_s(conndata.path, "/foo/"); - cl_assert_equal_p(conndata.user, NULL); - cl_assert_equal_p(conndata.pass, NULL); - cl_assert_equal_i(conndata.use_ssl, true); -} - -void test_network_urlparse__encoded_username_password(void) -{ - cl_git_pass(gitno_connection_data_from_url(&conndata, - "https://user%2fname:pass%40word%zyx%v@example.com/foo/bar/baz", "bar/baz")); - cl_assert_equal_s(conndata.host, "example.com"); - cl_assert_equal_s(conndata.port, "443"); - cl_assert_equal_s(conndata.path, "/foo/"); - cl_assert_equal_s(conndata.user, "user/name"); - cl_assert_equal_s(conndata.pass, "pass@word%zyx%v"); - cl_assert_equal_i(conndata.use_ssl, true); -} - -void test_network_urlparse__connection_data_cross_host_redirect(void) -{ - conndata.host = git__strdup("bar.com"); - cl_git_fail_with(gitno_connection_data_from_url(&conndata, - "https://foo.com/bar/baz", NULL), - -1); -} - -void test_network_urlparse__connection_data_http_downgrade(void) -{ - conndata.use_ssl = true; - cl_git_fail_with(gitno_connection_data_from_url(&conndata, - "http://foo.com/bar/baz", NULL), - -1); -} - -void test_network_urlparse__connection_data_relative_redirect(void) -{ - cl_git_pass(gitno_connection_data_from_url(&conndata, - "http://foo.com/bar/baz/biff", NULL)); - cl_git_pass(gitno_connection_data_from_url(&conndata, - "/zap/baz/biff?bam", NULL)); - cl_assert_equal_s(conndata.host, "foo.com"); - cl_assert_equal_s(conndata.port, "80"); - cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam"); - cl_assert_equal_p(conndata.user, NULL); - cl_assert_equal_p(conndata.pass, NULL); - cl_assert_equal_i(conndata.use_ssl, false); -} - -void test_network_urlparse__connection_data_relative_redirect_ssl(void) -{ - cl_git_pass(gitno_connection_data_from_url(&conndata, - "https://foo.com/bar/baz/biff", NULL)); - cl_git_pass(gitno_connection_data_from_url(&conndata, - "/zap/baz/biff?bam", NULL)); - cl_assert_equal_s(conndata.host, "foo.com"); - cl_assert_equal_s(conndata.port, "443"); - cl_assert_equal_s(conndata.path, "/zap/baz/biff?bam"); - cl_assert_equal_p(conndata.user, NULL); - cl_assert_equal_p(conndata.pass, NULL); - cl_assert_equal_i(conndata.use_ssl, true); -} - -/* Run this under valgrind */ -void test_network_urlparse__connection_data_cleanup(void) -{ - cl_git_pass(gitno_connection_data_from_url(&conndata, - "http://foo.com/bar/baz/biff", "baz/biff")); - cl_git_pass(gitno_connection_data_from_url(&conndata, - "https://foo.com/bar/baz/biff", "baz/biff")); + 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); } |