File Coverage

deps/libgit2/src/net.c
Criterion Covered Total %
statement 0 227 0.0
branch 0 140 0.0
condition n/a
subroutine n/a
pod n/a
total 0 367 0.0


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