]> git.proxmox.com Git - libgit2.git/blob - src/transports/http.c
Merge pull request #4260 from libgit2/ethomson/forced_checkout_2
[libgit2.git] / src / transports / http.c
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 #ifndef GIT_WINHTTP
8
9 #include "git2.h"
10 #include "http_parser.h"
11 #include "buffer.h"
12 #include "netops.h"
13 #include "global.h"
14 #include "remote.h"
15 #include "smart.h"
16 #include "auth.h"
17 #include "auth_negotiate.h"
18 #include "tls_stream.h"
19 #include "socket_stream.h"
20 #include "curl_stream.h"
21
22 git_http_auth_scheme auth_schemes[] = {
23 { GIT_AUTHTYPE_NEGOTIATE, "Negotiate", GIT_CREDTYPE_DEFAULT, git_http_auth_negotiate },
24 { GIT_AUTHTYPE_BASIC, "Basic", GIT_CREDTYPE_USERPASS_PLAINTEXT, git_http_auth_basic },
25 };
26
27 static const char *upload_pack_service = "upload-pack";
28 static const char *upload_pack_ls_service_url = "/info/refs?service=git-upload-pack";
29 static const char *upload_pack_service_url = "/git-upload-pack";
30 static const char *receive_pack_service = "receive-pack";
31 static const char *receive_pack_ls_service_url = "/info/refs?service=git-receive-pack";
32 static const char *receive_pack_service_url = "/git-receive-pack";
33 static const char *get_verb = "GET";
34 static const char *post_verb = "POST";
35
36 #define OWNING_SUBTRANSPORT(s) ((http_subtransport *)(s)->parent.subtransport)
37
38 #define PARSE_ERROR_GENERIC -1
39 #define PARSE_ERROR_REPLAY -2
40 /** Look at the user field */
41 #define PARSE_ERROR_EXT -3
42
43 #define CHUNK_SIZE 4096
44
45 enum last_cb {
46 NONE,
47 FIELD,
48 VALUE
49 };
50
51 typedef struct {
52 git_smart_subtransport_stream parent;
53 const char *service;
54 const char *service_url;
55 char *redirect_url;
56 const char *verb;
57 char *chunk_buffer;
58 unsigned chunk_buffer_len;
59 unsigned sent_request : 1,
60 received_response : 1,
61 chunked : 1,
62 redirect_count : 3;
63 } http_stream;
64
65 typedef struct {
66 git_smart_subtransport parent;
67 transport_smart *owner;
68 git_stream *io;
69 gitno_connection_data connection_data;
70 bool connected;
71
72 /* Parser structures */
73 http_parser parser;
74 http_parser_settings settings;
75 gitno_buffer parse_buffer;
76 git_buf parse_header_name;
77 git_buf parse_header_value;
78 char parse_buffer_data[NETIO_BUFSIZE];
79 char *content_type;
80 char *location;
81 git_vector www_authenticate;
82 enum last_cb last_cb;
83 int parse_error;
84 int error;
85 unsigned parse_finished : 1;
86
87 /* Authentication */
88 git_cred *cred;
89 git_cred *url_cred;
90 git_vector auth_contexts;
91 } http_subtransport;
92
93 typedef struct {
94 http_stream *s;
95 http_subtransport *t;
96
97 /* Target buffer details from read() */
98 char *buffer;
99 size_t buf_size;
100 size_t *bytes_read;
101 } parser_context;
102
103 static bool credtype_match(git_http_auth_scheme *scheme, void *data)
104 {
105 unsigned int credtype = *(unsigned int *)data;
106
107 return !!(scheme->credtypes & credtype);
108 }
109
110 static bool challenge_match(git_http_auth_scheme *scheme, void *data)
111 {
112 const char *scheme_name = scheme->name;
113 const char *challenge = (const char *)data;
114 size_t scheme_len;
115
116 scheme_len = strlen(scheme_name);
117 return (strncasecmp(challenge, scheme_name, scheme_len) == 0 &&
118 (challenge[scheme_len] == '\0' || challenge[scheme_len] == ' '));
119 }
120
121 static int auth_context_match(
122 git_http_auth_context **out,
123 http_subtransport *t,
124 bool (*scheme_match)(git_http_auth_scheme *scheme, void *data),
125 void *data)
126 {
127 git_http_auth_scheme *scheme = NULL;
128 git_http_auth_context *context = NULL, *c;
129 size_t i;
130
131 *out = NULL;
132
133 for (i = 0; i < ARRAY_SIZE(auth_schemes); i++) {
134 if (scheme_match(&auth_schemes[i], data)) {
135 scheme = &auth_schemes[i];
136 break;
137 }
138 }
139
140 if (!scheme)
141 return 0;
142
143 /* See if authentication has already started for this scheme */
144 git_vector_foreach(&t->auth_contexts, i, c) {
145 if (c->type == scheme->type) {
146 context = c;
147 break;
148 }
149 }
150
151 if (!context) {
152 if (scheme->init_context(&context, &t->connection_data) < 0)
153 return -1;
154 else if (!context)
155 return 0;
156 else if (git_vector_insert(&t->auth_contexts, context) < 0)
157 return -1;
158 }
159
160 *out = context;
161
162 return 0;
163 }
164
165 static int apply_credentials(git_buf *buf, http_subtransport *t)
166 {
167 git_cred *cred = t->cred;
168 git_http_auth_context *context;
169
170 /* Apply the credentials given to us in the URL */
171 if (!cred && t->connection_data.user && t->connection_data.pass) {
172 if (!t->url_cred &&
173 git_cred_userpass_plaintext_new(&t->url_cred,
174 t->connection_data.user, t->connection_data.pass) < 0)
175 return -1;
176
177 cred = t->url_cred;
178 }
179
180 if (!cred)
181 return 0;
182
183 /* Get or create a context for the best scheme for this cred type */
184 if (auth_context_match(&context, t, credtype_match, &cred->credtype) < 0)
185 return -1;
186
187 return context->next_token(buf, context, cred);
188 }
189
190 static const char *user_agent(void)
191 {
192 const char *custom = git_libgit2__user_agent();
193
194 if (custom)
195 return custom;
196
197 return "libgit2 " LIBGIT2_VERSION;
198 }
199
200 static int gen_request(
201 git_buf *buf,
202 http_stream *s,
203 size_t content_length)
204 {
205 http_subtransport *t = OWNING_SUBTRANSPORT(s);
206 const char *path = t->connection_data.path ? t->connection_data.path : "/";
207 size_t i;
208
209 git_buf_printf(buf, "%s %s%s HTTP/1.1\r\n", s->verb, path, s->service_url);
210
211 git_buf_printf(buf, "User-Agent: git/2.0 (%s)\r\n", user_agent());
212 git_buf_printf(buf, "Host: %s\r\n", t->connection_data.host);
213
214 if (s->chunked || content_length > 0) {
215 git_buf_printf(buf, "Accept: application/x-git-%s-result\r\n", s->service);
216 git_buf_printf(buf, "Content-Type: application/x-git-%s-request\r\n", s->service);
217
218 if (s->chunked)
219 git_buf_puts(buf, "Transfer-Encoding: chunked\r\n");
220 else
221 git_buf_printf(buf, "Content-Length: %"PRIuZ "\r\n", content_length);
222 } else
223 git_buf_puts(buf, "Accept: */*\r\n");
224
225 for (i = 0; i < t->owner->custom_headers.count; i++) {
226 if (t->owner->custom_headers.strings[i])
227 git_buf_printf(buf, "%s\r\n", t->owner->custom_headers.strings[i]);
228 }
229
230 /* Apply credentials to the request */
231 if (apply_credentials(buf, t) < 0)
232 return -1;
233
234 git_buf_puts(buf, "\r\n");
235
236 if (git_buf_oom(buf))
237 return -1;
238
239 return 0;
240 }
241
242 static int parse_authenticate_response(
243 git_vector *www_authenticate,
244 http_subtransport *t,
245 int *allowed_types)
246 {
247 git_http_auth_context *context;
248 char *challenge;
249 size_t i;
250
251 git_vector_foreach(www_authenticate, i, challenge) {
252 if (auth_context_match(&context, t, challenge_match, challenge) < 0)
253 return -1;
254 else if (!context)
255 continue;
256
257 if (context->set_challenge &&
258 context->set_challenge(context, challenge) < 0)
259 return -1;
260
261 *allowed_types |= context->credtypes;
262 }
263
264 return 0;
265 }
266
267 static int on_header_ready(http_subtransport *t)
268 {
269 git_buf *name = &t->parse_header_name;
270 git_buf *value = &t->parse_header_value;
271
272 if (!strcasecmp("Content-Type", git_buf_cstr(name))) {
273 if (!t->content_type) {
274 t->content_type = git__strdup(git_buf_cstr(value));
275 GITERR_CHECK_ALLOC(t->content_type);
276 }
277 }
278 else if (!strcasecmp("WWW-Authenticate", git_buf_cstr(name))) {
279 char *dup = git__strdup(git_buf_cstr(value));
280 GITERR_CHECK_ALLOC(dup);
281
282 git_vector_insert(&t->www_authenticate, dup);
283 }
284 else if (!strcasecmp("Location", git_buf_cstr(name))) {
285 if (!t->location) {
286 t->location = git__strdup(git_buf_cstr(value));
287 GITERR_CHECK_ALLOC(t->location);
288 }
289 }
290
291 return 0;
292 }
293
294 static int on_header_field(http_parser *parser, const char *str, size_t len)
295 {
296 parser_context *ctx = (parser_context *) parser->data;
297 http_subtransport *t = ctx->t;
298
299 /* Both parse_header_name and parse_header_value are populated
300 * and ready for consumption */
301 if (VALUE == t->last_cb)
302 if (on_header_ready(t) < 0)
303 return t->parse_error = PARSE_ERROR_GENERIC;
304
305 if (NONE == t->last_cb || VALUE == t->last_cb)
306 git_buf_clear(&t->parse_header_name);
307
308 if (git_buf_put(&t->parse_header_name, str, len) < 0)
309 return t->parse_error = PARSE_ERROR_GENERIC;
310
311 t->last_cb = FIELD;
312 return 0;
313 }
314
315 static int on_header_value(http_parser *parser, const char *str, size_t len)
316 {
317 parser_context *ctx = (parser_context *) parser->data;
318 http_subtransport *t = ctx->t;
319
320 assert(NONE != t->last_cb);
321
322 if (FIELD == t->last_cb)
323 git_buf_clear(&t->parse_header_value);
324
325 if (git_buf_put(&t->parse_header_value, str, len) < 0)
326 return t->parse_error = PARSE_ERROR_GENERIC;
327
328 t->last_cb = VALUE;
329 return 0;
330 }
331
332 static int on_headers_complete(http_parser *parser)
333 {
334 parser_context *ctx = (parser_context *) parser->data;
335 http_subtransport *t = ctx->t;
336 http_stream *s = ctx->s;
337 git_buf buf = GIT_BUF_INIT;
338 int error = 0, no_callback = 0, allowed_auth_types = 0;
339
340 /* Both parse_header_name and parse_header_value are populated
341 * and ready for consumption. */
342 if (VALUE == t->last_cb)
343 if (on_header_ready(t) < 0)
344 return t->parse_error = PARSE_ERROR_GENERIC;
345
346 /* Capture authentication headers which may be a 401 (authentication
347 * is not complete) or a 200 (simply informing us that auth *is*
348 * complete.)
349 */
350 if (parse_authenticate_response(&t->www_authenticate, t,
351 &allowed_auth_types) < 0)
352 return t->parse_error = PARSE_ERROR_GENERIC;
353
354 /* Check for an authentication failure. */
355 if (parser->status_code == 401 && get_verb == s->verb) {
356 if (!t->owner->cred_acquire_cb) {
357 no_callback = 1;
358 } else {
359 if (allowed_auth_types) {
360 if (t->cred) {
361 t->cred->free(t->cred);
362 t->cred = NULL;
363 }
364
365 error = t->owner->cred_acquire_cb(&t->cred,
366 t->owner->url,
367 t->connection_data.user,
368 allowed_auth_types,
369 t->owner->cred_acquire_payload);
370
371 if (error == GIT_PASSTHROUGH) {
372 no_callback = 1;
373 } else if (error < 0) {
374 t->error = error;
375 return t->parse_error = PARSE_ERROR_EXT;
376 } else {
377 assert(t->cred);
378
379 if (!(t->cred->credtype & allowed_auth_types)) {
380 giterr_set(GITERR_NET, "credentials callback returned an invalid cred type");
381 return t->parse_error = PARSE_ERROR_GENERIC;
382 }
383
384 /* Successfully acquired a credential. */
385 t->parse_error = PARSE_ERROR_REPLAY;
386 return 0;
387 }
388 }
389 }
390
391 if (no_callback) {
392 giterr_set(GITERR_NET, "authentication required but no callback set");
393 return t->parse_error = PARSE_ERROR_GENERIC;
394 }
395 }
396
397 /* Check for a redirect.
398 * Right now we only permit a redirect to the same hostname. */
399 if ((parser->status_code == 301 ||
400 parser->status_code == 302 ||
401 (parser->status_code == 303 && get_verb == s->verb) ||
402 parser->status_code == 307) &&
403 t->location) {
404
405 if (s->redirect_count >= 7) {
406 giterr_set(GITERR_NET, "too many redirects");
407 return t->parse_error = PARSE_ERROR_GENERIC;
408 }
409
410 if (gitno_connection_data_from_url(&t->connection_data, t->location, s->service_url) < 0)
411 return t->parse_error = PARSE_ERROR_GENERIC;
412
413 /* Set the redirect URL on the stream. This is a transfer of
414 * ownership of the memory. */
415 if (s->redirect_url)
416 git__free(s->redirect_url);
417
418 s->redirect_url = t->location;
419 t->location = NULL;
420
421 t->connected = 0;
422 s->redirect_count++;
423
424 t->parse_error = PARSE_ERROR_REPLAY;
425 return 0;
426 }
427
428 /* Check for a 200 HTTP status code. */
429 if (parser->status_code != 200) {
430 giterr_set(GITERR_NET,
431 "unexpected HTTP status code: %d",
432 parser->status_code);
433 return t->parse_error = PARSE_ERROR_GENERIC;
434 }
435
436 /* The response must contain a Content-Type header. */
437 if (!t->content_type) {
438 giterr_set(GITERR_NET, "no Content-Type header in response");
439 return t->parse_error = PARSE_ERROR_GENERIC;
440 }
441
442 /* The Content-Type header must match our expectation. */
443 if (get_verb == s->verb)
444 git_buf_printf(&buf,
445 "application/x-git-%s-advertisement",
446 ctx->s->service);
447 else
448 git_buf_printf(&buf,
449 "application/x-git-%s-result",
450 ctx->s->service);
451
452 if (git_buf_oom(&buf))
453 return t->parse_error = PARSE_ERROR_GENERIC;
454
455 if (strcmp(t->content_type, git_buf_cstr(&buf))) {
456 git_buf_free(&buf);
457 giterr_set(GITERR_NET,
458 "invalid Content-Type: %s",
459 t->content_type);
460 return t->parse_error = PARSE_ERROR_GENERIC;
461 }
462
463 git_buf_free(&buf);
464
465 return 0;
466 }
467
468 static int on_message_complete(http_parser *parser)
469 {
470 parser_context *ctx = (parser_context *) parser->data;
471 http_subtransport *t = ctx->t;
472
473 t->parse_finished = 1;
474
475 return 0;
476 }
477
478 static int on_body_fill_buffer(http_parser *parser, const char *str, size_t len)
479 {
480 parser_context *ctx = (parser_context *) parser->data;
481 http_subtransport *t = ctx->t;
482
483 /* If our goal is to replay the request (either an auth failure or
484 * a redirect) then don't bother buffering since we're ignoring the
485 * content anyway.
486 */
487 if (t->parse_error == PARSE_ERROR_REPLAY)
488 return 0;
489
490 if (ctx->buf_size < len) {
491 giterr_set(GITERR_NET, "can't fit data in the buffer");
492 return t->parse_error = PARSE_ERROR_GENERIC;
493 }
494
495 memcpy(ctx->buffer, str, len);
496 *(ctx->bytes_read) += len;
497 ctx->buffer += len;
498 ctx->buf_size -= len;
499
500 return 0;
501 }
502
503 static void clear_parser_state(http_subtransport *t)
504 {
505 http_parser_init(&t->parser, HTTP_RESPONSE);
506 gitno_buffer_setup_fromstream(t->io,
507 &t->parse_buffer,
508 t->parse_buffer_data,
509 sizeof(t->parse_buffer_data));
510
511 t->last_cb = NONE;
512 t->parse_error = 0;
513 t->parse_finished = 0;
514
515 git_buf_free(&t->parse_header_name);
516 git_buf_init(&t->parse_header_name, 0);
517
518 git_buf_free(&t->parse_header_value);
519 git_buf_init(&t->parse_header_value, 0);
520
521 git__free(t->content_type);
522 t->content_type = NULL;
523
524 git__free(t->location);
525 t->location = NULL;
526
527 git_vector_free_deep(&t->www_authenticate);
528 }
529
530 static int write_chunk(git_stream *io, const char *buffer, size_t len)
531 {
532 git_buf buf = GIT_BUF_INIT;
533
534 /* Chunk header */
535 git_buf_printf(&buf, "%" PRIxZ "\r\n", len);
536
537 if (git_buf_oom(&buf))
538 return -1;
539
540 if (git_stream_write(io, buf.ptr, buf.size, 0) < 0) {
541 git_buf_free(&buf);
542 return -1;
543 }
544
545 git_buf_free(&buf);
546
547 /* Chunk body */
548 if (len > 0 && git_stream_write(io, buffer, len, 0) < 0)
549 return -1;
550
551 /* Chunk footer */
552 if (git_stream_write(io, "\r\n", 2, 0) < 0)
553 return -1;
554
555 return 0;
556 }
557
558 static int apply_proxy_config(http_subtransport *t)
559 {
560 int error;
561 git_proxy_t proxy_type;
562
563 if (!git_stream_supports_proxy(t->io))
564 return 0;
565
566 proxy_type = t->owner->proxy.type;
567
568 if (proxy_type == GIT_PROXY_NONE)
569 return 0;
570
571 if (proxy_type == GIT_PROXY_AUTO) {
572 char *url;
573 git_proxy_options opts = GIT_PROXY_OPTIONS_INIT;
574
575 if ((error = git_remote__get_http_proxy(t->owner->owner, !!t->connection_data.use_ssl, &url)) < 0)
576 return error;
577
578 opts.credentials = t->owner->proxy.credentials;
579 opts.certificate_check = t->owner->proxy.certificate_check;
580 opts.payload = t->owner->proxy.payload;
581 opts.type = GIT_PROXY_SPECIFIED;
582 opts.url = url;
583 error = git_stream_set_proxy(t->io, &opts);
584 git__free(url);
585
586 return error;
587 }
588
589 return git_stream_set_proxy(t->io, &t->owner->proxy);
590 }
591
592 static int http_connect(http_subtransport *t)
593 {
594 int error;
595
596 if (t->connected &&
597 http_should_keep_alive(&t->parser) &&
598 t->parse_finished)
599 return 0;
600
601 if (t->io) {
602 git_stream_close(t->io);
603 git_stream_free(t->io);
604 t->io = NULL;
605 t->connected = 0;
606 }
607
608 if (t->connection_data.use_ssl) {
609 error = git_tls_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
610 } else {
611 #ifdef GIT_CURL
612 error = git_curl_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
613 #else
614 error = git_socket_stream_new(&t->io, t->connection_data.host, t->connection_data.port);
615 #endif
616 }
617
618 if (error < 0)
619 return error;
620
621 GITERR_CHECK_VERSION(t->io, GIT_STREAM_VERSION, "git_stream");
622
623 apply_proxy_config(t);
624
625 error = git_stream_connect(t->io);
626
627 if ((!error || error == GIT_ECERTIFICATE) && t->owner->certificate_check_cb != NULL &&
628 git_stream_is_encrypted(t->io)) {
629 git_cert *cert;
630 int is_valid = (error == GIT_OK);
631
632 if ((error = git_stream_certificate(&cert, t->io)) < 0)
633 return error;
634
635 giterr_clear();
636 error = t->owner->certificate_check_cb(cert, is_valid, t->connection_data.host, t->owner->message_cb_payload);
637
638 if (error < 0) {
639 if (!giterr_last())
640 giterr_set(GITERR_NET, "user cancelled certificate check");
641
642 return error;
643 }
644 }
645
646 if (error < 0)
647 return error;
648
649 t->connected = 1;
650 return 0;
651 }
652
653 static int http_stream_read(
654 git_smart_subtransport_stream *stream,
655 char *buffer,
656 size_t buf_size,
657 size_t *bytes_read)
658 {
659 http_stream *s = (http_stream *)stream;
660 http_subtransport *t = OWNING_SUBTRANSPORT(s);
661 parser_context ctx;
662 size_t bytes_parsed;
663
664 replay:
665 *bytes_read = 0;
666
667 assert(t->connected);
668
669 if (!s->sent_request) {
670 git_buf request = GIT_BUF_INIT;
671
672 clear_parser_state(t);
673
674 if (gen_request(&request, s, 0) < 0)
675 return -1;
676
677 if (git_stream_write(t->io, request.ptr, request.size, 0) < 0) {
678 git_buf_free(&request);
679 return -1;
680 }
681
682 git_buf_free(&request);
683
684 s->sent_request = 1;
685 }
686
687 if (!s->received_response) {
688 if (s->chunked) {
689 assert(s->verb == post_verb);
690
691 /* Flush, if necessary */
692 if (s->chunk_buffer_len > 0 &&
693 write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
694 return -1;
695
696 s->chunk_buffer_len = 0;
697
698 /* Write the final chunk. */
699 if (git_stream_write(t->io, "0\r\n\r\n", 5, 0) < 0)
700 return -1;
701 }
702
703 s->received_response = 1;
704 }
705
706 while (!*bytes_read && !t->parse_finished) {
707 size_t data_offset;
708 int error;
709
710 /*
711 * Make the parse_buffer think it's as full of data as
712 * the buffer, so it won't try to recv more data than
713 * we can put into it.
714 *
715 * data_offset is the actual data offset from which we
716 * should tell the parser to start reading.
717 */
718 if (buf_size >= t->parse_buffer.len) {
719 t->parse_buffer.offset = 0;
720 } else {
721 t->parse_buffer.offset = t->parse_buffer.len - buf_size;
722 }
723
724 data_offset = t->parse_buffer.offset;
725
726 if (gitno_recv(&t->parse_buffer) < 0)
727 return -1;
728
729 /* This call to http_parser_execute will result in invocations of the
730 * on_* family of callbacks. The most interesting of these is
731 * on_body_fill_buffer, which is called when data is ready to be copied
732 * into the target buffer. We need to marshal the buffer, buf_size, and
733 * bytes_read parameters to this callback. */
734 ctx.t = t;
735 ctx.s = s;
736 ctx.buffer = buffer;
737 ctx.buf_size = buf_size;
738 ctx.bytes_read = bytes_read;
739
740 /* Set the context, call the parser, then unset the context. */
741 t->parser.data = &ctx;
742
743 bytes_parsed = http_parser_execute(&t->parser,
744 &t->settings,
745 t->parse_buffer.data + data_offset,
746 t->parse_buffer.offset - data_offset);
747
748 t->parser.data = NULL;
749
750 /* If there was a handled authentication failure, then parse_error
751 * will have signaled us that we should replay the request. */
752 if (PARSE_ERROR_REPLAY == t->parse_error) {
753 s->sent_request = 0;
754
755 if ((error = http_connect(t)) < 0)
756 return error;
757
758 goto replay;
759 }
760
761 if (t->parse_error == PARSE_ERROR_EXT) {
762 return t->error;
763 }
764
765 if (t->parse_error < 0)
766 return -1;
767
768 if (bytes_parsed != t->parse_buffer.offset - data_offset) {
769 giterr_set(GITERR_NET,
770 "HTTP parser error: %s",
771 http_errno_description((enum http_errno)t->parser.http_errno));
772 return -1;
773 }
774 }
775
776 return 0;
777 }
778
779 static int http_stream_write_chunked(
780 git_smart_subtransport_stream *stream,
781 const char *buffer,
782 size_t len)
783 {
784 http_stream *s = (http_stream *)stream;
785 http_subtransport *t = OWNING_SUBTRANSPORT(s);
786
787 assert(t->connected);
788
789 /* Send the request, if necessary */
790 if (!s->sent_request) {
791 git_buf request = GIT_BUF_INIT;
792
793 clear_parser_state(t);
794
795 if (gen_request(&request, s, 0) < 0)
796 return -1;
797
798 if (git_stream_write(t->io, request.ptr, request.size, 0) < 0) {
799 git_buf_free(&request);
800 return -1;
801 }
802
803 git_buf_free(&request);
804
805 s->sent_request = 1;
806 }
807
808 if (len > CHUNK_SIZE) {
809 /* Flush, if necessary */
810 if (s->chunk_buffer_len > 0) {
811 if (write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
812 return -1;
813
814 s->chunk_buffer_len = 0;
815 }
816
817 /* Write chunk directly */
818 if (write_chunk(t->io, buffer, len) < 0)
819 return -1;
820 }
821 else {
822 /* Append as much to the buffer as we can */
823 int count = min(CHUNK_SIZE - s->chunk_buffer_len, len);
824
825 if (!s->chunk_buffer)
826 s->chunk_buffer = git__malloc(CHUNK_SIZE);
827
828 memcpy(s->chunk_buffer + s->chunk_buffer_len, buffer, count);
829 s->chunk_buffer_len += count;
830 buffer += count;
831 len -= count;
832
833 /* Is the buffer full? If so, then flush */
834 if (CHUNK_SIZE == s->chunk_buffer_len) {
835 if (write_chunk(t->io, s->chunk_buffer, s->chunk_buffer_len) < 0)
836 return -1;
837
838 s->chunk_buffer_len = 0;
839
840 if (len > 0) {
841 memcpy(s->chunk_buffer, buffer, len);
842 s->chunk_buffer_len = len;
843 }
844 }
845 }
846
847 return 0;
848 }
849
850 static int http_stream_write_single(
851 git_smart_subtransport_stream *stream,
852 const char *buffer,
853 size_t len)
854 {
855 http_stream *s = (http_stream *)stream;
856 http_subtransport *t = OWNING_SUBTRANSPORT(s);
857 git_buf request = GIT_BUF_INIT;
858
859 assert(t->connected);
860
861 if (s->sent_request) {
862 giterr_set(GITERR_NET, "subtransport configured for only one write");
863 return -1;
864 }
865
866 clear_parser_state(t);
867
868 if (gen_request(&request, s, len) < 0)
869 return -1;
870
871 if (git_stream_write(t->io, request.ptr, request.size, 0) < 0)
872 goto on_error;
873
874 if (len && git_stream_write(t->io, buffer, len, 0) < 0)
875 goto on_error;
876
877 git_buf_free(&request);
878 s->sent_request = 1;
879
880 return 0;
881
882 on_error:
883 git_buf_free(&request);
884 return -1;
885 }
886
887 static void http_stream_free(git_smart_subtransport_stream *stream)
888 {
889 http_stream *s = (http_stream *)stream;
890
891 if (s->chunk_buffer)
892 git__free(s->chunk_buffer);
893
894 if (s->redirect_url)
895 git__free(s->redirect_url);
896
897 git__free(s);
898 }
899
900 static int http_stream_alloc(http_subtransport *t,
901 git_smart_subtransport_stream **stream)
902 {
903 http_stream *s;
904
905 if (!stream)
906 return -1;
907
908 s = git__calloc(sizeof(http_stream), 1);
909 GITERR_CHECK_ALLOC(s);
910
911 s->parent.subtransport = &t->parent;
912 s->parent.read = http_stream_read;
913 s->parent.write = http_stream_write_single;
914 s->parent.free = http_stream_free;
915
916 *stream = (git_smart_subtransport_stream *)s;
917 return 0;
918 }
919
920 static int http_uploadpack_ls(
921 http_subtransport *t,
922 git_smart_subtransport_stream **stream)
923 {
924 http_stream *s;
925
926 if (http_stream_alloc(t, stream) < 0)
927 return -1;
928
929 s = (http_stream *)*stream;
930
931 s->service = upload_pack_service;
932 s->service_url = upload_pack_ls_service_url;
933 s->verb = get_verb;
934
935 return 0;
936 }
937
938 static int http_uploadpack(
939 http_subtransport *t,
940 git_smart_subtransport_stream **stream)
941 {
942 http_stream *s;
943
944 if (http_stream_alloc(t, stream) < 0)
945 return -1;
946
947 s = (http_stream *)*stream;
948
949 s->service = upload_pack_service;
950 s->service_url = upload_pack_service_url;
951 s->verb = post_verb;
952
953 return 0;
954 }
955
956 static int http_receivepack_ls(
957 http_subtransport *t,
958 git_smart_subtransport_stream **stream)
959 {
960 http_stream *s;
961
962 if (http_stream_alloc(t, stream) < 0)
963 return -1;
964
965 s = (http_stream *)*stream;
966
967 s->service = receive_pack_service;
968 s->service_url = receive_pack_ls_service_url;
969 s->verb = get_verb;
970
971 return 0;
972 }
973
974 static int http_receivepack(
975 http_subtransport *t,
976 git_smart_subtransport_stream **stream)
977 {
978 http_stream *s;
979
980 if (http_stream_alloc(t, stream) < 0)
981 return -1;
982
983 s = (http_stream *)*stream;
984
985 /* Use Transfer-Encoding: chunked for this request */
986 s->chunked = 1;
987 s->parent.write = http_stream_write_chunked;
988
989 s->service = receive_pack_service;
990 s->service_url = receive_pack_service_url;
991 s->verb = post_verb;
992
993 return 0;
994 }
995
996 static int http_action(
997 git_smart_subtransport_stream **stream,
998 git_smart_subtransport *subtransport,
999 const char *url,
1000 git_smart_service_t action)
1001 {
1002 http_subtransport *t = (http_subtransport *)subtransport;
1003 int ret;
1004
1005 if (!stream)
1006 return -1;
1007
1008 if ((!t->connection_data.host || !t->connection_data.port || !t->connection_data.path) &&
1009 (ret = gitno_connection_data_from_url(&t->connection_data, url, NULL)) < 0)
1010 return ret;
1011
1012 if ((ret = http_connect(t)) < 0)
1013 return ret;
1014
1015 switch (action) {
1016 case GIT_SERVICE_UPLOADPACK_LS:
1017 return http_uploadpack_ls(t, stream);
1018
1019 case GIT_SERVICE_UPLOADPACK:
1020 return http_uploadpack(t, stream);
1021
1022 case GIT_SERVICE_RECEIVEPACK_LS:
1023 return http_receivepack_ls(t, stream);
1024
1025 case GIT_SERVICE_RECEIVEPACK:
1026 return http_receivepack(t, stream);
1027 }
1028
1029 *stream = NULL;
1030 return -1;
1031 }
1032
1033 static int http_close(git_smart_subtransport *subtransport)
1034 {
1035 http_subtransport *t = (http_subtransport *) subtransport;
1036 git_http_auth_context *context;
1037 size_t i;
1038
1039 clear_parser_state(t);
1040
1041 t->connected = 0;
1042
1043 if (t->io) {
1044 git_stream_close(t->io);
1045 git_stream_free(t->io);
1046 t->io = NULL;
1047 }
1048
1049 if (t->cred) {
1050 t->cred->free(t->cred);
1051 t->cred = NULL;
1052 }
1053
1054 if (t->url_cred) {
1055 t->url_cred->free(t->url_cred);
1056 t->url_cred = NULL;
1057 }
1058
1059 git_vector_foreach(&t->auth_contexts, i, context) {
1060 if (context->free)
1061 context->free(context);
1062 }
1063
1064 git_vector_clear(&t->auth_contexts);
1065
1066 gitno_connection_data_free_ptrs(&t->connection_data);
1067 memset(&t->connection_data, 0x0, sizeof(gitno_connection_data));
1068
1069 return 0;
1070 }
1071
1072 static void http_free(git_smart_subtransport *subtransport)
1073 {
1074 http_subtransport *t = (http_subtransport *) subtransport;
1075
1076 http_close(subtransport);
1077
1078 git_vector_free(&t->auth_contexts);
1079 git__free(t);
1080 }
1081
1082 int git_smart_subtransport_http(git_smart_subtransport **out, git_transport *owner, void *param)
1083 {
1084 http_subtransport *t;
1085
1086 GIT_UNUSED(param);
1087
1088 if (!out)
1089 return -1;
1090
1091 t = git__calloc(sizeof(http_subtransport), 1);
1092 GITERR_CHECK_ALLOC(t);
1093
1094 t->owner = (transport_smart *)owner;
1095 t->parent.action = http_action;
1096 t->parent.close = http_close;
1097 t->parent.free = http_free;
1098
1099 t->settings.on_header_field = on_header_field;
1100 t->settings.on_header_value = on_header_value;
1101 t->settings.on_headers_complete = on_headers_complete;
1102 t->settings.on_body = on_body_fill_buffer;
1103 t->settings.on_message_complete = on_message_complete;
1104
1105 *out = (git_smart_subtransport *) t;
1106 return 0;
1107 }
1108
1109 #endif /* !GIT_WINHTTP */