netops.c 7.68 KB
Newer Older
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
3
 *
Vicent Marti committed
4 5
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
6
 */
7

8 9
#include "netops.h"

10
#include <ctype.h>
11 12
#include "git2/errors.h"

13
#include "posix.h"
14
#include "buffer.h"
15
#include "http_parser.h"
16
#include "global.h"
17

18 19 20
int gitno_recv(gitno_buffer *buf)
{
	return buf->recv(buf);
21 22
}

23 24 25 26 27
void gitno_buffer_setup_callback(
	gitno_buffer *buf,
	char *data,
	size_t len,
	int (*recv)(gitno_buffer *buf), void *cb_data)
28 29 30 31 32 33 34 35 36
{
	memset(data, 0x0, len);
	buf->data = data;
	buf->len = len;
	buf->offset = 0;
	buf->recv = recv;
	buf->cb_data = cb_data;
}

37
static int recv_stream(gitno_buffer *buf)
38
{
39 40
	git_stream *io = (git_stream *) buf->cb_data;
	int ret;
41

42 43 44 45 46 47
	ret = git_stream_read(io, buf->data + buf->offset, buf->len - buf->offset);
	if (ret < 0)
		return -1;

	buf->offset += ret;
	return ret;
48 49
}

50 51 52 53 54 55
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;
56 57
	buf->recv = recv_stream;
	buf->cb_data = st;
58 59
}

60
/* Consume up to ptr and move the rest of the buffer to the beginning */
61
void gitno_consume(gitno_buffer *buf, const char *ptr)
62
{
63
	size_t consumed;
64

65
	assert(ptr - buf->data >= 0);
66 67
	assert(ptr - buf->data <= (int) buf->len);

68
	consumed = ptr - buf->data;
69

70 71 72
	memmove(buf->data, ptr, buf->offset - consumed);
	memset(buf->data + buf->offset, 0x0, buf->len - buf->offset);
	buf->offset -= consumed;
73 74 75
}

/* Consume const bytes and move the rest of the buffer to the beginning */
76
void gitno_consume_n(gitno_buffer *buf, size_t cons)
77 78 79 80 81 82
{
	memmove(buf->data, buf->data + cons, buf->len - buf->offset);
	memset(buf->data + cons, 0x0, buf->len - buf->offset);
	buf->offset -= cons;
}

83
/* Match host names according to RFC 2818 rules */
84
int gitno__match_host(const char *pattern, const char *host)
85 86
{
	for (;;) {
87
		char c = git__tolower(*pattern++);
88 89 90 91 92 93 94 95 96 97

		if (c == '\0')
			return *host ? -1 : 0;

		if (c == '*') {
			c = *pattern;
			/* '*' at the end matches everything left */
			if (c == '\0')
				return 0;

98 99 100 101 102 103 104
	/*
	 * 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) {
105
				char h = git__tolower(*host);
106
				if (c == h)
107
					return gitno__match_host(pattern, host++);
108
				if (h == '.')
109
					return gitno__match_host(pattern, host);
110
				host++;
111
			}
112
			return -1;
113 114
		}

115
		if (c != git__tolower(*host++))
116 117 118 119 120 121
			return -1;
	}

	return -1;
}

122 123 124 125 126 127 128 129 130
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;
}

131 132 133 134 135 136
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,
137
		const char *service_suffix)
138
{
139
	int error = -1;
140
	const char *default_port = NULL, *path_search_start = NULL;
141
	char *original_host = NULL;
142 143 144 145

	/* service_suffix is optional */
	assert(data && url);

146
	/* Save these for comparison later */
147 148
	original_host = data->host;
	data->host = NULL;
149 150
	gitno_connection_data_free_ptrs(data);

151
	if (!git__prefixcmp(url, prefix_http)) {
152
		path_search_start = url + strlen(prefix_http);
153
		default_port = default_port_http;
154 155

		if (data->use_ssl) {
156
			git_error_set(GIT_ERROR_NET, "redirect from HTTPS to HTTP is not allowed");
157
			goto cleanup;
158
		}
159 160
	} else if (!git__prefixcmp(url, prefix_https)) {
		path_search_start = url + strlen(prefix_https);
161
		default_port = default_port_https;
162
		data->use_ssl = true;
163
	} else if (url[0] == '/')
164
		default_port = gitno__default_port(data);
165

166
	if (!default_port) {
167
		git_error_set(GIT_ERROR_NET, "unrecognized URL prefix");
168
		goto cleanup;
169 170 171
	}

	error = gitno_extract_url_parts(
172
		&data->host, &data->port, &data->path, &data->user, &data->pass,
173 174
		url, default_port);

175 176
	if (url[0] == '/') {
		/* Relative redirect; reuse original host name and port */
177
		path_search_start = url;
178 179 180 181 182
		git__free(data->host);
		data->host = original_host;
		original_host = NULL;
	}

183
	if (!error) {
184
		const char *path = strchr(path_search_start, '/');
185 186 187 188
		size_t pathlen = strlen(path);
		size_t suffixlen = service_suffix ? strlen(service_suffix) : 0;

		if (suffixlen &&
Carlos Martín Nieto committed
189 190
		    !memcmp(path + pathlen - suffixlen, service_suffix, suffixlen)) {
			git__free(data->path);
191
			data->path = git__strndup(path, pathlen - suffixlen);
Carlos Martín Nieto committed
192 193
		} else {
			git__free(data->path);
194
			data->path = git__strdup(path);
Carlos Martín Nieto committed
195
		}
196 197 198

		/* Check for errors in the resulting data */
		if (original_host && url[0] != '/' && strcmp(original_host, data->host)) {
199
			git_error_set(GIT_ERROR_NET, "cross host redirect not allowed");
200 201 202 203
			error = -1;
		}
	}

204 205
cleanup:
	if (original_host) git__free(original_host);
206 207 208 209 210 211 212 213 214 215 216 217
	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;
}

218
int gitno_extract_url_parts(
219 220 221 222 223 224 225
	char **host_out,
	char **port_out,
	char **path_out,
	char **username_out,
	char **password_out,
	const char *url,
	const char *default_port)
226
{
227
	struct http_parser_url u = {0};
228 229 230 231 232 233 234
	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;
235

236
	if (http_parser_parse_url(url, strlen(url), false, &u)) {
237
		git_error_set(GIT_ERROR_NET, "malformed URL '%s'", url);
238 239
		error = GIT_EINVALIDSPEC;
		goto done;
240
	}
241

242 243 244 245
	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));
246

247 248 249
	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;
250
		git_buf_decode_percent(&host, url_host, url_host_len);
251
	}
252

253 254 255 256 257 258 259
	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);
	}
260

261 262 263 264 265
	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) {
266
		git_error_set(GIT_ERROR_NET, "invalid url, missing path");
267 268
		error = GIT_EINVALIDSPEC;
		goto done;
269 270
	}

271 272 273 274 275
	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);

Ben Straub committed
276
		if (colon) {
277 278 279 280 281 282 283
			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);
284
		} else {
285
			git_buf_decode_percent(&username, url_userinfo, url_userinfo_len);
286 287 288
		}
	}

289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
	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:
304 305 306 307 308
	git_buf_dispose(&host);
	git_buf_dispose(&port);
	git_buf_dispose(&path);
	git_buf_dispose(&username);
	git_buf_dispose(&password);
309
	return error;
310
}