]> git.proxmox.com Git - libgit2.git/blob - src/netops.c
Update upstream source from tag 'upstream/0.27.0+dfsg.1'
[libgit2.git] / src / netops.c
1 /*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
3 *
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
6 */
7
8 #include "netops.h"
9
10 #include <ctype.h>
11 #include "git2/errors.h"
12
13 #include "posix.h"
14 #include "buffer.h"
15 #include "http_parser.h"
16 #include "global.h"
17
18 int gitno_recv(gitno_buffer *buf)
19 {
20 return buf->recv(buf);
21 }
22
23 void gitno_buffer_setup_callback(
24 gitno_buffer *buf,
25 char *data,
26 size_t len,
27 int (*recv)(gitno_buffer *buf), void *cb_data)
28 {
29 memset(data, 0x0, len);
30 buf->data = data;
31 buf->len = len;
32 buf->offset = 0;
33 buf->recv = recv;
34 buf->cb_data = cb_data;
35 }
36
37 static int recv_stream(gitno_buffer *buf)
38 {
39 git_stream *io = (git_stream *) buf->cb_data;
40 int ret;
41
42 ret = git_stream_read(io, buf->data + buf->offset, buf->len - buf->offset);
43 if (ret < 0)
44 return -1;
45
46 buf->offset += ret;
47 return ret;
48 }
49
50 void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len)
51 {
52 memset(data, 0x0, len);
53 buf->data = data;
54 buf->len = len;
55 buf->offset = 0;
56 buf->recv = recv_stream;
57 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 assert(ptr - buf->data <= (int) buf->len);
67
68 consumed = ptr - buf->data;
69
70 memmove(buf->data, ptr, buf->offset - consumed);
71 memset(buf->data + buf->offset, 0x0, buf->len - buf->offset);
72 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 memmove(buf->data, buf->data + cons, buf->len - buf->offset);
79 memset(buf->data + cons, 0x0, buf->len - buf->offset);
80 buf->offset -= cons;
81 }
82
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 if (c == '\0')
90 return *host ? -1 : 0;
91
92 if (c == '*') {
93 c = *pattern;
94 /* '*' at the end matches everything left */
95 if (c == '\0')
96 return 0;
97
98 /*
99 * We've found a pattern, so move towards the next matching
100 * char. The '.' is handled specially because wildcards aren't
101 * allowed to cross subdomains.
102 */
103
104 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 return -1;
117 }
118
119 return -1;
120 }
121
122 static const char *prefix_http = "http://";
123 static const char *prefix_https = "https://";
124
125 int gitno_connection_data_from_url(
126 gitno_connection_data *data,
127 const char *url,
128 const char *service_suffix)
129 {
130 int error = -1;
131 const char *default_port = NULL, *path_search_start = NULL;
132 char *original_host = NULL;
133
134 /* service_suffix is optional */
135 assert(data && url);
136
137 /* Save these for comparison later */
138 original_host = data->host;
139 data->host = NULL;
140 gitno_connection_data_free_ptrs(data);
141
142 if (!git__prefixcmp(url, prefix_http)) {
143 path_search_start = url + strlen(prefix_http);
144 default_port = "80";
145
146 if (data->use_ssl) {
147 giterr_set(GITERR_NET, "redirect from HTTPS to HTTP is not allowed");
148 goto cleanup;
149 }
150 } else if (!git__prefixcmp(url, prefix_https)) {
151 path_search_start = url + strlen(prefix_https);
152 default_port = "443";
153 data->use_ssl = true;
154 } else if (url[0] == '/')
155 default_port = data->use_ssl ? "443" : "80";
156
157 if (!default_port) {
158 giterr_set(GITERR_NET, "unrecognized URL prefix");
159 goto cleanup;
160 }
161
162 error = gitno_extract_url_parts(
163 &data->host, &data->port, &data->path, &data->user, &data->pass,
164 url, default_port);
165
166 if (url[0] == '/') {
167 /* Relative redirect; reuse original host name and port */
168 path_search_start = url;
169 git__free(data->host);
170 data->host = original_host;
171 original_host = NULL;
172 }
173
174 if (!error) {
175 const char *path = strchr(path_search_start, '/');
176 size_t pathlen = strlen(path);
177 size_t suffixlen = service_suffix ? strlen(service_suffix) : 0;
178
179 if (suffixlen &&
180 !memcmp(path + pathlen - suffixlen, service_suffix, suffixlen)) {
181 git__free(data->path);
182 data->path = git__strndup(path, pathlen - suffixlen);
183 } else {
184 git__free(data->path);
185 data->path = git__strdup(path);
186 }
187
188 /* Check for errors in the resulting data */
189 if (original_host && url[0] != '/' && strcmp(original_host, data->host)) {
190 giterr_set(GITERR_NET, "cross host redirect not allowed");
191 error = -1;
192 }
193 }
194
195 cleanup:
196 if (original_host) git__free(original_host);
197 return error;
198 }
199
200 void gitno_connection_data_free_ptrs(gitno_connection_data *d)
201 {
202 git__free(d->host); d->host = NULL;
203 git__free(d->port); d->port = NULL;
204 git__free(d->path); d->path = NULL;
205 git__free(d->user); d->user = NULL;
206 git__free(d->pass); d->pass = NULL;
207 }
208
209 int gitno_extract_url_parts(
210 char **host_out,
211 char **port_out,
212 char **path_out,
213 char **username_out,
214 char **password_out,
215 const char *url,
216 const char *default_port)
217 {
218 struct http_parser_url u = {0};
219 bool has_host, has_port, has_path, has_userinfo;
220 git_buf host = GIT_BUF_INIT,
221 port = GIT_BUF_INIT,
222 path = GIT_BUF_INIT,
223 username = GIT_BUF_INIT,
224 password = GIT_BUF_INIT;
225 int error = 0;
226
227 if (http_parser_parse_url(url, strlen(url), false, &u)) {
228 giterr_set(GITERR_NET, "malformed URL '%s'", url);
229 error = GIT_EINVALIDSPEC;
230 goto done;
231 }
232
233 has_host = !!(u.field_set & (1 << UF_HOST));
234 has_port = !!(u.field_set & (1 << UF_PORT));
235 has_path = !!(u.field_set & (1 << UF_PATH));
236 has_userinfo = !!(u.field_set & (1 << UF_USERINFO));
237
238 if (has_host) {
239 const char *url_host = url + u.field_data[UF_HOST].off;
240 size_t url_host_len = u.field_data[UF_HOST].len;
241 git_buf_decode_percent(&host, url_host, url_host_len);
242 }
243
244 if (has_port) {
245 const char *url_port = url + u.field_data[UF_PORT].off;
246 size_t url_port_len = u.field_data[UF_PORT].len;
247 git_buf_put(&port, url_port, url_port_len);
248 } else {
249 git_buf_puts(&port, default_port);
250 }
251
252 if (has_path && path_out) {
253 const char *url_path = url + u.field_data[UF_PATH].off;
254 size_t url_path_len = u.field_data[UF_PATH].len;
255 git_buf_decode_percent(&path, url_path, url_path_len);
256 } else if (path_out) {
257 giterr_set(GITERR_NET, "invalid url, missing path");
258 error = GIT_EINVALIDSPEC;
259 goto done;
260 }
261
262 if (has_userinfo) {
263 const char *url_userinfo = url + u.field_data[UF_USERINFO].off;
264 size_t url_userinfo_len = u.field_data[UF_USERINFO].len;
265 const char *colon = memchr(url_userinfo, ':', url_userinfo_len);
266
267 if (colon) {
268 const char *url_username = url_userinfo;
269 size_t url_username_len = colon - url_userinfo;
270 const char *url_password = colon + 1;
271 size_t url_password_len = url_userinfo_len - (url_username_len + 1);
272
273 git_buf_decode_percent(&username, url_username, url_username_len);
274 git_buf_decode_percent(&password, url_password, url_password_len);
275 } else {
276 git_buf_decode_percent(&username, url_userinfo, url_userinfo_len);
277 }
278 }
279
280 if (git_buf_oom(&host) ||
281 git_buf_oom(&port) ||
282 git_buf_oom(&path) ||
283 git_buf_oom(&username) ||
284 git_buf_oom(&password))
285 return -1;
286
287 *host_out = git_buf_detach(&host);
288 *port_out = git_buf_detach(&port);
289 if (path_out)
290 *path_out = git_buf_detach(&path);
291 *username_out = git_buf_detach(&username);
292 *password_out = git_buf_detach(&password);
293
294 done:
295 git_buf_free(&host);
296 git_buf_free(&port);
297 git_buf_free(&path);
298 git_buf_free(&username);
299 git_buf_free(&password);
300 return error;
301 }