]> git.proxmox.com Git - libgit2.git/blob - src/transports/http.c
test: fix some memory leaks
[libgit2.git] / src / transports / http.c
1 /*
2 * Copyright (C) 2009-2012 the libgit2 contributors
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 #include <stdlib.h>
8 #include "git2.h"
9 #include "http_parser.h"
10
11 #include "transport.h"
12 #include "common.h"
13 #include "netops.h"
14 #include "buffer.h"
15 #include "pkt.h"
16 #include "refs.h"
17 #include "pack.h"
18 #include "fetch.h"
19 #include "filebuf.h"
20 #include "repository.h"
21 #include "protocol.h"
22 #if GIT_WINHTTP
23 # include <winhttp.h>
24 # pragma comment(lib, "winhttp.lib")
25 #endif
26
27 #define WIDEN2(s) L ## s
28 #define WIDEN(s) WIDEN2(s)
29
30 enum last_cb {
31 NONE,
32 FIELD,
33 VALUE
34 };
35
36 typedef struct {
37 git_transport parent;
38 http_parser_settings settings;
39 git_buf buf;
40 int error;
41 int transfer_finished :1,
42 ct_found :1,
43 ct_finished :1,
44 pack_ready :1;
45 enum last_cb last_cb;
46 http_parser parser;
47 char *content_type;
48 char *path;
49 char *host;
50 char *port;
51 char *service;
52 char buffer[65536];
53 #ifdef GIT_WIN32
54 WSADATA wsd;
55 #endif
56 #ifdef GIT_WINHTTP
57 HINTERNET session;
58 HINTERNET connection;
59 HINTERNET request;
60 #endif
61 } transport_http;
62
63 static int gen_request(git_buf *buf, const char *path, const char *host, const char *op,
64 const char *service, ssize_t content_length, int ls)
65 {
66 if (path == NULL) /* Is 'git fetch http://host.com/' valid? */
67 path = "/";
68
69 if (ls) {
70 git_buf_printf(buf, "%s %s/info/refs?service=git-%s HTTP/1.1\r\n", op, path, service);
71 } else {
72 git_buf_printf(buf, "%s %s/git-%s HTTP/1.1\r\n", op, path, service);
73 }
74 git_buf_puts(buf, "User-Agent: git/1.0 (libgit2 " LIBGIT2_VERSION ")\r\n");
75 git_buf_printf(buf, "Host: %s\r\n", host);
76 if (content_length > 0) {
77 git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", service);
78 git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", service);
79 git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length);
80 } else {
81 git_buf_puts(buf, "Accept: */*\r\n");
82 }
83 git_buf_puts(buf, "\r\n");
84
85 if (git_buf_oom(buf))
86 return -1;
87
88 return 0;
89 }
90
91 static int send_request(transport_http *t, const char *service, void *data, ssize_t content_length, int ls)
92 {
93 #ifndef GIT_WINHTTP
94 git_buf request = GIT_BUF_INIT;
95 const char *verb;
96 int error = -1;
97
98 verb = ls ? "GET" : "POST";
99 /* Generate and send the HTTP request */
100 if (gen_request(&request, t->path, t->host, verb, service, content_length, ls) < 0) {
101 giterr_set(GITERR_NET, "Failed to generate request");
102 return -1;
103 }
104
105
106 if (gitno_send((git_transport *) t, request.ptr, request.size, 0) < 0)
107 goto cleanup;
108
109 if (content_length) {
110 if (gitno_send((git_transport *) t, data, content_length, 0) < 0)
111 goto cleanup;
112 }
113
114 error = 0;
115
116 cleanup:
117 git_buf_free(&request);
118 return error;
119
120 #else
121 wchar_t *verb;
122 wchar_t url[GIT_WIN_PATH], ct[GIT_WIN_PATH];
123 git_buf buf = GIT_BUF_INIT;
124 BOOL ret;
125 DWORD flags;
126 void *buffer;
127 wchar_t *types[] = {
128 L"*/*",
129 NULL,
130 };
131
132 verb = ls ? L"GET" : L"POST";
133 buffer = data ? data : WINHTTP_NO_REQUEST_DATA;
134 flags = t->parent.use_ssl ? WINHTTP_FLAG_SECURE : 0;
135
136 if (ls)
137 git_buf_printf(&buf, "%s/info/refs?service=git-%s", t->path, service);
138 else
139 git_buf_printf(&buf, "%s/git-%s", t->path, service);
140
141 if (git_buf_oom(&buf))
142 return -1;
143
144 git__utf8_to_16(url, GIT_WIN_PATH, git_buf_cstr(&buf));
145
146 t->request = WinHttpOpenRequest(t->connection, verb, url, NULL, WINHTTP_NO_REFERER, types, flags);
147 if (t->request == NULL) {
148 git_buf_free(&buf);
149 giterr_set(GITERR_OS, "Failed to open request");
150 return -1;
151 }
152
153 git_buf_clear(&buf);
154 if (git_buf_printf(&buf, "Content-Type: application/x-git-%s-request", service) < 0)
155 goto on_error;
156
157 git__utf8_to_16(ct, GIT_WIN_PATH, git_buf_cstr(&buf));
158
159 if (WinHttpAddRequestHeaders(t->request, ct, (ULONG) -1L, WINHTTP_ADDREQ_FLAG_ADD) == FALSE) {
160 giterr_set(GITERR_OS, "Failed to add a header to the request");
161 goto on_error;
162 }
163
164 if (!t->parent.check_cert) {
165 int flags = SECURITY_FLAG_IGNORE_CERT_CN_INVALID | SECURITY_FLAG_IGNORE_CERT_DATE_INVALID | SECURITY_FLAG_IGNORE_UNKNOWN_CA;
166 if (WinHttpSetOption(t->request, WINHTTP_OPTION_SECURITY_FLAGS, &flags, sizeof(flags)) == FALSE) {
167 giterr_set(GITERR_OS, "Failed to set options to ignore cert errors");
168 goto on_error;
169 }
170 }
171
172 if (WinHttpSendRequest(t->request, WINHTTP_NO_ADDITIONAL_HEADERS, 0,
173 data, (DWORD)content_length, (DWORD)content_length, 0) == FALSE) {
174 giterr_set(GITERR_OS, "Failed to send request");
175 goto on_error;
176 }
177
178 ret = WinHttpReceiveResponse(t->request, NULL);
179 if (ret == FALSE) {
180 giterr_set(GITERR_OS, "Failed to receive response");
181 goto on_error;
182 }
183
184 return 0;
185
186 on_error:
187 git_buf_free(&buf);
188 if (t->request)
189 WinHttpCloseHandle(t->request);
190 t->request = NULL;
191 return -1;
192 #endif
193 }
194
195 static int do_connect(transport_http *t)
196 {
197 #ifndef GIT_WINHTTP
198 if (t->parent.connected && http_should_keep_alive(&t->parser))
199 return 0;
200
201 if (gitno_connect((git_transport *) t, t->host, t->port) < 0)
202 return -1;
203
204 t->parent.connected = 1;
205
206 return 0;
207 #else
208 wchar_t *ua = L"git/1.0 (libgit2 " WIDEN(LIBGIT2_VERSION) L")";
209 wchar_t host[GIT_WIN_PATH];
210 int32_t port;
211
212 t->session = WinHttpOpen(ua, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
213 WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
214
215 if (t->session == NULL) {
216 giterr_set(GITERR_OS, "Failed to init WinHTTP");
217 goto on_error;
218 }
219
220 git__utf8_to_16(host, GIT_WIN_PATH, t->host);
221
222 if (git__strtol32(&port, t->port, NULL, 10) < 0)
223 goto on_error;
224
225 t->connection = WinHttpConnect(t->session, host, port, 0);
226 if (t->connection == NULL) {
227 giterr_set(GITERR_OS, "Failed to connect to host");
228 goto on_error;
229 }
230
231 t->parent.connected = 1;
232 return 0;
233
234 on_error:
235 if (t->session) {
236 WinHttpCloseHandle(t->session);
237 t->session = NULL;
238 }
239 return -1;
240 #endif
241 }
242
243 /*
244 * The HTTP parser is streaming, so we need to wait until we're in the
245 * field handler before we can be sure that we can store the previous
246 * value. Right now, we only care about the
247 * Content-Type. on_header_{field,value} should be kept generic enough
248 * to work for any request.
249 */
250
251 static const char *typestr = "Content-Type";
252
253 static int on_header_field(http_parser *parser, const char *str, size_t len)
254 {
255 transport_http *t = (transport_http *) parser->data;
256 git_buf *buf = &t->buf;
257
258 if (t->last_cb == VALUE && t->ct_found) {
259 t->ct_finished = 1;
260 t->ct_found = 0;
261 t->content_type = git__strdup(git_buf_cstr(buf));
262 GITERR_CHECK_ALLOC(t->content_type);
263 git_buf_clear(buf);
264 }
265
266 if (t->ct_found) {
267 t->last_cb = FIELD;
268 return 0;
269 }
270
271 if (t->last_cb != FIELD)
272 git_buf_clear(buf);
273
274 git_buf_put(buf, str, len);
275 t->last_cb = FIELD;
276
277 return git_buf_oom(buf);
278 }
279
280 static int on_header_value(http_parser *parser, const char *str, size_t len)
281 {
282 transport_http *t = (transport_http *) parser->data;
283 git_buf *buf = &t->buf;
284
285 if (t->ct_finished) {
286 t->last_cb = VALUE;
287 return 0;
288 }
289
290 if (t->last_cb == VALUE)
291 git_buf_put(buf, str, len);
292
293 if (t->last_cb == FIELD && !strcmp(git_buf_cstr(buf), typestr)) {
294 t->ct_found = 1;
295 git_buf_clear(buf);
296 git_buf_put(buf, str, len);
297 }
298
299 t->last_cb = VALUE;
300
301 return git_buf_oom(buf);
302 }
303
304 static int on_headers_complete(http_parser *parser)
305 {
306 transport_http *t = (transport_http *) parser->data;
307 git_buf *buf = &t->buf;
308
309 /* The content-type is text/plain for 404, so don't validate */
310 if (parser->status_code == 404) {
311 git_buf_clear(buf);
312 return 0;
313 }
314
315 if (t->content_type == NULL) {
316 t->content_type = git__strdup(git_buf_cstr(buf));
317 if (t->content_type == NULL)
318 return t->error = -1;
319 }
320
321 git_buf_clear(buf);
322 git_buf_printf(buf, "application/x-git-%s-advertisement", t->service);
323 if (git_buf_oom(buf))
324 return t->error = -1;
325
326 if (strcmp(t->content_type, git_buf_cstr(buf)))
327 return t->error = -1;
328
329 git_buf_clear(buf);
330 return 0;
331 }
332
333 static int on_message_complete(http_parser *parser)
334 {
335 transport_http *t = (transport_http *) parser->data;
336
337 t->transfer_finished = 1;
338
339 if (parser->status_code == 404) {
340 giterr_set(GITERR_NET, "Remote error: %s", git_buf_cstr(&t->buf));
341 t->error = -1;
342 }
343
344 return 0;
345 }
346
347 static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
348 {
349 git_transport *transport = (git_transport *) parser->data;
350 transport_http *t = (transport_http *) parser->data;
351 gitno_buffer *buf = &transport->buffer;
352
353 if (buf->len - buf->offset < len) {
354 giterr_set(GITERR_NET, "Can't fit data in the buffer");
355 return t->error = -1;
356 }
357
358 memcpy(buf->data + buf->offset, str, len);
359 buf->offset += len;
360
361 return 0;
362 }
363
364 static int http_recv_cb(gitno_buffer *buf)
365 {
366 git_transport *transport = (git_transport *) buf->cb_data;
367 transport_http *t = (transport_http *) transport;
368 size_t old_len;
369 char buffer[2048];
370 #ifdef GIT_WINHTTP
371 DWORD recvd;
372 #else
373 gitno_buffer inner;
374 int error;
375 #endif
376
377 if (t->transfer_finished)
378 return 0;
379
380 #ifndef GIT_WINHTTP
381 gitno_buffer_setup(transport, &inner, buffer, sizeof(buffer));
382
383 if ((error = gitno_recv(&inner)) < 0)
384 return -1;
385
386 old_len = buf->offset;
387 http_parser_execute(&t->parser, &t->settings, inner.data, inner.offset);
388 if (t->error < 0)
389 return t->error;
390 #else
391 old_len = buf->offset;
392 if (WinHttpReadData(t->request, buffer, sizeof(buffer), &recvd) == FALSE) {
393 giterr_set(GITERR_OS, "Failed to read data from the network");
394 return t->error = -1;
395 }
396
397 if (buf->len - buf->offset < recvd) {
398 giterr_set(GITERR_NET, "Can't fit data in the buffer");
399 return t->error = -1;
400 }
401
402 memcpy(buf->data + buf->offset, buffer, recvd);
403 buf->offset += recvd;
404 #endif
405
406 return (int)(buf->offset - old_len);
407 }
408
409 /* Set up the gitno_buffer so calling gitno_recv() grabs data from the HTTP response */
410 static void setup_gitno_buffer(git_transport *transport)
411 {
412 transport_http *t = (transport_http *) transport;
413
414 /* WinHTTP takes care of this for us */
415 #ifndef GIT_WINHTTP
416 http_parser_init(&t->parser, HTTP_RESPONSE);
417 t->parser.data = t;
418 t->transfer_finished = 0;
419 memset(&t->settings, 0x0, sizeof(http_parser_settings));
420 t->settings.on_header_field = on_header_field;
421 t->settings.on_header_value = on_header_value;
422 t->settings.on_headers_complete = on_headers_complete;
423 t->settings.on_body = on_body_fill_buffer;
424 t->settings.on_message_complete = on_message_complete;
425 #endif
426
427 gitno_buffer_setup_callback(transport, &transport->buffer, t->buffer, sizeof(t->buffer), http_recv_cb, t);
428 }
429
430 static int http_connect(git_transport *transport, int direction)
431 {
432 transport_http *t = (transport_http *) transport;
433 int ret;
434 git_buf request = GIT_BUF_INIT;
435 const char *service = "upload-pack";
436 const char *url = t->parent.url, *prefix_http = "http://", *prefix_https = "https://";
437 const char *default_port;
438 git_pkt *pkt;
439
440 if (direction == GIT_DIR_PUSH) {
441 giterr_set(GITERR_NET, "Pushing over HTTP is not implemented");
442 return -1;
443 }
444
445 t->parent.direction = direction;
446
447 if (!git__prefixcmp(url, prefix_http)) {
448 url = t->parent.url + strlen(prefix_http);
449 default_port = "80";
450 }
451
452 if (!git__prefixcmp(url, prefix_https)) {
453 url += strlen(prefix_https);
454 default_port = "443";
455 }
456
457 t->path = strchr(url, '/');
458
459 if ((ret = gitno_extract_host_and_port(&t->host, &t->port, url, default_port)) < 0)
460 goto cleanup;
461
462 t->service = git__strdup(service);
463 GITERR_CHECK_ALLOC(t->service);
464
465 if ((ret = do_connect(t)) < 0)
466 goto cleanup;
467
468 if ((ret = send_request(t, "upload-pack", NULL, 0, 1)) < 0)
469 goto cleanup;
470
471 setup_gitno_buffer(transport);
472 if ((ret = git_protocol_store_refs(transport, 2)) < 0)
473 goto cleanup;
474
475 pkt = git_vector_get(&transport->refs, 0);
476 if (pkt == NULL || pkt->type != GIT_PKT_COMMENT) {
477 giterr_set(GITERR_NET, "Invalid HTTP response");
478 return t->error = -1;
479 } else {
480 /* Remove the comment pkt from the list */
481 git_vector_remove(&transport->refs, 0);
482 git__free(pkt);
483 }
484
485 if (git_protocol_detect_caps(git_vector_get(&transport->refs, 0), &transport->caps) < 0)
486 return t->error = -1;
487
488 cleanup:
489 git_buf_free(&request);
490 git_buf_clear(&t->buf);
491
492 return ret;
493 }
494
495 static int http_negotiation_step(struct git_transport *transport, void *data, size_t len)
496 {
497 transport_http *t = (transport_http *) transport;
498 int ret;
499
500 /* First, send the data as a HTTP POST request */
501 if ((ret = do_connect(t)) < 0)
502 return -1;
503
504 if (send_request(t, "upload-pack", data, len, 0) < 0)
505 return -1;
506
507 /* Then we need to set up the buffer to grab data from the HTTP response */
508 setup_gitno_buffer(transport);
509
510 return 0;
511 }
512
513 static int http_close(git_transport *transport)
514 {
515 #ifndef GIT_WINHTTP
516 if (gitno_ssl_teardown(transport) < 0)
517 return -1;
518
519 if (gitno_close(transport->socket) < 0) {
520 giterr_set(GITERR_OS, "Failed to close the socket: %s", strerror(errno));
521 return -1;
522 }
523 #else
524 transport_http *t = (transport_http *) transport;
525
526 if (t->request)
527 WinHttpCloseHandle(t->request);
528 if (t->connection)
529 WinHttpCloseHandle(t->connection);
530 if (t->session)
531 WinHttpCloseHandle(t->session);
532 #endif
533
534 transport->connected = 0;
535
536 return 0;
537 }
538
539
540 static void http_free(git_transport *transport)
541 {
542 transport_http *t = (transport_http *) transport;
543 git_vector *refs = &transport->refs;
544 git_vector *common = &transport->common;
545 unsigned int i;
546 git_pkt *p;
547
548 #ifdef GIT_WIN32
549 /* cleanup the WSA context. note that this context
550 * can be initialized more than once with WSAStartup(),
551 * and needs to be cleaned one time for each init call
552 */
553 WSACleanup();
554 #endif
555
556 git_vector_foreach(refs, i, p) {
557 git_pkt_free(p);
558 }
559 git_vector_free(refs);
560 git_vector_foreach(common, i, p) {
561 git_pkt_free(p);
562 }
563 git_vector_free(common);
564 git_buf_free(&t->buf);
565 git__free(t->content_type);
566 git__free(t->host);
567 git__free(t->port);
568 git__free(t->service);
569 git__free(t->parent.url);
570 git__free(t);
571 }
572
573 int git_transport_http(git_transport **out)
574 {
575 transport_http *t;
576
577 t = git__malloc(sizeof(transport_http));
578 GITERR_CHECK_ALLOC(t);
579
580 memset(t, 0x0, sizeof(transport_http));
581
582 t->parent.connect = http_connect;
583 t->parent.negotiation_step = http_negotiation_step;
584 t->parent.close = http_close;
585 t->parent.free = http_free;
586 t->parent.rpc = 1;
587
588 if (git_vector_init(&t->parent.refs, 16, NULL) < 0) {
589 git__free(t);
590 return -1;
591 }
592
593 #ifdef GIT_WIN32
594 /* on win32, the WSA context needs to be initialized
595 * before any socket calls can be performed */
596 if (WSAStartup(MAKEWORD(2,2), &t->wsd) != 0) {
597 http_free((git_transport *) t);
598 giterr_set(GITERR_OS, "Winsock init failed");
599 return -1;
600 }
601 #endif
602
603 *out = (git_transport *) t;
604 return 0;
605 }
606
607 int git_transport_https(git_transport **out)
608 {
609 #if defined(GIT_SSL) || defined(GIT_WINHTTP)
610 transport_http *t;
611 if (git_transport_http((git_transport **)&t) < 0)
612 return -1;
613
614 t->parent.use_ssl = 1;
615 t->parent.check_cert = 1;
616 *out = (git_transport *) t;
617
618 return 0;
619 #else
620 GIT_UNUSED(out);
621
622 giterr_set(GITERR_NET, "HTTPS support not available");
623 return -1;
624 #endif
625 }