]> git.proxmox.com Git - libgit2.git/blame - src/netops.c
Merge pull request #2570 from cirosantilli/rm-unused-var
[libgit2.git] / src / netops.c
CommitLineData
1b4f8140 1/*
359fc2d2 2 * Copyright (C) the libgit2 contributors. All rights reserved.
1b4f8140 3 *
bb742ede
VM
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.
1b4f8140 6 */
39cdf272 7#ifndef _WIN32
6e34111e
VM
8# include <sys/types.h>
9# include <sys/socket.h>
10# include <sys/select.h>
31ffc141 11# include <sys/time.h>
6e34111e 12# include <netdb.h>
7a2cf780 13# include <netinet/in.h>
66798ad0 14# include <arpa/inet.h>
4e95ef02 15#else
90c2b37f 16# include <winsock2.h>
c1318f71 17# include <ws2tcpip.h>
6e34111e 18# ifdef _MSC_VER
41fb1ca0 19# pragma comment(lib, "ws2_32")
6e34111e 20# endif
4e95ef02 21#endif
1b4f8140 22
d3e1367f 23#ifdef GIT_SSL
a6f24a5b 24# include <openssl/ssl.h>
41fb1ca0 25# include <openssl/err.h>
dbb36e1b 26# include <openssl/x509v3.h>
66024c7c 27#endif
6e34111e 28
dbb36e1b 29#include <ctype.h>
1b4f8140
CMN
30#include "git2/errors.h"
31
32#include "common.h"
33#include "netops.h"
34bfb4b0 34#include "posix.h"
bd6585a7 35#include "buffer.h"
c227c173 36#include "http_parser.h"
1d3364ac 37#include "global.h"
bd6585a7
CMN
38
39#ifdef GIT_WIN32
40static void net_set_error(const char *str)
41{
b0dc81f0 42 int error = WSAGetLastError();
c70455c7 43 char * win32_error = git_win32_get_error_message(error);
bd6585a7 44
c70455c7
SS
45 if (win32_error) {
46 giterr_set(GITERR_NET, "%s: %s", str, win32_error);
47 git__free(win32_error);
48 } else {
49 giterr_set(GITERR_NET, str);
bd25a302 50 }
bd6585a7
CMN
51}
52#else
53static void net_set_error(const char *str)
54{
55 giterr_set(GITERR_NET, "%s: %s", str, strerror(errno));
56}
57#endif
1b4f8140 58
d3e1367f 59#ifdef GIT_SSL
a6f24a5b
CMN
60static int ssl_set_error(gitno_ssl *ssl, int error)
61{
62 int err;
65ac67fb
MS
63 unsigned long e;
64
a6f24a5b 65 err = SSL_get_error(ssl->ssl, error);
65ac67fb
MS
66
67 assert(err != SSL_ERROR_WANT_READ);
68 assert(err != SSL_ERROR_WANT_WRITE);
69
70 switch (err) {
71 case SSL_ERROR_WANT_CONNECT:
72 case SSL_ERROR_WANT_ACCEPT:
73 giterr_set(GITERR_NET, "SSL error: connection failure\n");
74 break;
75 case SSL_ERROR_WANT_X509_LOOKUP:
76 giterr_set(GITERR_NET, "SSL error: x509 error\n");
77 break;
78 case SSL_ERROR_SYSCALL:
79 e = ERR_get_error();
80 if (e > 0) {
81 giterr_set(GITERR_NET, "SSL error: %s",
82 ERR_error_string(e, NULL));
83 break;
84 } else if (error < 0) {
85 giterr_set(GITERR_OS, "SSL error: syscall failure");
86 break;
87 }
88 giterr_set(GITERR_NET, "SSL error: received early EOF");
89 break;
90 case SSL_ERROR_SSL:
91 e = ERR_get_error();
92 giterr_set(GITERR_NET, "SSL error: %s",
93 ERR_error_string(e, NULL));
94 break;
95 case SSL_ERROR_NONE:
96 case SSL_ERROR_ZERO_RETURN:
97 default:
98 giterr_set(GITERR_NET, "SSL error: unknown error");
99 break;
100 }
a6f24a5b
CMN
101 return -1;
102}
66024c7c
CMN
103#endif
104
b49c8f71
CMN
105int gitno_recv(gitno_buffer *buf)
106{
107 return buf->recv(buf);
66024c7c
CMN
108}
109
d3e1367f 110#ifdef GIT_SSL
8861d32f 111static int gitno__recv_ssl(gitno_buffer *buf)
a6f24a5b
CMN
112{
113 int ret;
114
115 do {
41fb1ca0
PK
116 ret = SSL_read(buf->socket->ssl.ssl, buf->data + buf->offset, buf->len - buf->offset);
117 } while (SSL_get_error(buf->socket->ssl.ssl, ret) == SSL_ERROR_WANT_READ);
a6f24a5b 118
8861d32f
CMN
119 if (ret < 0) {
120 net_set_error("Error receiving socket data");
121 return -1;
122 }
a6f24a5b 123
8861d32f 124 buf->offset += ret;
a6f24a5b 125 return ret;
ea7a5452 126}
a6f24a5b 127#endif
ea7a5452 128
41fb1ca0 129static int gitno__recv(gitno_buffer *buf)
ea7a5452
CMN
130{
131 int ret;
132
41fb1ca0 133 ret = p_recv(buf->socket->socket, buf->data + buf->offset, buf->len - buf->offset, 0);
56b7df10 134 if (ret < 0) {
44ef8b1b 135 net_set_error("Error receiving socket data");
56b7df10
CMN
136 return -1;
137 }
ea7a5452
CMN
138
139 buf->offset += ret;
ea7a5452
CMN
140 return ret;
141}
142
e25dda51 143void gitno_buffer_setup_callback(
41fb1ca0 144 gitno_socket *socket,
e25dda51
VM
145 gitno_buffer *buf,
146 char *data,
147 size_t len,
148 int (*recv)(gitno_buffer *buf), void *cb_data)
8861d32f 149{
8861d32f
CMN
150 memset(data, 0x0, len);
151 buf->data = data;
152 buf->len = len;
153 buf->offset = 0;
41fb1ca0 154 buf->socket = socket;
8861d32f
CMN
155 buf->recv = recv;
156 buf->cb_data = cb_data;
157}
158
41fb1ca0 159void gitno_buffer_setup(gitno_socket *socket, gitno_buffer *buf, char *data, size_t len)
8861d32f
CMN
160{
161#ifdef GIT_SSL
cf15ac8a 162 if (socket->ssl.ssl) {
41fb1ca0
PK
163 gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv_ssl, NULL);
164 return;
165 }
8861d32f 166#endif
41fb1ca0
PK
167
168 gitno_buffer_setup_callback(socket, buf, data, len, gitno__recv, NULL);
8861d32f
CMN
169}
170
ea7a5452 171/* Consume up to ptr and move the rest of the buffer to the beginning */
c7c787ce 172void gitno_consume(gitno_buffer *buf, const char *ptr)
ea7a5452 173{
0bd594b6 174 size_t consumed;
ea7a5452 175
0bd594b6 176 assert(ptr - buf->data >= 0);
ea7a5452
CMN
177 assert(ptr - buf->data <= (int) buf->len);
178
c7c787ce 179 consumed = ptr - buf->data;
ea7a5452 180
c7c787ce
CMN
181 memmove(buf->data, ptr, buf->offset - consumed);
182 memset(buf->data + buf->offset, 0x0, buf->len - buf->offset);
183 buf->offset -= consumed;
ea7a5452
CMN
184}
185
186/* Consume const bytes and move the rest of the buffer to the beginning */
0bd594b6 187void gitno_consume_n(gitno_buffer *buf, size_t cons)
ea7a5452
CMN
188{
189 memmove(buf->data, buf->data + cons, buf->len - buf->offset);
190 memset(buf->data + cons, 0x0, buf->len - buf->offset);
191 buf->offset -= cons;
192}
193
d3e1367f 194#ifdef GIT_SSL
89460f3f 195
41fb1ca0
PK
196static int gitno_ssl_teardown(gitno_ssl *ssl)
197{
198 int ret;
2f7538ec 199
f2b00cbd 200 ret = SSL_shutdown(ssl->ssl);
89460f3f 201 if (ret < 0)
2f7538ec
PK
202 ret = ssl_set_error(ssl, ret);
203 else
204 ret = 0;
89460f3f 205
41fb1ca0 206 SSL_free(ssl->ssl);
2f7538ec 207 return ret;
89460f3f
CMN
208}
209
7c57cd97
AM
210#endif
211
16768191 212/* Match host names according to RFC 2818 rules */
1f0d4f3d 213int gitno__match_host(const char *pattern, const char *host)
dbb36e1b
CMN
214{
215 for (;;) {
16768191 216 char c = tolower(*pattern++);
dbb36e1b
CMN
217
218 if (c == '\0')
219 return *host ? -1 : 0;
220
221 if (c == '*') {
222 c = *pattern;
223 /* '*' at the end matches everything left */
224 if (c == '\0')
225 return 0;
226
16768191
CMN
227 /*
228 * We've found a pattern, so move towards the next matching
229 * char. The '.' is handled specially because wildcards aren't
230 * allowed to cross subdomains.
231 */
232
233 while(*host) {
234 char h = tolower(*host);
235 if (c == h)
1f0d4f3d 236 return gitno__match_host(pattern, host++);
16768191 237 if (h == '.')
1f0d4f3d 238 return gitno__match_host(pattern, host);
16768191 239 host++;
dbb36e1b 240 }
16768191 241 return -1;
dbb36e1b
CMN
242 }
243
16768191 244 if (c != tolower(*host++))
dbb36e1b
CMN
245 return -1;
246 }
247
248 return -1;
249}
250
251static int check_host_name(const char *name, const char *host)
252{
253 if (!strcasecmp(name, host))
254 return 0;
255
1f0d4f3d 256 if (gitno__match_host(name, host) < 0)
dbb36e1b
CMN
257 return -1;
258
259 return 0;
260}
261
7c57cd97
AM
262#ifdef GIT_SSL
263
41fb1ca0 264static int verify_server_cert(gitno_ssl *ssl, const char *host)
dbb36e1b
CMN
265{
266 X509 *cert;
267 X509_NAME *peer_name;
441df990
CMN
268 ASN1_STRING *str;
269 unsigned char *peer_cn = NULL;
3f9eb1e5 270 int matched = -1, type = GEN_DNS;
dbb36e1b 271 GENERAL_NAMES *alts;
3f9eb1e5
CMN
272 struct in6_addr addr6;
273 struct in_addr addr4;
274 void *addr;
441df990
CMN
275 int i = -1,j;
276
41fb1ca0 277 if (SSL_get_verify_result(ssl->ssl) != X509_V_OK) {
0d5dce26
CMN
278 giterr_set(GITERR_SSL, "The SSL certificate is invalid");
279 return -1;
280 }
3f9eb1e5
CMN
281
282 /* Try to parse the host as an IP address to see if it is */
345eef23 283 if (p_inet_pton(AF_INET, host, &addr4)) {
3f9eb1e5
CMN
284 type = GEN_IPADD;
285 addr = &addr4;
286 } else {
345eef23 287 if(p_inet_pton(AF_INET6, host, &addr6)) {
3f9eb1e5
CMN
288 type = GEN_IPADD;
289 addr = &addr6;
290 }
291 }
292
dbb36e1b 293
41fb1ca0 294 cert = SSL_get_peer_certificate(ssl->ssl);
783555d8
CMN
295 if (!cert) {
296 giterr_set(GITERR_SSL, "the server did not provide a certificate");
297 return -1;
298 }
dbb36e1b
CMN
299
300 /* Check the alternative names */
301 alts = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
302 if (alts) {
441df990 303 int num;
dbb36e1b
CMN
304
305 num = sk_GENERAL_NAME_num(alts);
306 for (i = 0; i < num && matched != 1; i++) {
307 const GENERAL_NAME *gn = sk_GENERAL_NAME_value(alts, i);
308 const char *name = (char *) ASN1_STRING_data(gn->d.ia5);
309 size_t namelen = (size_t) ASN1_STRING_length(gn->d.ia5);
310
3f9eb1e5
CMN
311 /* Skip any names of a type we're not looking for */
312 if (gn->type != type)
dbb36e1b
CMN
313 continue;
314
3f9eb1e5
CMN
315 if (type == GEN_DNS) {
316 /* If it contains embedded NULs, don't even try */
441df990 317 if (memchr(name, '\0', namelen))
3f9eb1e5
CMN
318 continue;
319
320 if (check_host_name(name, host) < 0)
321 matched = 0;
322 else
323 matched = 1;
324 } else if (type == GEN_IPADD) {
325 /* Here name isn't so much a name but a binary representation of the IP */
326 matched = !!memcmp(name, addr, namelen);
327 }
dbb36e1b
CMN
328 }
329 }
330 GENERAL_NAMES_free(alts);
331
441df990 332 if (matched == 0)
51d3f6f5 333 goto cert_fail_name;
441df990 334
dbb36e1b
CMN
335 if (matched == 1)
336 return 0;
337
338 /* If no alternative names are available, check the common name */
339 peer_name = X509_get_subject_name(cert);
441df990
CMN
340 if (peer_name == NULL)
341 goto on_error;
342
343 if (peer_name) {
344 /* Get the index of the last CN entry */
345 while ((j = X509_NAME_get_index_by_NID(peer_name, NID_commonName, i)) >= 0)
346 i = j;
347 }
348
349 if (i < 0)
350 goto on_error;
351
352 str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(peer_name, i));
353 if (str == NULL)
354 goto on_error;
355
356 /* Work around a bug in OpenSSL whereby ASN1_STRING_to_UTF8 fails if it's already in utf-8 */
357 if (ASN1_STRING_type(str) == V_ASN1_UTF8STRING) {
358 int size = ASN1_STRING_length(str);
359
360 if (size > 0) {
361 peer_cn = OPENSSL_malloc(size + 1);
362 GITERR_CHECK_ALLOC(peer_cn);
363 memcpy(peer_cn, ASN1_STRING_data(str), size);
364 peer_cn[size] = '\0';
365 }
366 } else {
367 int size = ASN1_STRING_to_UTF8(&peer_cn, str);
368 GITERR_CHECK_ALLOC(peer_cn);
369 if (memchr(peer_cn, '\0', size))
51d3f6f5 370 goto cert_fail_name;
dbb36e1b
CMN
371 }
372
441df990 373 if (check_host_name((char *)peer_cn, host) < 0)
51d3f6f5 374 goto cert_fail_name;
441df990
CMN
375
376 OPENSSL_free(peer_cn);
377
dbb36e1b 378 return 0;
441df990
CMN
379
380on_error:
381 OPENSSL_free(peer_cn);
41fb1ca0 382 return ssl_set_error(ssl, 0);
441df990 383
51d3f6f5 384cert_fail_name:
441df990 385 OPENSSL_free(peer_cn);
51d3f6f5 386 giterr_set(GITERR_SSL, "hostname does not match certificate");
9b940586 387 return GIT_ECERTIFICATE;
dbb36e1b 388}
dbb36e1b 389
41698f22 390static int ssl_setup(gitno_socket *socket, const char *host)
1d3364ac
CMN
391{
392 int ret;
393
081e76ba
CMN
394 if (git__ssl_ctx == NULL) {
395 giterr_set(GITERR_NET, "OpenSSL initialization failed");
1d3364ac 396 return -1;
081e76ba 397 }
1d3364ac 398
cf15ac8a 399 socket->ssl.ssl = SSL_new(git__ssl_ctx);
41fb1ca0
PK
400 if (socket->ssl.ssl == NULL)
401 return ssl_set_error(&socket->ssl, 0);
a6f24a5b 402
41fb1ca0
PK
403 if((ret = SSL_set_fd(socket->ssl.ssl, socket->socket)) == 0)
404 return ssl_set_error(&socket->ssl, ret);
a6f24a5b 405
41fb1ca0
PK
406 if ((ret = SSL_connect(socket->ssl.ssl)) <= 0)
407 return ssl_set_error(&socket->ssl, ret);
a6f24a5b 408
9c8dbc88 409 return verify_server_cert(&socket->ssl, host);
d3e1367f 410}
41fb1ca0
PK
411#endif
412
413static int gitno__close(GIT_SOCKET s)
d3e1367f 414{
41fb1ca0
PK
415#ifdef GIT_WIN32
416 if (SOCKET_ERROR == closesocket(s))
417 return -1;
418
419 if (0 != WSACleanup()) {
420 giterr_set(GITERR_OS, "Winsock cleanup failed");
421 return -1;
422 }
423
89460f3f 424 return 0;
41fb1ca0
PK
425#else
426 return close(s);
d3e1367f 427#endif
41fb1ca0 428}
d3e1367f 429
41fb1ca0 430int gitno_connect(gitno_socket *s_out, const char *host, const char *port, int flags)
1b4f8140 431{
44ef8b1b 432 struct addrinfo *info = NULL, *p;
1b4f8140 433 struct addrinfo hints;
44ef8b1b 434 GIT_SOCKET s = INVALID_SOCKET;
41fb1ca0
PK
435 int ret;
436
437#ifdef GIT_WIN32
438 /* on win32, the WSA context needs to be initialized
439 * before any socket calls can be performed */
440 WSADATA wsd;
441
442 if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) {
443 giterr_set(GITERR_OS, "Winsock init failed");
444 return -1;
445 }
446
447 if (LOBYTE(wsd.wVersion) != 2 || HIBYTE(wsd.wVersion) != 2) {
448 WSACleanup();
449 giterr_set(GITERR_OS, "Winsock init failed");
450 return -1;
451 }
452#endif
453
454 /* Zero the socket structure provided */
455 memset(s_out, 0x0, sizeof(gitno_socket));
1b4f8140
CMN
456
457 memset(&hints, 0x0, sizeof(struct addrinfo));
1b4f8140 458 hints.ai_socktype = SOCK_STREAM;
a8df98c6 459 hints.ai_family = AF_UNSPEC;
1b4f8140 460
798e4d53
VM
461 if ((ret = p_getaddrinfo(host, port, &hints, &info)) < 0) {
462 giterr_set(GITERR_NET,
463 "Failed to resolve address for %s: %s", host, p_gai_strerror(ret));
cd58c15c 464 return -1;
1b4f8140
CMN
465 }
466
467 for (p = info; p != NULL; p = p->ai_next) {
468 s = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
8d18f1f7 469
ccc9872d 470 if (s == INVALID_SOCKET) {
44ef8b1b
RB
471 net_set_error("error creating socket");
472 break;
1b4f8140
CMN
473 }
474
44ef8b1b
RB
475 if (connect(s, p->ai_addr, (socklen_t)p->ai_addrlen) == 0)
476 break;
1b4f8140 477
44ef8b1b 478 /* If we can't connect, try the next one */
41fb1ca0 479 gitno__close(s);
44ef8b1b 480 s = INVALID_SOCKET;
1b4f8140
CMN
481 }
482
483 /* Oops, we couldn't connect to any address */
cd58c15c 484 if (s == INVALID_SOCKET && p == NULL) {
44ef8b1b 485 giterr_set(GITERR_OS, "Failed to connect to %s", host);
cfc39f50 486 p_freeaddrinfo(info);
cd58c15c
VM
487 return -1;
488 }
44ef8b1b 489
41fb1ca0 490 s_out->socket = s;
798e4d53 491 p_freeaddrinfo(info);
66024c7c 492
41fb1ca0 493#ifdef GIT_SSL
9b940586 494 if ((flags & GITNO_CONNECT_SSL) &&
41698f22 495 (ret = ssl_setup(s_out, host)) < 0)
9b940586 496 return ret;
41fb1ca0
PK
497#else
498 /* SSL is not supported */
499 if (flags & GITNO_CONNECT_SSL) {
500 giterr_set(GITERR_OS, "SSL is not supported by this copy of libgit2.");
501 return -1;
502 }
503#endif
66024c7c 504
cd58c15c 505 return 0;
1b4f8140 506}
4e95ef02 507
d3e1367f 508#ifdef GIT_SSL
41fb1ca0 509static int gitno_send_ssl(gitno_ssl *ssl, const char *msg, size_t len, int flags)
4e95ef02 510{
0bd594b6
VM
511 int ret;
512 size_t off = 0;
4e95ef02 513
41fb1ca0
PK
514 GIT_UNUSED(flags);
515
4e95ef02 516 while (off < len) {
a6f24a5b 517 ret = SSL_write(ssl->ssl, msg + off, len - off);
4deda91b 518 if (ret <= 0 && ret != SSL_ERROR_WANT_WRITE)
a6f24a5b
CMN
519 return ssl_set_error(ssl, ret);
520
521 off += ret;
41fb1ca0 522 }
ccc9872d 523
a6f24a5b
CMN
524 return off;
525}
66024c7c
CMN
526#endif
527
41fb1ca0 528int gitno_send(gitno_socket *socket, const char *msg, size_t len, int flags)
66024c7c
CMN
529{
530 int ret;
531 size_t off = 0;
532
a6f24a5b 533#ifdef GIT_SSL
cf15ac8a 534 if (socket->ssl.ssl)
41fb1ca0 535 return gitno_send_ssl(&socket->ssl, msg, len, flags);
66024c7c
CMN
536#endif
537
538 while (off < len) {
539 errno = 0;
41fb1ca0 540 ret = p_send(socket->socket, msg + off, len - off, flags);
56b7df10 541 if (ret < 0) {
bd6585a7 542 net_set_error("Error sending data");
56b7df10
CMN
543 return -1;
544 }
4e95ef02
CMN
545
546 off += ret;
547 }
548
44ef8b1b 549 return (int)off;
4e95ef02 550}
74bd343a 551
41fb1ca0 552int gitno_close(gitno_socket *s)
bad53552 553{
41fb1ca0 554#ifdef GIT_SSL
cf15ac8a 555 if (s->ssl.ssl &&
41fb1ca0
PK
556 gitno_ssl_teardown(&s->ssl) < 0)
557 return -1;
bad53552
CMN
558#endif
559
41fb1ca0
PK
560 return gitno__close(s->socket);
561}
562
74bd343a
CMN
563int gitno_select_in(gitno_buffer *buf, long int sec, long int usec)
564{
565 fd_set fds;
566 struct timeval tv;
567
568 tv.tv_sec = sec;
569 tv.tv_usec = usec;
570
571 FD_ZERO(&fds);
41fb1ca0 572 FD_SET(buf->socket->socket, &fds);
74bd343a
CMN
573
574 /* The select(2) interface is silly */
41fb1ca0 575 return select((int)buf->socket->socket + 1, &fds, NULL, NULL, &tv);
74bd343a 576}
db84b798 577
8988688c
BS
578static const char *prefix_http = "http://";
579static const char *prefix_https = "https://";
580
581int gitno_connection_data_from_url(
582 gitno_connection_data *data,
583 const char *url,
ea59f659 584 const char *service_suffix)
8988688c 585{
ea59f659 586 int error = -1;
c227c173 587 const char *default_port = NULL, *path_search_start = NULL;
ea59f659 588 char *original_host = NULL;
8988688c
BS
589
590 /* service_suffix is optional */
591 assert(data && url);
592
ea59f659 593 /* Save these for comparison later */
b59344bf
BS
594 original_host = data->host;
595 data->host = NULL;
ea59f659
BS
596 gitno_connection_data_free_ptrs(data);
597
8988688c 598 if (!git__prefixcmp(url, prefix_http)) {
c227c173 599 path_search_start = url + strlen(prefix_http);
8988688c
BS
600 default_port = "80";
601
602 if (data->use_ssl) {
603 giterr_set(GITERR_NET, "Redirect from HTTPS to HTTP is not allowed");
ea59f659 604 goto cleanup;
8988688c 605 }
c227c173
BS
606 } else if (!git__prefixcmp(url, prefix_https)) {
607 path_search_start = url + strlen(prefix_https);
8988688c
BS
608 default_port = "443";
609 data->use_ssl = true;
c227c173 610 } else if (url[0] == '/')
41a6de28
BS
611 default_port = data->use_ssl ? "443" : "80";
612
8988688c
BS
613 if (!default_port) {
614 giterr_set(GITERR_NET, "Unrecognized URL prefix");
ea59f659 615 goto cleanup;
8988688c
BS
616 }
617
618 error = gitno_extract_url_parts(
c227c173 619 &data->host, &data->port, &data->path, &data->user, &data->pass,
8988688c
BS
620 url, default_port);
621
41a6de28
BS
622 if (url[0] == '/') {
623 /* Relative redirect; reuse original host name and port */
c227c173 624 path_search_start = url;
41a6de28
BS
625 git__free(data->host);
626 data->host = original_host;
627 original_host = NULL;
628 }
629
8988688c 630 if (!error) {
c227c173 631 const char *path = strchr(path_search_start, '/');
8988688c
BS
632 size_t pathlen = strlen(path);
633 size_t suffixlen = service_suffix ? strlen(service_suffix) : 0;
634
635 if (suffixlen &&
e1ce5249
CMN
636 !memcmp(path + pathlen - suffixlen, service_suffix, suffixlen)) {
637 git__free(data->path);
8988688c 638 data->path = git__strndup(path, pathlen - suffixlen);
e1ce5249
CMN
639 } else {
640 git__free(data->path);
8988688c 641 data->path = git__strdup(path);
e1ce5249 642 }
8988688c
BS
643
644 /* Check for errors in the resulting data */
8988688c
BS
645 if (original_host && url[0] != '/' && strcmp(original_host, data->host)) {
646 giterr_set(GITERR_NET, "Cross host redirect not allowed");
647 error = -1;
648 }
649 }
650
ea59f659
BS
651cleanup:
652 if (original_host) git__free(original_host);
8988688c
BS
653 return error;
654}
655
656void gitno_connection_data_free_ptrs(gitno_connection_data *d)
657{
658 git__free(d->host); d->host = NULL;
659 git__free(d->port); d->port = NULL;
660 git__free(d->path); d->path = NULL;
661 git__free(d->user); d->user = NULL;
662 git__free(d->pass); d->pass = NULL;
663}
664
79c44342 665#define hex2c(c) ((c | 32) % 39 - 9)
16bffd1c
BS
666static char* unescape(char *str)
667{
668 int x, y;
98eaf39a 669 int len = (int)strlen(str);
16bffd1c 670
79c44342 671 for (x=y=0; str[y]; ++x, ++y) {
16bffd1c 672 if ((str[x] = str[y]) == '%') {
79c44342
BS
673 if (y < len-2 && isxdigit(str[y+1]) && isxdigit(str[y+2])) {
674 str[x] = (hex2c(str[y+1]) << 4) + hex2c(str[y+2]);
675 y += 2;
676 }
16bffd1c
BS
677 }
678 }
679 str[x] = '\0';
680 return str;
681}
682
cf7038a6
BS
683int gitno_extract_url_parts(
684 char **host,
685 char **port,
c227c173 686 char **path,
cf7038a6
BS
687 char **username,
688 char **password,
689 const char *url,
690 const char *default_port)
db84b798 691{
c227c173
BS
692 struct http_parser_url u = {0};
693 const char *_host, *_port, *_path, *_userinfo;
db84b798 694
c227c173
BS
695 if (http_parser_parse_url(url, strlen(url), false, &u)) {
696 giterr_set(GITERR_NET, "Malformed URL '%s'", url);
697 return GIT_EINVALIDSPEC;
7e035908 698 }
db84b798 699
c227c173
BS
700 _host = url+u.field_data[UF_HOST].off;
701 _port = url+u.field_data[UF_PORT].off;
702 _path = url+u.field_data[UF_PATH].off;
703 _userinfo = url+u.field_data[UF_USERINFO].off;
704
fe294b95 705 if (u.field_set & (1 << UF_HOST)) {
c227c173
BS
706 *host = git__substrdup(_host, u.field_data[UF_HOST].len);
707 GITERR_CHECK_ALLOC(*host);
56b7df10 708 }
db84b798 709
fe294b95 710 if (u.field_set & (1 << UF_PORT))
c227c173
BS
711 *port = git__substrdup(_port, u.field_data[UF_PORT].len);
712 else
7e035908 713 *port = git__strdup(default_port);
7e035908 714 GITERR_CHECK_ALLOC(*port);
db84b798 715
fe294b95 716 if (u.field_set & (1 << UF_PATH)) {
c227c173
BS
717 *path = git__substrdup(_path, u.field_data[UF_PATH].len);
718 GITERR_CHECK_ALLOC(*path);
1380e7c6
CMN
719 } else {
720 giterr_set(GITERR_NET, "invalid url, missing path");
721 return GIT_EINVALIDSPEC;
c227c173
BS
722 }
723
fe294b95
BS
724 if (u.field_set & (1 << UF_USERINFO)) {
725 const char *colon = memchr(_userinfo, ':', u.field_data[UF_USERINFO].len);
d6eb3f9c 726 if (colon) {
16bffd1c
BS
727 *username = unescape(git__substrdup(_userinfo, colon - _userinfo));
728 *password = unescape(git__substrdup(colon+1, u.field_data[UF_USERINFO].len - (colon+1-_userinfo)));
c227c173
BS
729 GITERR_CHECK_ALLOC(*password);
730 } else {
731 *username = git__substrdup(_userinfo, u.field_data[UF_USERINFO].len);
732 }
733 GITERR_CHECK_ALLOC(*username);
16bffd1c 734
c227c173
BS
735 }
736
56b7df10 737 return 0;
db84b798 738}