| /* |
| * 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 "netops.h" |
| |
| #include <ctype.h> |
| #include "git2/errors.h" |
| |
| #include "posix.h" |
| #include "buffer.h" |
| #include "http_parser.h" |
| #include "global.h" |
| |
| int gitno_recv(gitno_buffer *buf) |
| { |
| return buf->recv(buf); |
| } |
| |
| void gitno_buffer_setup_callback( |
| gitno_buffer *buf, |
| char *data, |
| size_t len, |
| int (*recv)(gitno_buffer *buf), void *cb_data) |
| { |
| memset(data, 0x0, len); |
| buf->data = data; |
| buf->len = len; |
| buf->offset = 0; |
| buf->recv = recv; |
| buf->cb_data = cb_data; |
| } |
| |
| static int recv_stream(gitno_buffer *buf) |
| { |
| git_stream *io = (git_stream *) buf->cb_data; |
| int ret; |
| |
| ret = git_stream_read(io, buf->data + buf->offset, buf->len - buf->offset); |
| if (ret < 0) |
| return -1; |
| |
| buf->offset += ret; |
| return ret; |
| } |
| |
| void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len) |
| { |
| memset(data, 0x0, len); |
| buf->data = data; |
| buf->len = len; |
| buf->offset = 0; |
| buf->recv = recv_stream; |
| buf->cb_data = st; |
| } |
| |
| /* Consume up to ptr and move the rest of the buffer to the beginning */ |
| void gitno_consume(gitno_buffer *buf, const char *ptr) |
| { |
| size_t consumed; |
| |
| assert(ptr - buf->data >= 0); |
| assert(ptr - buf->data <= (int) buf->len); |
| |
| consumed = ptr - buf->data; |
| |
| memmove(buf->data, ptr, buf->offset - consumed); |
| memset(buf->data + buf->offset, 0x0, buf->len - buf->offset); |
| buf->offset -= consumed; |
| } |
| |
| /* Consume const bytes and move the rest of the buffer to the beginning */ |
| void gitno_consume_n(gitno_buffer *buf, size_t cons) |
| { |
| memmove(buf->data, buf->data + cons, buf->len - buf->offset); |
| memset(buf->data + cons, 0x0, buf->len - buf->offset); |
| buf->offset -= cons; |
| } |
| |
| /* Match host names according to RFC 2818 rules */ |
| int gitno__match_host(const char *pattern, const char *host) |
| { |
| for (;;) { |
| char c = git__tolower(*pattern++); |
| |
| if (c == '\0') |
| return *host ? -1 : 0; |
| |
| if (c == '*') { |
| c = *pattern; |
| /* '*' at the end matches everything left */ |
| if (c == '\0') |
| return 0; |
| |
| /* |
| * We've found a pattern, so move towards the next matching |
| * char. The '.' is handled specially because wildcards aren't |
| * allowed to cross subdomains. |
| */ |
| |
| while(*host) { |
| char h = git__tolower(*host); |
| if (c == h) |
| return gitno__match_host(pattern, host++); |
| if (h == '.') |
| return gitno__match_host(pattern, host); |
| host++; |
| } |
| return -1; |
| } |
| |
| if (c != git__tolower(*host++)) |
| return -1; |
| } |
| |
| 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, |
| 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); |
| |
| /* Save these for comparison later */ |
| original_host = data->host; |
| data->host = NULL; |
| gitno_connection_data_free_ptrs(data); |
| |
| if (!git__prefixcmp(url, prefix_http)) { |
| path_search_start = url + strlen(prefix_http); |
| default_port = default_port_http; |
| |
| if (data->use_ssl) { |
| git_error_set(GIT_ERROR_NET, "redirect from HTTPS to HTTP is not allowed"); |
| goto cleanup; |
| } |
| } 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; |
| } |
| |
| error = gitno_extract_url_parts( |
| &data->host, &data->port, &data->path, &data->user, &data->pass, |
| url, default_port); |
| |
| 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; |
| } |
| |
| 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); |
| } |
| |
| /* 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; |
| } |
| } |
| |
| cleanup: |
| if (original_host) git__free(original_host); |
| return error; |
| } |
| |
| 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; |
| |
| 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; |
| } |
| |
| 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)); |
| |
| 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 (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 (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; |
| } |
| |
| 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); |
| } |
| } |
| |
| 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); |
| return error; |
| } |