Line Flow Count Block(s) Source
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 "net.h"
9 - #include "netops.h"
10 -
11 - #include <ctype.h>
12 - #include "git2/errors.h"
13 -
14 - #include "posix.h"
15 - #include "buffer.h"
16 - #include "http_parser.h"
17 - #include "global.h"
18 -
19 - #define DEFAULT_PORT_HTTP "80"
20 - #define DEFAULT_PORT_HTTPS "443"
21 - #define DEFAULT_PORT_GIT "9418"
22 - #define DEFAULT_PORT_SSH "22"
23 -
24 371 2 static const char *default_port_for_scheme(const char *scheme)
25 - {
26 371 2 if (strcmp(scheme, "http") == 0)
27 95 3 return DEFAULT_PORT_HTTP;
28 276 4 else if (strcmp(scheme, "https") == 0)
29 152 5 return DEFAULT_PORT_HTTPS;
30 124 6 else if (strcmp(scheme, "git") == 0)
31 124 7 return DEFAULT_PORT_GIT;
32 ##### 8 else if (strcmp(scheme, "ssh") == 0)
33 ##### 9 return DEFAULT_PORT_SSH;
34 -
35 ##### 10 return NULL;
36 - }
37 -
38 871 2 int git_net_url_parse(git_net_url *url, const char *given)
39 - {
40 871 2 struct http_parser_url u = {0};
41 - bool has_scheme, has_host, has_port, has_path, has_query, has_userinfo;
42 871 2 git_buf scheme = GIT_BUF_INIT,
43 871 2 host = GIT_BUF_INIT,
44 871 2 port = GIT_BUF_INIT,
45 871 2 path = GIT_BUF_INIT,
46 871 2 username = GIT_BUF_INIT,
47 871 2 password = GIT_BUF_INIT,
48 871 2 query = GIT_BUF_INIT;
49 871 2 int error = GIT_EINVALIDSPEC;
50 -
51 871 2,3 if (http_parser_parse_url(given, strlen(given), false, &u)) {
52 327 4 git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given);
53 327 5 goto done;
54 - }
55 -
56 544 6 has_scheme = !!(u.field_set & (1 << UF_SCHEMA));
57 544 6 has_host = !!(u.field_set & (1 << UF_HOST));
58 544 6 has_port = !!(u.field_set & (1 << UF_PORT));
59 544 6 has_path = !!(u.field_set & (1 << UF_PATH));
60 544 6 has_query = !!(u.field_set & (1 << UF_QUERY));
61 544 6 has_userinfo = !!(u.field_set & (1 << UF_USERINFO));
62 -
63 544 6 if (has_scheme) {
64 200 7 const char *url_scheme = given + u.field_data[UF_SCHEMA].off;
65 200 7 size_t url_scheme_len = u.field_data[UF_SCHEMA].len;
66 200 7 git_buf_put(&scheme, url_scheme, url_scheme_len);
67 200 8 git__strntolower(scheme.ptr, scheme.size);
68 - } else {
69 344 9 git_error_set(GIT_ERROR_NET, "malformed URL '%s'", given);
70 344 10 goto done;
71 - }
72 -
73 200 11 if (has_host) {
74 200 12 const char *url_host = given + u.field_data[UF_HOST].off;
75 200 12 size_t url_host_len = u.field_data[UF_HOST].len;
76 200 12 git_buf_decode_percent(&host, url_host, url_host_len);
77 - }
78 -
79 200 13 if (has_port) {
80 5 14 const char *url_port = given + u.field_data[UF_PORT].off;
81 5 14 size_t url_port_len = u.field_data[UF_PORT].len;
82 5 14 git_buf_put(&port, url_port, url_port_len);
83 - } else {
84 195 15 const char *default_port = default_port_for_scheme(scheme.ptr);
85 -
86 195 16 if (default_port == NULL) {
87 ##### 17 git_error_set(GIT_ERROR_NET, "unknown scheme for URL '%s'", given);
88 ##### 18 goto done;
89 - }
90 -
91 195 19 git_buf_puts(&port, default_port);
92 - }
93 -
94 200 20 if (has_path) {
95 195 21 const char *url_path = given + u.field_data[UF_PATH].off;
96 195 21 size_t url_path_len = u.field_data[UF_PATH].len;
97 195 21 git_buf_put(&path, url_path, url_path_len);
98 - } else {
99 5 22 git_buf_puts(&path, "/");
100 - }
101 -
102 200 23 if (has_query) {
103 24 24 const char *url_query = given + u.field_data[UF_QUERY].off;
104 24 24 size_t url_query_len = u.field_data[UF_QUERY].len;
105 24 24 git_buf_decode_percent(&query, url_query, url_query_len);
106 - }
107 -
108 200 25 if (has_userinfo) {
109 15 26 const char *url_userinfo = given + u.field_data[UF_USERINFO].off;
110 15 26 size_t url_userinfo_len = u.field_data[UF_USERINFO].len;
111 15 26 const char *colon = memchr(url_userinfo, ':', url_userinfo_len);
112 -
113 15 26 if (colon) {
114 10 27 const char *url_username = url_userinfo;
115 10 27 size_t url_username_len = colon - url_userinfo;
116 10 27 const char *url_password = colon + 1;
117 10 27 size_t url_password_len = url_userinfo_len - (url_username_len + 1);
118 -
119 10 27 git_buf_decode_percent(&username, url_username, url_username_len);
120 10 28 git_buf_decode_percent(&password, url_password, url_password_len);
121 - } else {
122 5 29 git_buf_decode_percent(&username, url_userinfo, url_userinfo_len);
123 - }
124 - }
125 -
126 200 30,31,33 if (git_buf_oom(&scheme) ||
127 200 32,35 git_buf_oom(&host) ||
128 200 34,37 git_buf_oom(&port) ||
129 200 36,39 git_buf_oom(&path) ||
130 200 38,41 git_buf_oom(&query) ||
131 200 40,43 git_buf_oom(&username) ||
132 200 42 git_buf_oom(&password))
133 ##### 44 return -1;
134 -
135 200 45 url->scheme = git_buf_detach(&scheme);
136 200 46 url->host = git_buf_detach(&host);
137 200 47 url->port = git_buf_detach(&port);
138 200 48 url->path = git_buf_detach(&path);
139 200 49 url->query = git_buf_detach(&query);
140 200 50 url->username = git_buf_detach(&username);
141 200 51 url->password = git_buf_detach(&password);
142 -
143 200 52 error = 0;
144 -
145 - done:
146 871 53 git_buf_dispose(&scheme);
147 871 54 git_buf_dispose(&host);
148 871 55 git_buf_dispose(&port);
149 871 56 git_buf_dispose(&path);
150 871 57 git_buf_dispose(&query);
151 871 58 git_buf_dispose(&username);
152 871 59 git_buf_dispose(&password);
153 871 60 return error;
154 - }
155 -
156 121 2 int git_net_url_joinpath(
157 - git_net_url *out,
158 - git_net_url *one,
159 - const char *two)
160 - {
161 121 2 git_buf path = GIT_BUF_INIT;
162 - const char *query;
163 - size_t one_len, two_len;
164 -
165 121 2 git_net_url_dispose(out);
166 -
167 121 3 if ((query = strchr(two, '?')) != NULL) {
168 79 4 two_len = query - two;
169 -
170 79 4 if (*(++query) != '\0') {
171 78 5 out->query = git__strdup(query);
172 78 6,7 GIT_ERROR_CHECK_ALLOC(out->query);
173 - }
174 - } else {
175 42 8 two_len = strlen(two);
176 - }
177 -
178 - /* Strip all trailing `/`s from the first path */
179 121 9-11 one_len = one->path ? strlen(one->path) : 0;
180 145 12,14,15 while (one_len && one->path[one_len - 1] == '/')
181 24 13 one_len--;
182 -
183 - /* Strip all leading `/`s from the second path */
184 233 16,18 while (*two == '/') {
185 112 17 two++;
186 112 17 two_len--;
187 - }
188 -
189 121 19 git_buf_put(&path, one->path, one_len);
190 121 20 git_buf_putc(&path, '/');
191 121 21 git_buf_put(&path, two, two_len);
192 -
193 121 22,23 if (git_buf_oom(&path))
194 ##### 24 return -1;
195 -
196 121 25 out->path = git_buf_detach(&path);
197 -
198 121 26 if (one->scheme) {
199 121 27 out->scheme = git__strdup(one->scheme);
200 121 28,29 GIT_ERROR_CHECK_ALLOC(out->scheme);
201 - }
202 -
203 121 30 if (one->host) {
204 121 31 out->host = git__strdup(one->host);
205 121 32,33 GIT_ERROR_CHECK_ALLOC(out->host);
206 - }
207 -
208 121 34 if (one->port) {
209 121 35 out->port = git__strdup(one->port);
210 121 36,37 GIT_ERROR_CHECK_ALLOC(out->port);
211 - }
212 -
213 121 38 if (one->username) {
214 12 39 out->username = git__strdup(one->username);
215 12 40,41 GIT_ERROR_CHECK_ALLOC(out->username);
216 - }
217 -
218 121 42 if (one->password) {
219 5 43 out->password = git__strdup(one->password);
220 5 44,45 GIT_ERROR_CHECK_ALLOC(out->password);
221 - }
222 -
223 121 46 return 0;
224 - }
225 -
226 - /*
227 - * Some servers strip the query parameters from the Location header
228 - * when sending a redirect. Others leave it in place.
229 - * Check for both, starting with the stripped case first,
230 - * since it appears to be more common.
231 - */
232 26 2 static void remove_service_suffix(
233 - git_net_url *url,
234 - const char *service_suffix)
235 - {
236 26 2 const char *service_query = strchr(service_suffix, '?');
237 26 2 size_t full_suffix_len = strlen(service_suffix);
238 26 5 size_t suffix_len = service_query ?
239 26 2-4 (size_t)(service_query - service_suffix) : full_suffix_len;
240 26 5 size_t path_len = strlen(url->path);
241 26 5 ssize_t truncate = -1;
242 -
243 - /*
244 - * Check for a redirect without query parameters,
245 - * like "/newloc/info/refs"'
246 - */
247 26 5,6 if (suffix_len && path_len >= suffix_len) {
248 26 7 size_t suffix_offset = path_len - suffix_len;
249 -
250 26 7,8 if (git__strncmp(url->path + suffix_offset, service_suffix, suffix_len) == 0 &&
251 21 9 (!service_query || git__strcmp(url->query, service_query + 1) == 0)) {
252 25 10 truncate = suffix_offset;
253 - }
254 - }
255 -
256 - /*
257 - * If we haven't already found where to truncate to remove the
258 - * suffix, check for a redirect with query parameters, like
259 - * "/newloc/info/refs?service=git-upload-pack"
260 - */
261 26 11-13 if (truncate < 0 && git__suffixcmp(url->path, service_suffix) == 0)
262 1 14 truncate = path_len - full_suffix_len;
263 -
264 - /* Ensure we leave a minimum of '/' as the path */
265 26 15 if (truncate == 0)
266 1 16 truncate++;
267 -
268 26 17 if (truncate > 0) {
269 26 18 url->path[truncate] = '\0';
270 -
271 26 18 git__free(url->query);
272 26 19 url->query = NULL;
273 - }
274 26 20 }
275 -
276 30 2 int git_net_url_apply_redirect(
277 - git_net_url *url,
278 - const char *redirect_location,
279 - const char *service_suffix)
280 - {
281 30 2 git_net_url tmp = GIT_NET_URL_INIT;
282 30 2 int error = 0;
283 -
284 30 2-4 assert(url && redirect_location);
285 -
286 30 5 if (redirect_location[0] == '/') {
287 4 6 git__free(url->path);
288 -
289 4 7,8 if ((url->path = git__strdup(redirect_location)) == NULL) {
290 ##### 9 error = -1;
291 ##### 9 goto done;
292 - }
293 - } else {
294 26 10 git_net_url *original = url;
295 -
296 26 10,11 if ((error = git_net_url_parse(&tmp, redirect_location)) < 0)
297 ##### 12 goto done;
298 -
299 - /* Validate that this is a legal redirection */
300 -
301 26 13,14 if (original->scheme &&
302 26 14,15 strcmp(original->scheme, tmp.scheme) != 0 &&
303 19 15 strcmp(tmp.scheme, "https") != 0) {
304 1 16 git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'",
305 - original->scheme, tmp.scheme);
306 -
307 1 17 error = -1;
308 1 17 goto done;
309 - }
310 -
311 25 18,20 if (original->host &&
312 25 19 git__strcasecmp(original->host, tmp.host) != 0) {
313 1 21 git_error_set(GIT_ERROR_NET, "cannot redirect from '%s' to '%s'",
314 - original->host, tmp.host);
315 -
316 1 22 error = -1;
317 1 22 goto done;
318 - }
319 -
320 24 23 git_net_url_swap(url, &tmp);
321 - }
322 -
323 - /* Remove the service suffix if it was given to us */
324 28 24 if (service_suffix)
325 26 25 remove_service_suffix(url, service_suffix);
326 -
327 - done:
328 30 26 git_net_url_dispose(&tmp);
329 30 27 return error;
330 - }
331 -
332 67 2 bool git_net_url_valid(git_net_url *url)
333 - {
334 67 2 return (url->host && url->port && url->path);
335 - }
336 -
337 176 2 int git_net_url_is_default_port(git_net_url *url)
338 - {
339 176 2 return (strcmp(url->port, default_port_for_scheme(url->scheme)) == 0);
340 - }
341 -
342 24 2 void git_net_url_swap(git_net_url *a, git_net_url *b)
343 - {
344 24 2 git_net_url tmp = GIT_NET_URL_INIT;
345 -
346 24 2 memcpy(&tmp, a, sizeof(git_net_url));
347 24 2 memcpy(a, b, sizeof(git_net_url));
348 24 2 memcpy(b, &tmp, sizeof(git_net_url));
349 24 2 }
350 -
351 79 2 int git_net_url_fmt(git_buf *buf, git_net_url *url)
352 - {
353 79 2 git_buf_puts(buf, url->scheme);
354 79 3 git_buf_puts(buf, "://");
355 -
356 79 4 if (url->username) {
357 ##### 5 git_buf_puts(buf, url->username);
358 -
359 ##### 6 if (url->password) {
360 ##### 7 git_buf_puts(buf, ":");
361 ##### 8 git_buf_puts(buf, url->password);
362 - }
363 -
364 ##### 9 git_buf_putc(buf, '@');
365 - }
366 -
367 79 10 git_buf_puts(buf, url->host);
368 -
369 79 11-13 if (url->port && !git_net_url_is_default_port(url)) {
370 ##### 14 git_buf_putc(buf, ':');
371 ##### 15 git_buf_puts(buf, url->port);
372 - }
373 -
374 79 16-19 git_buf_puts(buf, url->path ? url->path : "/");
375 -
376 79 20 if (url->query) {
377 ##### 21 git_buf_putc(buf, '?');
378 ##### 22 git_buf_puts(buf, url->query);
379 - }
380 -
381 79 23 return git_buf_oom(buf) ? -1 : 0;
382 - }
383 -
384 85 2 int git_net_url_fmt_path(git_buf *buf, git_net_url *url)
385 - {
386 85 2-5 git_buf_puts(buf, url->path ? url->path : "/");
387 -
388 85 6 if (url->query) {
389 60 7 git_buf_putc(buf, '?');
390 60 8 git_buf_puts(buf, url->query);
391 - }
392 -
393 85 9 return git_buf_oom(buf) ? -1 : 0;
394 - }
395 -
396 1862 2 void git_net_url_dispose(git_net_url *url)
397 - {
398 1862 2 if (url->username)
399 24 3 git__memzero(url->username, strlen(url->username));
400 -
401 1862 4 if (url->password)
402 12 5 git__memzero(url->password, strlen(url->password));
403 -
404 1862 6 git__free(url->scheme); url->scheme = NULL;
405 1862 7 git__free(url->host); url->host = NULL;
406 1862 8 git__free(url->port); url->port = NULL;
407 1862 9 git__free(url->path); url->path = NULL;
408 1862 10 git__free(url->query); url->query = NULL;
409 1862 11 git__free(url->username); url->username = NULL;
410 1862 12 git__free(url->password); url->password = NULL;
411 1862 13 }