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